-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathtesting.af
More file actions
207 lines (166 loc) · 4.85 KB
/
testing.af
File metadata and controls
207 lines (166 loc) · 4.85 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
import' ./lang.af
\ Similar to the common Forth testing words:
\
\ https://forth-standard.org/standard/testsuite
\
\ Example tests:
\
\ T{ 10 20 <T> 10 20 }T \ OK
\ T{ 10 20 <T> 10 20 30 }T \ fail
\ T{ 10 20 30 <T> 10 20 }T \ fail
\
\ Internal note. In intepretation, testing uses the default stack,
\ provided by the interpreter (`STACK`). In compiled code, testing
\ uses its own custom stack for compatibility with AOT compilation.
fun: testing_stack_eq { len0 len1 len stk -- equal }
len 0 +for: ind
ind len0 + stk stack_at @ { one }
ind len1 + stk stack_at @ { two }
one two <> if false ret end
end
true
end
fun: testing_stack_log { len0 len1 len2 stk }
" T{ " elog
len1 len0 +for: ind
ind stk stack_at @ { val }
" %zd " val elogf
end
" <T> " elog
len2 ind +for: ind
ind stk stack_at @ { val }
" %zd " val elogf
end
" }T" elog elf
end
\ ## Interpretation-mode test routines (top level)
\ Used only in interpretation.
0 var: T_ST_LEN_0 \ Stack length at `T{`.
0 var: T_ST_LEN_1 \ Stack length at `<T>`.
fun: testing_interp_reset { stk }
T_ST_LEN_0 @ stk stack_len_set
T_ST_LEN_0 off!
T_ST_LEN_1 off!
end
fun: T{ STACK stack_len T_ST_LEN_0 ! end
fun: <T> STACK stack_len T_ST_LEN_1 ! end
\ 10 20 T{ 30 40 <T> 50 60 }T
\ ^ len0 = 2 ^ len1 = 4 ^ len2 = 6
\ ^ rel0 = 2 ^ rel1 = 2
fun: }T
STACK { stk }
stk stack_len { len2 } \ Length at `}T`.
T_ST_LEN_1 @ { len1 } \ Stack length at `<T>`.
T_ST_LEN_0 @ { len0 } \ Stack length at `T{`.
len1 len0 - { rel0 } \ Relative length before `<T>`.
len2 len1 - { rel1 } \ Relative length after `<T>`.
\ Does the stack length match?
rel0 rel1 =
if
\ Does the content match?
len0 len1 rel1 stk testing_stack_eq
if stk testing_interp_reset ret end
else
" [test] stack length mismatch: (%zd) <T> (%zd)" rel0 rel1 elogf elf
end
" [test] stack content mismatch: " elog
len0 len1 len2 stk testing_stack_log
stk testing_interp_reset
" test failure" throw
unreachable
end
\ ## Compilation-mode test routines (inside words)
Stack mem: TEST_STACK
false var: TEST_STACK_INITED_AOT \ Must remain `false` in JIT.
32 TEST_STACK stack_init
\ Captured at `<T>`, used only in compilation. We could have used
\ the regular control stack for this, but using a dedicated place
\ is probably more reliable, since buggy code could clobber that.
0 var: TEST_ARG_LEN
\ Needed in AOT execution.
\
\ We initialize `TEST_STACK` in initial interpretation. AOT compilation
\ preserves the state of the stack header, but the addresses it contains
\ are no longer valid, since the memory was requested from the OS and is
\ not managed by the compiler. This re-inits the stack in AOT, making it
\ valid again.
\
\ TODO: general solution for similar cases.
fun: testing_stack_init_aot
has_interp if ret end
TEST_STACK_INITED_AOT @ if ret end
32 TEST_STACK stack_init
TEST_STACK_INITED_AOT on!
end
fun: testing_compiled_reset
TEST_STACK stack_clear
TEST_ARG_LEN off!
end
fun: testing_compile_push_args { len }
len ifn ret end
len { stk_reg }
len inc { top_reg }
stk_reg comp_clobber
top_reg comp_clobber
TEST_STACK stk_reg comp_page_addr
len stk_reg top_reg comp_args_to_stack
0 comp_args_set
end
fun_comp: T{
" when calling `T{`" 0 comp_args_valid
TEST_ARG_LEN @ if
" internal error: non-empty `TEST_ARG_LEN` at `T{`"
throw
end
\ Briefly disabling auto-catching allows testing to work
\ seamlessly in tests annotated with `[ true catches ]`.
get_catches { ok }
ok if false catches end
compile' testing_stack_init_aot
ok if true catches end
end
fun_comp: <T>
comp_args_get { len }
len testing_compile_push_args
len TEST_ARG_LEN !
end
\ Modified from `}T` to be more suitable for compiled code in reg-CC.
fun: testing_compiled_end { arg_len } [ true catches ]
TEST_STACK { stk }
0 { len0 } \ T{
arg_len { len1 } \ <T>
arg_len 2 * { len2 } \ }T
len0 len1 arg_len stk testing_stack_eq
if
testing_compiled_reset
ret
end
" [test] mismatch: " elog
len0 len1 len2 stk testing_stack_log
testing_compiled_reset
" test failure" throw
unreachable
end
fun_comp: }T
TEST_ARG_LEN @ { len }
TEST_ARG_LEN off!
" when calling `}T`" len comp_args_valid
len testing_compile_push_args
len comp_push
\ Briefly disabling auto-catching allows testing to work
\ seamlessly in tests annotated with `[ true catches ]`.
get_catches { ok }
ok if false catches end
compile' testing_compiled_end
ok if true catches end
end
\ ## Misc assertions
fun: T_err_contains { act exp }
act ifn
" missing error message; expected error to contain: %s"
exp errf throw
end
act exp strstr if ret end
" expected error message to contain: %s; actual error message: %s"
exp act errf throw
end