-
Notifications
You must be signed in to change notification settings - Fork 23
/
bach.sh
749 lines (676 loc) · 23 KB
/
bach.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
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
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
# -*- mode: sh -*-
# Bach Unit Testing Framework, https://bach.sh
# Copyright (C) 2019 Chai Feng <chaifeng@chaifeng.com>
#
# Bach Unit Testing Framework is dual licensed under:
# - GNU General Public License v3.0
# - Mozilla Public License 2.0
set -euo pipefail
IFS='.' read -r __major __minor _ <<< "${BASH_VERSION:-0.0.0}"
if [ "$__major" -lt 4 ] || { [ "$__major" -eq 4 ] && [ "$__minor" -lt 3 ]; }; then
echo "Error: Bach Unit Testing Framework requires Bash version 4.3 or higher. Your current version is ${BASH_VERSION:-unknown}." >&2
exit 1
fi
if [ -z "${BASH_VERSION:-}" ] || [ -z "${BASH_SOURCE:-}" ]; then
echo "Error: Bach Unit Testing Framework must be run in Bash. Please switch to Bash and try again."
exit 1
fi
shopt -s expand_aliases
builtin export BACH_COLOR="${BACH_COLOR:-auto}"
builtin export PS4='+(${BASH_SOURCE##*/}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
BACH_OS_NAME="$(uname)"
declare -gxr BACH_OS_NAME
declare -gxa bach_origin_paths=()
while builtin read -r -d: folder; do
bach_origin_paths+=("$folder")
done <<< "${PATH}:"
function @out() {
if [[ "${1:-}" == "-" || ! -t 0 ]]; then
[[ "${1:-}" == "-" ]] && shift
while IFS=$'\n' read -r line; do
builtin printf "%s\n" "${*}$line"
done
elif [[ "$#" -gt 0 ]]; then
builtin printf "%s\n" "$*"
else
builtin printf "\n"
fi
} 8>/dev/null
builtin export -f @out
function @err() {
@out "$@"
} >&2
builtin export -f @err
function @die() {
@out "$@"
exit 1
} >&2
builtin export -f @die
if [[ -z "${BASH_VERSION:-}" ]] || [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
@die "Bach Testing Framework only support Bash v4+!"
fi
if [[ "${BACH_DEBUG:-}" != true ]]; then
function @debug() {
:
}
else
exec 8>&2
function @debug() {
builtin printf '[DEBUG] %s\n' "$*"
} >&8
fi
builtin export -f @debug
function .bach.real-path() {
declare folder name="$1"
declare altname="${name#*|}"
name="${name%|*}"
for folder in "${bach_origin_paths[@]}"; do
if [[ -x "$folder/$name" ]]; then
builtin echo "$folder/$name"
return 0
elif [[ "$name" != "$altname" && -x "$folder/$altname" ]]; then
builtin echo "$folder/$altname"
return 0
fi
done
return 1
}
builtin export -f .bach.real-path
builtin export BACH_DEV_STDIN=""
function .bach.restore-stdin() {
if [[ ! -t 0 ]]; then
declare name
[[ -n "$BACH_DEV_STDIN" ]] || for name in /dev/ptmx /dev/pts/ptmx /dev/ttyv[0-9a-f]; do
if [[ -r "$name" && -c "$name" ]]; then
ls -l "$name" >&2
BACH_DEV_STDIN="$name"
break
fi
done
exec 0<&-
exec 0<"$BACH_DEV_STDIN"
fi
}
function .bach.initialize(){
enable -n alias bg bind dirs disown fc fg hash help history jobs kill suspend times ulimit umask unalias wait
declare util name util_path
declare -a bash_builtin_cmds=(cd enable popd pushd pwd shopt test trap type)
for name in . command echo exec export false set true unset "${bash_builtin_cmds[@]}"; do
eval "function @${name}() { builtin $name \"\$@\"; } 8>/dev/null; builtin export -f @${name}"
done
for name in eval; do
eval "function @${name}() { builtin $name \"\$@\"; }; builtin export -f @${name}"
done
function @source() {
declare script="$1"
shift
builtin source "$script" "$@"
}
declare -a bach_core_utils=(cat chmod cut diff find env grep ls "shasum|sha1sum" mkdir mktemp rm rmdir sed sort tee touch which xargs)
for util in "${bach_core_utils[@]}"; do
if [[ "$util" == "shasum|"* && "$BACH_OS_NAME" == FreeBSD ]]; then
util="shasum|sha1"
fi
name="${util%|*}"
util_path="$(.bach.real-path "$util")"
eval "[[ -n \"${util_path}\" ]] || @die \"Fatal, CAN NOT find '$name' in \\\$PATH\"; function @${name}() { \"${util_path}\" \"\$@\"; } 8>/dev/null; builtin export -f @${name}"
done
.bach.restore-stdin
@mockall "${bash_builtin_cmds[@]}" source .
function export() { builtin export "$@"; @dryrun export "$@"; }
eval "$(builtin declare -x | @real cut -d= -f1 | while read -rs name; do
[[ "$name" = "declare -"* ]] || continue
name="${name%%=*}"
name="${name##* }"
[[ "${name^^}" != BACH_* ]] || continue
builtin echo "unset '$name' || builtin true"
done)"
builtin export LANG=C TERM=vt100
unset name util_path
}
function @real() {
declare name="$1" real_cmd
if [[ "$name" == */* ||"$name" == @(bg|fc|fg|jobs|ulimit|umask|wait) ]]; then
@echo "$@"
return
fi
declare -a cmd
if [[ "$name" == @(alias|builtin|cd|command|declare|eval|false|getopts|hash|printf|read|set|type|unalias|unset) ]]; then
cmd=(builtin "$name")
[[ "$name" != declare ]] || cmd+=(-g)
else
real_cmd="$(.bach.real-path "$1" 7>&1 || true)"
if [[ -z "${real_cmd}" ]]; then
real_cmd="${name}_not_found"
fi
cmd=("${real_cmd}")
fi
cmd+=("${@:2}")
@debug "[REAL-CMD]" "${cmd[@]}"
"${cmd[@]}"
}
builtin export -f @real
function .bach.get-all-functions() {
declare -F
}
builtin export -f .bach.get-all-functions
function .bach.skip-the-test() {
declare test="$1" test_filter
while read -d, test_filter; do
[[ -n "$test_filter" ]] || continue
[[ "$test" == $test_filter ]] && return 0
[[ "$test" == test-$test_filter ]] && return 0
done <<< "${BACH_TESTS:-},"
}
builtin export -f .bach.skip-the-test
function .bach.run-tests--get-all-tests() {
.bach.get-all-functions | @sort -R | while read -r _ _ name; do
[[ "$name" == test?* ]] || continue
[[ "$name" == *-assert ]] && continue
.bach.skip-the-test "$name" || continue
builtin printf "%s\n" "$name"
done
}
for donotpanic in donotpanic dontpanic do-not-panic dont-panic do_not_panic dont_panic; do
eval "function @${donotpanic}() { builtin printf '\n%s\n line number: %s\n script stack: %s\n\n' 'DO NOT PANIC!' \"\${BASH_LINENO}\" \"\${BASH_SOURCE[*]}\"; builtin exit 1; } >&2; builtin export -f @${donotpanic};"
done
function .bach.is-function() {
[[ "$(@type -t "$1")" == function ]]
}
builtin export -f .bach.is-function
declare -gr __bach_run_test__ignore_prefix="## BACH:"
function @comment() {
@out "${__bach_run_test__ignore_prefix}" "$@"
}
builtin export -f @comment
function .bach.run-tests() {
set -euo pipefail
.bach.initialize
for donotpanic in donotpanic dontpanic do-not-panic dont-panic do_not_panic dont_panic; do
eval "function @${donotpanic}() { builtin true; }; builtin export -f @${donotpanic}"
done
function command() {
if [[ "$1" != -* ]] && .bach.is-function "$1"; then
"$@"
else
declare mockfunc="$(.bach.gen_function_name command "$@")"
if .bach.is-function "${mockfunc}"; then
@debug "[BC-func]" "${mockfunc}" "$@"
"${mockfunc}" "$@"
else
command_not_found_handle command "$@"
fi
fi
}
builtin export -f command
function xargs() {
declare param
declare -a xargs_opts
while param="${1:-}"; [[ -n "${param:-}" ]]; do
shift || true
if [[ "$param" == "--" ]]; then
xargs_opts+=("${BASH:-bash}" "-c" "$(builtin printf "'%s' " "$@") \$@" "-s")
break
else
xargs_opts+=("$param")
fi
done
@debug "@mock-xargs" "${xargs_opts[@]}"
if [[ "$#" -gt 0 ]]; then
@xargs "${xargs_opts[@]}"
else
[[ -t 0 ]] || @cat &>/dev/null
@dryrun xargs "${xargs_opts[@]}"
fi
}
builtin export -f xargs
function [() {
declare mockfunc;
local _mock_regex
[[ -z "${_bach_mock_regex_test-}" ]] || for _mock_regex in "${_bach_mock_regex_test[@]}"; do
if [[ "$*" =~ $_mock_regex ]]; then
mockfunc="$(.bach.gen_function_name "[" "$_mock_regex")";
break;
fi
done
[[ -n "${mockfunc-}" ]] || mockfunc="$(.bach.gen_function_name "[" "$@")";
if .bach.is-function "${mockfunc}"; then
@debug "[LSB-func]" "${mockfunc}" "$@"
"${mockfunc}" "$@"
else
builtin '[' "$@"
fi
}
builtin export -f '['
function echo() {
declare mockfunc;
local _mock_regex
[[ -z "${_bach_mock_regex_echo-}" ]] || for _mock_regex in "${_bach_mock_regex_echo[@]}"; do
if [[ "$*" =~ $_mock_regex ]]; then
mockfunc="$(.bach.gen_function_name echo "$_mock_regex")";
break;
fi
done
[[ -n "${mockfunc-}" ]] || mockfunc="$(.bach.gen_function_name echo "$@")";
if .bach.is-function "${mockfunc}"; then
@debug "[LSB-func]" "${mockfunc}" "$@"
"${mockfunc}" "$@"
else
builtin echo "$@"
fi
}
builtin export -f echo
if [[ "${BACH_ASSERT_IGNORE_COMMENT}" == true ]]; then
BACH_ASSERT_DIFF_OPTS+=(-I "^${__bach_run_test__ignore_prefix}")
fi
declare color_ok color_err color_end
if [[ "$BACH_COLOR" == "always" ]] || [[ "$BACH_COLOR" != "no" && -t 1 && -t 2 ]]; then
color_ok="\e[1;32m"
color_err="\e[1;31m"
color_end="\e[0;m"
else
color_ok=""
color_err=""
color_end=""
fi
declare name friendly_name testresult test_name_assert_fail
declare -i total=0 error=0
declare -a all_tests
mapfile -t all_tests < <(.bach.run-tests--get-all-tests)
@echo "1..${#all_tests[@]}"
for name in "${all_tests[@]}"; do
# @debug "Running test: $name"
friendly_name="${name/#test-/}"
friendly_name="${friendly_name//-/ }"
friendly_name="${friendly_name// / -}"
: $(( ++total ))
testresult="$(@mktemp)"
@set +e
.bach.assert-execution "$name" &>"$testresult"; test_retval="$?"
@set -e
if [[ "$name" == test-ASSERT-FAIL-* ]]; then
test_retval="$(( test_retval == 0?1:0 ))"
test_name_assert_fail="${color_err}ASSERT FAIL${color_end}"
friendly_name="${friendly_name/#ASSERT FAIL/}"
else
test_name_assert_fail=""
fi
if [[ "$test_retval" -eq 0 ]]; then
builtin printf "${color_ok}ok %d - ${test_name_assert_fail}${color_ok}%s${color_end}\n" "$total" "$friendly_name"
else
: $(( ++error ))
builtin printf "${color_err}not ok %d - ${test_name_assert_fail}${color_err}%s${color_end}\n" "$total" "$friendly_name"
{
builtin printf "\n"
@cat "$testresult" >&2
builtin printf "\n"
} >&2
fi
@rm "$testresult" &>/dev/null
done
declare color_result="$color_ok"
if (( error > 0 )); then
color_result="$color_err"
fi
builtin printf -- "# -----\n#${color_result} All tests: %s, failed: %d, skipped: %d${color_end}\n" \
"${#all_tests[@]}" "$error" "$(( ${#all_tests[@]} - total ))">&2
[[ "$error" == 0 ]] && [[ "${#all_tests[@]}" -eq "$total" ]]
}
function .bach.on-exit() {
if [[ -o xtrace ]]; then
exec 8>&2
BASH_XTRACEFD=8
fi
if [[ "$?" -eq 0 ]]; then
[[ "${BACH_DISABLED:-false}" == true ]] || .bach.run-tests
else
builtin printf "Bail out! %s\n" "Couldn't initlize tests."
fi
}
trap .bach.on-exit EXIT
function .bach.gen_function_name() {
declare name="$1"
@echo "mock_exec_${name}_$(@dryrun "${@}" | @shasum | @cut -b1-7)"
}
builtin export -f .bach.gen_function_name
function @mock() {
declare -a param name cmd func body desttype
name="$1"
if [[ "$name" == @(builtin|declare|export|eval|set|unset|true|false|read) ]]; then
@die "Cannot mock the builtin command: $name"
fi
if [[ command == "$name" && "$2" != -* ]]; then
shift
name="$1"
fi
desttype="$(@type -t "$name" || true)"
for param; do
if [[ "$param" == '===' ]]; then
shift
break
fi
cmd+=("$param")
done
shift "${#cmd[@]}"
if [[ "$name" == /* ]]; then
@die "Cannot mock an absolute path: $name"
elif [[ "$name" == */* ]] && [[ -e "$name" ]]; then
@die "Cannot mock an existed path: $name"
fi
@debug "@mock $name"
if [[ "$#" -gt 0 ]]; then
@debug "@mock $name $*"
declare -a params=("$@")
func="$(declare -p params); \"\${params[@]}\""
#func="$*"
elif [[ ! -t 0 ]]; then
@debug "@mock $name @cat"
func="$(@cat)"
fi
if [[ -z "${func:-}" ]]; then
@debug "@mock default $name"
if [[ "$name" == echo ]]; then
func="@dryrun ${name} \"\$@\" >&7"
else
func="if [[ -t 0 ]]; then @dryrun \"${name}\" \"\$@\" >&7; fi"
fi
elif [[ "$name" == echo ]]; then
@die "Cannot mock the builtin command: $name"
fi
if [[ "$name" == */* ]]; then
[[ -d "${name%/*}" ]] || @mkdir -p "${name%/*}"
@cat > "$name" <<SCRIPT
#!${BASH:-/bin/bash}
${func}
SCRIPT
@chmod +x "$name" >&2
else
if [[ -z "$desttype" || "$desttype" == builtin ]]; then
eval "function ${name}() {
declare mockfunc;
local _mock_regex
[[ -z \"\${_bach_mock_regex_${name//[^a-zA-Z_]/__}-}\" ]] || for _mock_regex in \"\${_bach_mock_regex_${name//[^a-zA-Z_]/__}[@]}\"; do
if [[ \"\$*\" =~ \$_mock_regex ]]; then
mockfunc=\"\$(.bach.gen_function_name ${name} \"\$_mock_regex\")\";
break;
fi
done
[[ -n \"\${mockfunc-}\" ]] || mockfunc=\"\$(.bach.gen_function_name ${name} \"\$@\")\";
if .bach.is-function \"\$mockfunc\"; then
\"\${mockfunc}\" \"\$@\"
else
@dryrun ${name} \"\$@\" >&7
fi
}; builtin export -f ${name}"
fi
declare mockfunc
mockfunc="$(.bach.gen_function_name "${cmd[@]}")"
#stderr name="$name"
#body="function ${mockfunc}() { @debug Running mock : '${cmd[*]}' :; $func; }"
declare mockfunc_seq="${mockfunc//_/__}_SEQ"
mockfunc_seq="${mockfunc_seq//[-\!+.@\[\]\{\}~]/_}"
body="function ${mockfunc}() {
declare -gxi ${mockfunc_seq}=\"\${${mockfunc_seq}:-0}\";
if .bach.is-function \"${mockfunc}_\$(( ${mockfunc_seq} + 1))\"; then
let ++${mockfunc_seq};
fi;
\"${mockfunc}_\${${mockfunc_seq}}\" \"\$@\";
}; builtin export -f ${mockfunc}"
@debug "$body"
eval "$body"
for (( mockfunc__SEQ=1; mockfunc__SEQ <= ${BACH_MOCK_FUNCTION_MAX_COUNT:-0}; ++mockfunc__SEQ )); do
.bach.is-function "${mockfunc}_${mockfunc__SEQ}" || break
done
body="function ${mockfunc}_${mockfunc__SEQ}() {
# @mock ${name} ${cmd[@]} ===
$func
}; builtin export -f ${mockfunc}_${mockfunc__SEQ}"
@debug "$body"
eval "$body"
fi
}
builtin export -f @mock
function @@mock() {
BACH_MOCK_FUNCTION_MAX_COUNT=15 @mock "$@"
}
builtin export -f @@mock
function @mockpipe() {
@mock "$@" === @cat
}
builtin export -f @mockpipe
function @mocktrue() {
@mock "$@" === @true
}
builtin export -f @mocktrue
function @mockfalse() {
@mock "$@" === @false
}
builtin export -f @mockfalse
function @mock-regex() {
declare _param _name="${1:?missing command name}"
if [[ "$_name" = "[" ]]; then _name=test; fi
_name="${_name//[^a-zA-Z_]/__}"
declare -a _mock_param=()
for _param; do
[[ "$_param" = "===" ]] && break
_mock_param+=("$_param")
done
shift "${#_mock_param[@]}"
eval "[[ -n \"\${_bach_mock_regex_${_name}-}\" ]] || declare -gax _bach_mock_regex_${_name}; _bach_mock_regex_${_name}+=(\"${_mock_param[*]:1}\")"
@debug mock-regex: @mock "${_mock_param[0]}" "${_mock_param[*]:1}" "$@"
@mock "${_mock_param[0]}" "${_mock_param[*]:1}" "$@"
}
builtin export -f @mock-regex
function @mockall() {
declare name
for name; do
@mock "$name"
done
}
builtin export -f @mockall
function @capture() {
@mock "$@" <<_EOS_207_
@assert-capture "$@"
_EOS_207_
}
function @assert-capture() {
@echo "## Capture input: " "$@"
@echo "$@" "<<_BACH_ASSERT_CAPTURE_"
@cat
@echo _BACH_ASSERT_CAPTURE_
}
BACH_FRAMEWORK__SETUP_FUNCNAME="_bach_framework_setup_"
alias @setup="function $BACH_FRAMEWORK__SETUP_FUNCNAME"
BACH_FRAMEWORK__PRE_TEST_FUNCNAME='_bach_framework_pre_test_'
alias @setup-test="function $BACH_FRAMEWORK__PRE_TEST_FUNCNAME"
BACH_FRAMEWORK__PRE_ASSERT_FUNCNAME='_bach_framework_pre_assert_'
alias @setup-assert="function $BACH_FRAMEWORK__PRE_ASSERT_FUNCNAME"
function .bach.run_function() {
declare name="$1"
if .bach.is-function "$name"; then
"$name"
fi
}
builtin export -f .bach.run_function
function @dryrun() {
builtin declare param
builtin declare -a input
[[ "$#" -le 1 ]] || {
for param in "${@:2}"; do
if [[ -z "$param" ]]; then input+=($'\x1b[31m\u2205\x1b[0m'); else input+=("$param"); fi
done
builtin printf -v param ' %s' "${input[@]}"
}
builtin echo "${1}${param:-}"
}
builtin export -f @dryrun
declare -gxa BACH_ASSERT_DIFF_OPTS=(-u)
declare -gx BACH_ASSERT_IGNORE_COMMENT="${BACH_ASSERT_IGNORE_COMMENT:-true}"
declare -gx BACH_ASSERT_DIFF="${BACH_ASSERT_DIFF:-diff}"
function .bach.assert-execution() (
@unset BACH_TESTS
declare bach_test_name="$1" bach_tmpdir bach_actual_output bach_expected_output
bach_tmpdir="$(@mktemp -d)"
#trap '/bin/rm -vrf "$bach_tmpdir"' RETURN
@mkdir "${bach_tmpdir}/test_root"
@pushd "${bach_tmpdir}/test_root" &>/dev/null
declare retval=1
exec 7>&2
function command_not_found_handle() {
declare mockfunc bach_cmd_name="$1"
[[ -n "$bach_cmd_name" ]] || @out "Error: Bach found an empty command at line ${BASH_LINENO}." >&7
mockfunc="$(.bach.gen_function_name "$@")"
# @debug "mockid=$mockid" >&2
if .bach.is-function "${mockfunc}"; then
@debug "[CNFH-func]" "${mockfunc}" "$@"
"${mockfunc}" "$@"
elif [[ "${bach_cmd_name}" == @(cd|command|echo|eval|exec|false|popd|pushd|pwd|source|true|type) ]]; then
@debug "[CNFH-builtin]" "$@"
builtin "$@"
else
@debug "[CNFH-default]" "$@"
@dryrun "$@"
fi
} >&7 #8>/dev/null
builtin export -f command_not_found_handle
function .bach.pre_run_test_and_assert() {
@trap - EXIT RETURN
@set +euo pipefail
declare -gxr PATH=bach-fake-path
.bach.restore-stdin
.bach.run_function "$BACH_FRAMEWORK__SETUP_FUNCNAME"
}
function .bach.run_test() (
.bach.pre_run_test_and_assert
.bach.run_function "${BACH_FRAMEWORK__PRE_TEST_FUNCNAME}"
"${1}"
) 7>&1
function .bach.run_assert() (
@unset -f @mock @mockall @ignore @setup-test
.bach.pre_run_test_and_assert
.bach.run_function "${BACH_FRAMEWORK__PRE_ASSERT_FUNCNAME}"
"${1}-assert"
) 7>&1
bach_actual_stdout="${bach_tmpdir}/actual-stdout.txt"
bach_expected_stdout="${bach_tmpdir}/expected-stdout.txt"
if .bach.is-function "${bach_test_name}-assert"; then
@cat <(
.bach.run_test "$bach_test_name"
@echo "# Exit code: $?"
) > "${bach_actual_stdout}"
@cat <(
.bach.run_assert "$bach_test_name"
@echo "# Exit code: $?"
) > "${bach_expected_stdout}"
@cd ..
if @real "${BACH_ASSERT_DIFF}" "${BACH_ASSERT_DIFF_OPTS[@]}" -- \
"${bach_actual_stdout##*/}" "${bach_expected_stdout##*/}"
then
retval=0
fi
else
.bach.run_test "$bach_test_name" |
@tee /dev/stderr | if [[ "$bach_test_name" = test-ASSERT-FAIL-* ]]; then
@cat
@echo "${__bach_run_test__ignore_prefix} Should fail"
else
@grep "^${__bach_run_test__ignore_prefix} \\[assert-" >/dev/null
fi
retval="$?"
fi
@popd &>/dev/null
@rm -rf "$bach_tmpdir"
return "$retval"
)
function @ignore() {
declare name
for name; do
if [[ "$name" == @(builtin|declare|eval|set|unset|true|false|read) ]]; then
@die "Cannot ignore the builtin command: $name"
elif [[ "$name" == export ]]; then
function export() { builtin export "$@"; }
else
eval "function ${name}() {
declare mockfunc=\"\$(.bach.gen_function_name ${name} \"\${@}\")\";
if .bach.is-function \"\$mockfunc\"; then
\"\${mockfunc}\" \"\$@\";
fi
}; builtin export -f ${name}"
fi
done
}
builtin export -f @ignore
function @stderr() {
builtin printf "%s\n" "$@" >&2
}
builtin export -f @stderr
function @stdout() {
builtin printf "%s\n" "$@"
}
builtin export -f @stdout
function @load_function() {
local file="${1:?script filename}"
local func="${2:?function name}"
@source <(@sed -Ene "/^function[[:space:]]+${func}([\(\{\[[:space:]]|[[:space:]]*\$)/,/^}\$/p" "$file")
} 8>/dev/null
builtin export -f @load_function
builtin export BACH_STARTUP_PWD="${PWD:-$(pwd)}"
function @run() {
declare script="${1:?missing script name}"
shift
builtin pushd "${BACH_STARTUP_PWD}" &>/dev/null
@source "$script" "$@"
builtin popd &>/dev/null
}
builtin export -f @run
function @fail() {
declare retval=1
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
retval="$1"
shift
fi
if [[ "$#" -gt 0 ]]; then
@out "${@}"
fi
builtin exit "${retval}"
}
builtin export -f @fail
function @assert-equals() {
declare expected="${1:?missing the expected result}" actual="${2:?missing the actual result}"
if [[ "${expected}" == "${actual}" ]]; then
@out <<EOF
${__bach_run_test__ignore_prefix} [assert-equals] expected: ${expected}
## actual: ${actual}
EOF
else
@die - 2>&7 <<EOF
Assert Failed:
Expected: $expected
But got: $actual
EOF
fi
} >&7
builtin export -f @assert-equals
function @assert-fail() {
declare expected="<non-zero>" actual="$?"
[[ "$actual" -eq 0 ]] || expected="$actual"
@assert-equals "$expected" "$actual"
}
builtin export -f @assert-fail
function @assert-success() {
declare expected=0 actual="$?"
@assert-equals "$expected" "$actual"
}
builtin export -f @assert-success
function @do-nothing() {
:
}
builtin export -f @do-nothing
function @unmock() {
declare name="${1:?missing command name}"
declare mockfunc="$(.bach.gen_function_name "${@}")";
if .bach.is-function "$mockfunc"; then
unset -f "$mockfunc"
fi
}
builtin export -f @unmock