-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathassert.base.bash
More file actions
372 lines (317 loc) · 8.95 KB
/
Copy pathassert.base.bash
File metadata and controls
372 lines (317 loc) · 8.95 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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
#!/usr/bin/env bash
##
# @file
# Failure reporting and error message formatting.
#
##
# Fails the test with a message.
#
# Arguments:
# 1. message: Message to print. Optional, read from STDIN when omitted.
#
# Globals:
# BATS_TEST_SOURCE, BATS_TEST_FILENAME, BATS_ROOT: Read to resolve the frames
# of the stack trace back to the files the consumer wrote.
#
# Outputs:
# STDERR: The message and the stack trace of the caller, between the banners
# marking where the failure starts and ends, with the volatile paths
# rewritten to the names of the variables holding them.
#
# Returns:
# 1 always.
##
flunk() {
local message
if [ "$#" -eq 0 ]; then
message="$(cat -)"
else
message="$*"
fi
local trace
trace="$(_report_stack_trace)"
if [ -n "${trace}" ]; then
message="${message}"$'\n\n'"$(_report_decorate "stack trace" "${trace}")"
fi
_report_normalize_paths "$(_report_banner "${message}")" >&2
return 1
}
##
# Formats a failure report as a decorated block of aligned rows.
#
# A row whose value spans lines switches every row to a labelled form carrying
# its line count, so that two values stay comparable rather than one of them
# collapsing onto a single line and the other not. Rows keyed 'expected' and
# 'actual' are replaced by a unified diff of the two once either of them spans
# lines.
#
# Arguments:
# 1. title: Short summary naming what went wrong.
# 2+. key value: Rows, given as a key and a value each. Optional.
#
# Outputs:
# STDOUT: The formatted report.
##
format_error() {
if [ "$#" -eq 0 ]; then
flunk "A failure report requires a title."
return 1
fi
local title="${1}"
shift
if [ $(($# % 2)) -ne 0 ]; then
flunk "Failure report rows require a key and a value each."
return 1
fi
##
## Row collection.
##
local -a keys=()
local -a values=()
local width=0
local multiline=0
local expected_index=-1
local actual_index=-1
while [ "$#" -gt 0 ]; do
case "${1}" in
expected) expected_index="${#keys[@]}" ;;
actual) actual_index="${#keys[@]}" ;;
esac
[ "${#1}" -gt "${width}" ] && width="${#1}"
[[ ${2} == *$'\n'* ]] && multiline=1
keys+=("${1}")
values+=("${2}")
shift 2
done
# Two values that each fit on a line are already comparable as rows, so a diff
# is what makes the mismatch readable only once either of them spans lines.
local diffable=0
if [ "${expected_index}" -ge 0 ] && [ "${actual_index}" -ge 0 ]; then
if [[ ${values[expected_index]} == *$'\n'* ]] || [[ ${values[actual_index]} == *$'\n'* ]]; then
diffable=1
fi
fi
##
## Rendering.
##
local body=""
local count
local i
for ((i = 0; i < ${#keys[@]}; i++)); do
if [ "${diffable}" = "1" ] && [ "${i}" -eq "${actual_index}" ]; then
continue
fi
if [ "${diffable}" = "1" ] && [ "${i}" -eq "${expected_index}" ]; then
body="${body}$(_report_diff "${values[expected_index]}" "${values[actual_index]}")"$'\n'
continue
fi
if [ "${multiline}" = "1" ]; then
count="$(_report_count_lines "${values[i]}")"
body="${body}${keys[i]} ($(_report_plural_lines "${count}")):"$'\n'"${values[i]}"$'\n'
continue
fi
body="${body}$(printf '%-*s : %s' "${width}" "${keys[i]}" "${values[i]}")"$'\n'
done
_report_normalize_paths "$(_report_decorate "${title}" "${body%$'\n'}")"
}
##
## Rendering.
##
##
# Marks where a failure starts and ends.
#
# A run prints the output of the code under test as well as its own, so a
# failure has to be findable in a wall of text that is mostly not about it. The
# banner is deliberately louder than anything either side of it, and it wraps
# the stack trace as well as the report so that nothing of the failure falls
# outside the two markers.
#
# Arguments:
# 1. body: Failure to wrap.
#
# Outputs:
# STDOUT: The body, between the opening and the closing banner.
##
_report_banner() {
echo "##################################################"
echo "# BEGIN ERROR MESSAGE #"
echo "##################################################"
printf '%s\n' "${1}"
echo "##################################################"
echo "# END ERROR MESSAGE #"
echo "##################################################"
}
##
# Wraps a block of text in a titled border.
#
# Arguments:
# 1. title: Title to name the block with.
# 2. body: Text to wrap. Optional, an empty body prints no line of its own.
#
# Outputs:
# STDOUT: The decorated block.
##
_report_decorate() {
printf -- '-- %s --\n' "${1}"
if [ -n "${2-}" ]; then
printf '%s\n' "${2}"
fi
printf -- '--\n'
}
##
# Renders a unified diff of two values.
#
# Arguments:
# 1. expected: Expected value.
# 2. actual: Actual value.
#
# Outputs:
# STDOUT: The diff, labelled with the name of each side.
##
_report_diff() {
local -a options=(-u -L expected -L actual)
if _report_color_enabled; then
options+=(--color=always)
fi
# A difference is what this function is called to render, and 'diff' reports
# one with a non-zero status.
diff "${options[@]}" <(printf '%s\n' "${1}") <(printf '%s\n' "${2}") || true
}
##
# Counts the lines a value spans.
#
# Arguments:
# 1. value: Value to measure.
#
# Outputs:
# STDOUT: The number of lines, at least one.
##
_report_count_lines() {
local stripped="${1//$'\n'/}"
printf '%s\n' "$((${#1} - ${#stripped} + 1))"
}
##
# Names a number of lines.
#
# Arguments:
# 1. count: Number of lines.
#
# Outputs:
# STDOUT: The count and the noun agreeing with it.
##
_report_plural_lines() {
if [ "${1}" -eq 1 ]; then
printf '1 line\n'
return 0
fi
printf '%s lines\n' "${1}"
}
##
## Colour.
##
##
# Reports whether a diff should be coloured.
#
# Globals:
# BATS_HELPERS_REPORT_COLOR: '0' to never colour, '1' to colour whenever the
# platform supports it. Unset or empty defers to 'NO_COLOR'.
# NO_COLOR: Any non-empty value suppresses colour.
#
# Returns:
# 0 when the diff should be coloured, 1 when it should not.
##
_report_color_enabled() {
local override="${BATS_HELPERS_REPORT_COLOR-}"
[ "${override}" = "0" ] && return 1
if [ "${override}" != "1" ] && [ -n "${NO_COLOR-}" ]; then
return 1
fi
_report_diff_color_supported
}
##
# Reports whether the platform's 'diff' understands the colour flag.
#
# The flag is absent from busybox diff and from GNU diffutils before 3.4, where
# passing it unconditionally would replace the report with a usage error.
#
# Returns:
# 0 when the flag is understood, 1 when it is not.
##
_report_diff_color_supported() {
diff --color=always /dev/null /dev/null >/dev/null 2>&1
}
##
## Context.
##
##
# Prints where the failing assertion was called from.
#
# The frames of the library itself and of bats-core are left out, so that a
# failure raised several calls deep inside a helper still names the line of the
# consumer's own code that reached it.
#
# Globals:
# BATS_ROOT: Installation directory of bats-core, whose frames are skipped.
#
# Outputs:
# STDOUT: One 'file:line: function' entry per frame, outermost last. Nothing
# when every frame belongs to the library or to bats-core.
##
_report_stack_trace() {
local root="${BASH_SOURCE[0]%/*}"
local bats_root="${BATS_ROOT-}"
local source
local i
for ((i = 1; i < ${#FUNCNAME[@]}; i++)); do
source="${BASH_SOURCE[i + 1]-}"
# The outermost frame is entered from the command line rather than from a
# file, and there is nothing above it to walk to.
[ -z "${source}" ] && break
if [[ ${source} == "${root}"/* ]]; then
continue
fi
if [ -n "${bats_root}" ] && [[ ${source} == "${bats_root}"/* ]]; then
continue
fi
printf '%s:%s: %s\n' "${source}" "${BASH_LINENO[i]}" "${FUNCNAME[i]}"
done
}
##
# Rewrites the paths that change between runs to the names holding them.
#
# The bats-core temporary directories nest, so they are rewritten from the most
# specific to the least, and the working directory and the home directory are
# rewritten last for the same reason.
#
# Arguments:
# 1. text: Text to rewrite.
#
# Globals:
# BATS_TEST_SOURCE, BATS_TEST_FILENAME: Resolve the preprocessed copy of the
# test file back to the file the consumer wrote.
# BATS_TEST_TMPDIR, BATS_FILE_TMPDIR, BATS_SUITE_TMPDIR, BATS_RUN_TMPDIR, PWD,
# HOME: Rewritten to their own names.
#
# Outputs:
# STDOUT: The rewritten text.
##
_report_normalize_paths() {
local text="${1}"
if [ -n "${BATS_TEST_SOURCE-}" ] && [ -n "${BATS_TEST_FILENAME-}" ]; then
text="${text//"${BATS_TEST_SOURCE}"/${BATS_TEST_FILENAME}}"
fi
local name
local value
local token
for name in BATS_TEST_TMPDIR BATS_FILE_TMPDIR BATS_SUITE_TMPDIR BATS_RUN_TMPDIR PWD HOME; do
value="${!name-}"
# The root directory is a prefix of every path, so rewriting it would leave
# nothing readable behind.
if [ -z "${value}" ] || [ "${value}" = "/" ]; then
continue
fi
token="\${${name}}"
text="${text//"${value}"/${token}}"
done
printf '%s\n' "${text}"
}