-
Notifications
You must be signed in to change notification settings - Fork 35
/
test.zsh
executable file
·267 lines (217 loc) · 5.77 KB
/
test.zsh
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
#!/usr/bin/env zsh
#
# zsh-async test runner.
# Checks for test files named *_test.zsh or *_test.sh and runs all functions
# named test_*.
#
emulate -R zsh
zmodload zsh/datetime
zmodload zsh/parameter
zmodload zsh/zutil
zmodload zsh/system
zmodload zsh/zselect
TEST_GLOB=.
TEST_RUN=
TEST_VERBOSE=0
TEST_TRACE=1
TEST_CODE_SKIP=100
TEST_CODE_ERROR=101
TEST_CODE_TIMEOUT=102
show_help() {
print "usage: ./test.zsh [-v] [-x] [-run pattern] [search pattern]"
}
parse_opts() {
local -a verbose debug trace help run
local out
zparseopts -E -D \
v=verbose verbose=verbose -verbose=verbose \
d=debug debug=debug -debug=debug \
x=trace trace=trace -trace=trace \
h=help -help=help \
\?=help \
run:=run -run:=run
(( $? )) || (( $+help[1] )) && show_help && exit 0
if (( $#@ > 1 )); then
print -- "unknown arguments: $@"
show_help
exit 1
fi
[[ -n $1 ]] && TEST_GLOB=$1
TEST_VERBOSE=$+verbose[1]
TEST_TRACE=$+trace[1]
ZTEST_DEBUG=$+debug[1]
(( $+run[2] )) && TEST_RUN=$run[2]
}
t_runner_init() {
emulate -L zsh
zmodload zsh/parameter
# _t_runner is the main loop that waits for tests,
# used to abort test execution by exec.
_t_runner() {
local -a _test_defer_funcs
integer _test_errors=0
while read -r; do
eval "$REPLY"
done
}
_t_log() {
local trace=$1; shift
local -a lines indent
lines=("${(@f)@}")
indent=($'\t\t'${^lines[2,$#lines]})
print -u7 -lr - $'\t'"$trace: $lines[1]" ${(F)indent}
}
# t_log is for printing log output, visible in verbose (-v) mode.
t_log() {
local line=$funcfiletrace[1]
[[ ${line%:[0-9]*} = "" ]] && line=ztest:$functrace[1] # Not from a file.
_t_log $line "$*"
}
# t_skip is for skipping a test.
t_skip() {
_t_log $funcfiletrace[1] "$*"
() { return 100 }
t_done
}
# t_error logs the error and fails the test without aborting.
t_error() {
(( _test_errors++ ))
_t_log $funcfiletrace[1] "$*"
}
# t_fatal fails the test and halts execution immediately.
t_fatal() {
_t_log $funcfiletrace[1] "$*"
() { return 101 }
t_done
}
# t_defer takes a function (and optionally, arguments)
# to be executed after the test has completed.
t_defer() {
_test_defer_funcs+=("$*")
}
# t_done completes the test execution, called automatically after a test.
# Can also be called manually when the test is done.
t_done() {
local ret=$? w=${1:-1}
(( _test_errors )) && ret=101
(( w )) && wait # Wait for test children to exit.
for d in $_test_defer_funcs; do
eval "$d"
done
print -n -u8 $ret # Send exit code to ztest.
exec _t_runner # Replace shell, wait for new test.
}
source $1 # Load the test module.
# Send available test functions to main process.
print -u7 ${(R)${(okM)functions:#test_*}:#test_main}
# Run test_main.
if [[ -n $functions[test_main] ]]; then
test_main
fi
exec _t_runner # Wait for commands.
}
# run_test_module runs all the tests from a test module (asynchronously).
run_test_module() {
local module=$1
local -a tests
float start module_time
# Create fd's for communication with test runner.
integer run_pid cmdoutfd cmdinfd outfd infd doneoutfd doneinfd
coproc cat; exec {cmdoutfd}>&p; exec {cmdinfd}<&p
coproc cat; exec {outfd}>&p; exec {infd}<&p
coproc cat; exec {doneoutfd}>&p; exec {doneinfd}<&p
# No need to keep coproc (&p) open since we
# have redirected the outputs and inputs.
coproc exit
# Launch a new interactive zsh test runner. We don't capture stdout
typeset -a run_args
(( TEST_TRACE )) && run_args+=('-x')
zsh -s $run_args <&$cmdinfd 7>&$outfd 8>&$doneoutfd &
run_pid=$!
# Initialize by sending function body from t_runner_init
# and immediately execute it as an anonymous function.
syswrite -o $cmdoutfd "() { ${functions[t_runner_init]} } $module"$'\n'
sysread -i $infd
tests=(${(@)=REPLY})
[[ -n $TEST_RUN ]] && tests=(${(M)tests:#*$TEST_RUN*})
integer mod_exit=0
float mod_start mod_time
mod_start=$EPOCHREALTIME # Store the module start time.
# Run all tests.
local test_out
float test_start test_time
integer text_exit
for test in $tests; do
(( TEST_VERBOSE )) && print "=== RUN $test"
test_start=$EPOCHREALTIME # Store the test start time.
# Start the test.
syswrite -o $cmdoutfd "$test; t_done"$'\n'
test_out=
test_exit=-1
while (( test_exit == -1 )); do
# Block until there is data to be read.
zselect -r $doneinfd -r $infd
if [[ $reply[2] = $doneinfd ]]; then
sysread -i $doneinfd
test_exit=$REPLY # Store reply from sysread
# Store the test execution time.
test_time=$(( EPOCHREALTIME - test_start ))
fi
# Read all output from the test output channel.
while sysread -i $infd -t 0; do
test_out+=$REPLY
unset REPLY
done
done
case $test_exit in
(0|1) state=PASS;;
(100) state=SKIP;;
(101|102) state=FAIL; mod_exit=1;;
*) state="????";;
esac
if [[ $state = FAIL ]] || (( TEST_VERBOSE )); then
printf -- "--- $state: $test (%.2fs)\n" $test_time
print -n $test_out
fi
done
# Store module execution time.
mod_time=$(( EPOCHREALTIME - mod_start ))
# Perform cleanup.
kill -HUP $run_pid
exec {outfd}>&-
exec {infd}<&-
exec {cmdinfd}>&-
exec {cmdoutfd}<&-
exec {doneinfd}<&-
exec {doneoutfd}>&-
if (( mod_exit )); then
print "FAIL"
(( TEST_VERBOSE )) && print "exit code $mod_exit"
printf "FAIL\t$module\t%.3fs\n" $mod_time
else
(( TEST_VERBOSE )) && print "PASS"
printf "ok\t$module\t%.3fs\n" $mod_time
fi
return $mod_exit
}
cleanup() {
trap '' HUP
kill -HUP -$$ 2>/dev/null
trap - HUP
kill -HUP $$ 2>/dev/null
}
trap cleanup EXIT INT HUP QUIT TERM USR1
# Parse command arguments.
parse_opts $@
(( ZTEST_DEBUG )) && setopt xtrace
# Execute tests modules.
failed=0
for tf in ${~TEST_GLOB}/*_test.(zsh|sh); do
run_test_module $tf &
wait $!
(( $? )) && failed=1
done
trap - EXIT
trap '' HUP
kill -HUP -$$ 2>/dev/null
exit $failed