-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathassert.sh
260 lines (220 loc) · 8.1 KB
/
assert.sh
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
#!/bin/sh
# This file contains the actual assertion functions.
# The exit status for the test script (and the subshell script)
# if an assertion fails or fail() or abort() are called.
EXIT_ASSERT=99
# The stdout output of the last assertCmd() command.
ASSERTCMDOUTPUT=
# The number of assertions that have been run.
ASSERTCNT=0
# If this contains a non-empty value, addAssertionCount() will have no effect.
SKIP_ASSERTCNT=
# ANSI color constants. Used by init.sh functions too.
color_info='[1;37m'
color_success='[1;32m'
color_skip='[1;33m'
color_error='[1;31m'
color_normal='[0m'
# abort()
# If this is called from a test script, we should simply call cleanup and exit.
# But if this is called from a subshell, we should signal the error condition to the test script running outside!
# This function should not be called manually -- use fail() instead.
abort () {
[ -n "$IN_SUBSHELL" ] && [ -n "$ERRCOND" ] && touch $ERRCOND # signal the error condition back to the test script
cleanup
exit $EXIT_ASSERT
}
# err errorMessage
# This function prints an error message on stderr, but does not abort the test script execution.
# Use it for multi-line error messages; otherwise, use fail(), as it also aborts the test script.
err () {
printf '%s\n' "${color_error}""$*""${color_normal}" >&2
}
# fail errorMessage
# This function prints an error message on stderr, then aborts the test script execution,
# also calling cleanup() in the process.
fail () {
err "$@"
abort
}
# assertCmd [-v] command [expectedReturnStatus=0]
# Tries to run a command and checks its return status.
# The command's stdout output will be saved in $ASSERTCMDOUTPUT,
# and won't be visible (unless the -v option is present).
# The command's stderr output will NOT be redirected.
# expectedReturnStatus can be any number (0..255),
# or the special value 'any', in which case
# all return status values will be accepted
# (except 126 and 127, those are still considered a failure,
# because they usually signify a shell command invocation error).
assertCmd () {
local verbose=
if [ "$1" = "-v" ]; then
verbose=yes
shift
fi
local cmd="$1"
local expectedReturnStatus="${2:-0}"
ASSERTCMDOUTPUT=
# Run the command, save the return status and output,
# and don't fail, no matter what the return status is!
if ASSERTCMDOUTPUT="$(printf '%s' "$cmd" | sh -s)"; then
local status="$?" # ok
else
local status="$?" # also ok
fi
[ -n "$verbose" ] && printf '%s\n' "$ASSERTCMDOUTPUT"
# The command might possibly have run a subshell (see prepare_subshell).
# In this case, it will have run its own assertions,
# so now we have to check if the error condition file has been created.
if [ -n "$ERRCOND" ] && [ -f "$ERRCOND" ]; then
# The error condition file exists!
# We'll assume that the assertion that caused this
# has already printed an error message, so we'll abort silently.
# No need to check the command status/output anymore.
abort
fi
local isStatusError=
if [ "$expectedReturnStatus" = "any" ]; then
# "any" means to accept all return status values -- except 126 and 127,
# those come from the shell, usually due to an invalid command.
if [ "$status" -eq 126 ] || [ "$status" -eq 127 ]; then
isStatusError=yes
fi
elif [ "$status" -ne "$expectedReturnStatus" ]; then
# status mismatch
isStatusError=yes
fi
addAssertionCount
if [ -n "$isStatusError" ]; then
err "Command '$cmd' was not executed successfully!"
err "(Expected return status: $expectedReturnStatus, Actual: $status)"
abort
fi
}
# assertEq valueActual valueExpected [errorMessage]
# This assertion compares two strings and tests them for equality.
assertEq () {
local valueActual="$1"
local valueExpected="$2"
local errorMessage="${3:-"Equality assertion failed!"}"
addAssertionCount
if [ "$valueActual" != "$valueExpected" ]; then
err "$errorMessage"
err "(Expected: '$valueExpected', Actual: '$valueActual')"
abort
fi
}
# assertContains valueActual valueExpectedPart [errorMessage]
# This assertion compares two strings,
# expecting the second to be contained somewhere in the first.
assertContains () {
local valueActual="$1"
local valueExpectedPart="$2"
local errorMessage="${3:-"Substring assertion failed!"}"
addAssertionCount
case "$valueActual" in
*"$valueExpectedPart"*) true ;; # ok
*)
err "$errorMessage"
err "(Expected '$valueExpectedPart' is not contained in '$valueActual')"
abort ;;
esac
}
# assertRegex valueActual regex [errorMessage]
# This assertion checks whether the regex (PCRE regular expression)
# matches the valueActual string.
# The regex argument must be enclosed by slashes,
# can start with a '!' to negate the matching sense,
# and can end with i/m/s modifier(s).
assertRegex () {
local valueActual="$1"
local valueRegex="$2"
local errorMessage="${3:-"Regex assertion failed!"}"
local inverse=
local modifiers=
local regex="${valueRegex#!}"
if [ "$regex" != "$valueRegex" ]; then
inverse="!"
fi
local modifiers="${regex##*/}"
case "$modifiers" in
*[!ism]*)
err "Illegal regex modifiers: /$modifiers"
abort
;;
esac
regex="${regex#/}"
regex="${regex%/*}"
addAssertionCount
if perl -e "(\$_, \$regex) = @ARGV; exit ! $inverse m/\$regex/$modifiers" "$valueActual" "$regex"; then
true # ok
else
err "$errorMessage"
err "(Regex $valueRegex does not match '$valueActual')"
abort
fi
}
# assertEmpty valueActual [errorMessage]
# This assertion tests a string, expecting it to be empty.
assertEmpty () {
local valueActual="$1"
local errorMessage="${2:-"Emptyness assertion failed!"}"
assertEq "$valueActual" "" "$errorMessage"
}
# assertCmdEq command expectedOutput [errorMessage]
# This assertion is a combination of assertCmd+assertEq.
# It executes a command, then compares its entire stdout output against expectedOutput.
# The command is expected to always have a return status of zero.
assertCmdEq () {
local cmd="$1"
local expectedOutput="$2"
local errorMessage="${3:-"Command output assertion failed!"}"
assertCmd "$cmd" 0 # run cmd, check success return status
assertEq "$ASSERTCMDOUTPUT" "$expectedOutput" "$errorMessage" # compare output
addAssertionCount -1
}
# assertCmdEmpty command [errorMessage]
# This assertion is a combination of assertCmd+assertEmpty.
# It executes a command, then compares its entire stdout output against the empty string.
# The command is expected to always have a return status of zero.
assertCmdEmpty () {
local cmd="$1"
local errorMessage="${2:-"Command output emptyness assertion failed!"}"
assertCmdEq "$cmd" "$expectedReturnStatus" "" "$errorMessage"
}
# assertFileSize fileName expectedSize [errorMessage]
# This assertion checks than a file exists and compares its total size (in bytes)
# against expectedSize.
assertFileSize () {
local fileName="$1"
local expectedSize="$2"
local errorMessage="${3:-"File '$fileName' has wrong size!"}"
assertCmdEq "stat --format='%s' '$fileName'" "$expectedSize" "$errorMessage"
}
# assertFileMode fileName expectedOctalMode [errorMessage]
# This assertion checks that a file exists and compares its octal access mode
# (as printed by 'stat -c %a', e.g. '755') against expectedOctalMode.
assertFileMode () {
local fileName="$1"
local expectedOctalMode="$2"
local errorMessage="${3:-"File '$fileName' has wrong access mode!"}"
assertCmdEq "stat --format='%a' '$fileName'" "$expectedOctalMode" "$errorMessage"
}
# assertSubshellWasExecuted [errorMessage]
# This assertion checks whether the last subshell script created via prepare_subshell() has been executed.
# It does so by checking the existence of the marker file which the subshell script should have created.
assertSubshellWasExecuted () {
local errorMessage="${1:-"Subshell script was not executed! (Marker file not found.)"}"
[ -n "$SUBSHELL_MARKER" ] || fail "Could not check subshell execution, prepare_subshell() was not used"
[ -e "$SUBSHELL_MARKER" ] || fail "$errorMessage"
addAssertionCount
:;
}
# addAssertionCount [+1]
# Modifies the $ASSERTCNT counter var by the argument.
# Most assertions use this internally to increase $ASSERTCNT by 1.
addAssertionCount () {
[ "$SKIP_ASSERTCNT" ] && return # nop
ASSERTCNT="$((ASSERTCNT + ${1:-1}))"
}