summaryrefslogtreecommitdiffstats
path: root/tests/run.sh
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2023-10-19 16:37:47 -0400
committerTavian Barnes <tavianator@tavianator.com>2023-10-19 16:37:47 -0400
commit785a3f2d777627f39bed44f4ae7a0180d5184109 (patch)
tree2bee8cb669ab91982a7d6d1e8ada9958a2369ffd /tests/run.sh
parentd484cba3424dcfca4851ba867d8877e3a9381a0e (diff)
downloadbfs-785a3f2d777627f39bed44f4ae7a0180d5184109.tar.xz
tests: Refactor implementation into separate files
Diffstat (limited to 'tests/run.sh')
-rw-r--r--tests/run.sh316
1 files changed, 316 insertions, 0 deletions
diff --git a/tests/run.sh b/tests/run.sh
new file mode 100644
index 0000000..70c9cc2
--- /dev/null
+++ b/tests/run.sh
@@ -0,0 +1,316 @@
+#!/hint/bash
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+## Running test cases
+
+# Beginning/end of line escape sequences
+BOL=$'\n'
+EOL=$'\n'
+
+# Update $EOL for the terminal size
+update_eol() {
+ # Bash gets $COLUMNS from stderr, so if it's redirected use tput instead
+ local cols="${COLUMNS-}"
+ if [ -z "$cols" ]; then
+ cols=$(tput cols)
+ fi
+
+ # Put the cursor at the last column, then write a space so the next
+ # character will wrap
+ EOL=$'\e['"${cols}G "
+}
+
+# ERR trap for tests
+debug_err() {
+ local ret=$? line func file
+ callers | while read -r line func file; do
+ if [ "$func" = source ]; then
+ local cmd="$(awk "NR == $line" "$file" 2>/dev/null)" || :
+ debug "$file" $line "${RED}error $ret${RST}" "$cmd" >&4
+ break
+ fi
+ done
+}
+
+# Run a single test
+run_test() (
+ set -eE
+ trap debug_err ERR
+ cd "$TMP"
+ source "$@"
+)
+
+# Run all the tests
+run_tests() {
+ if ((VERBOSE_TESTS)); then
+ BOL=''
+ elif ((COLOR_STDOUT)); then
+ # Carriage return + clear line
+ BOL=$'\r\e[K'
+
+ # Workaround for bash 4: checkwinsize is off by default. We can turn it
+ # on, but we also have to explicitly trigger a foreground job to finish
+ # so that it will update the window size before we use $COLUMNS
+ shopt -s checkwinsize
+ (:)
+
+ update_eol
+ trap update_eol WINCH
+ fi
+
+ passed=0
+ failed=0
+ skipped=0
+
+ if ((COLOR_STDOUT || VERBOSE_TESTS)); then
+ TEST_FMT="${BOL}${YLW}%s${RST}${EOL}"
+ else
+ TEST_FMT="."
+ fi
+
+ # Turn off set -e (but turn it back on in run_test)
+ set +e
+
+ for TEST in "${TEST_CASES[@]}"; do
+ printf "$TEST_FMT" "$TEST"
+
+ OUT="$TMP/$TEST.out"
+ mkdir -p "${OUT%/*}"
+
+ if ((VERBOSE_ERRORS)); then
+ run_test "$TESTS/$TEST.sh"
+ else
+ run_test "$TESTS/$TEST.sh" 2>"$TMP/$TEST.err"
+ fi
+ status=$?
+
+ if ((status == 0)); then
+ ((++passed))
+ elif ((status == EX_SKIP)); then
+ ((++skipped))
+ else
+ ((++failed))
+ ((VERBOSE_ERRORS)) || cat "$TMP/$TEST.err" >&2
+ cprintf "${BOL}${RED}%s failed!${RST}\n" "$TEST"
+ ((STOP)) && break
+ fi
+ done
+
+ printf "${BOL}"
+
+ if ((passed > 0)); then
+ cprintf "${GRN}tests passed: %d${RST}\n" "$passed"
+ fi
+ if ((skipped > 0)); then
+ cprintf "${CYN}tests skipped: %s${RST}\n" "$skipped"
+ fi
+ if ((failed > 0)); then
+ cprintf "${RED}tests failed: %s${RST}\n" "$failed"
+ exit 1
+ fi
+}
+
+## Utilities for the tests themselves
+
+# Return value when a test is skipped
+EX_SKIP=77
+
+# Skip the current test
+skip() {
+ if ((VERBOSE_SKIPPED)); then
+ caller | {
+ read -r line file
+ printf "${BOL}"
+ debug "$file" $line "${CYN}$TEST skipped!${RST}" "$(awk "NR == $line" "$file")" >&3
+ }
+ elif ((VERBOSE_TESTS)); then
+ cprintf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST"
+ fi
+
+ exit $EX_SKIP
+}
+
+# Run a command and check its exit status
+check_exit() {
+ local expected="$1"
+ local actual="0"
+ shift
+ "$@" || actual="$?"
+ ((actual == expected))
+}
+
+# Run a command with sudo
+bfs_sudo() {
+ if ((${#SUDO[@]})); then
+ "${SUDO[@]}" "$@"
+ else
+ return 1
+ fi
+}
+
+# Get the inode number of a file
+inum() {
+ ls -id "$@" | awk '{ print $1 }'
+}
+
+# Set an ACL on a file
+set_acl() {
+ case "$UNAME" in
+ Darwin)
+ chmod +a "$(id -un) allow read,write" "$1"
+ ;;
+ FreeBSD)
+ if (($(getconf ACL_NFS4 "$1") > 0)); then
+ setfacl -m "u:$(id -un):rw::allow" "$1"
+ else
+ setfacl -m "u:$(id -un):rw" "$1"
+ fi
+ ;;
+ *)
+ setfacl -m "u:$(id -un):rw" "$1"
+ ;;
+ esac
+}
+
+# Print a bfs invocation for --verbose=commands
+bfs_verbose() (
+ if ((!VERBOSE_COMMANDS)); then
+ return
+ fi
+
+ # Free up an fd for the pipe
+ exec 4>&-
+
+ {
+ printf "${GRN}%q${RST} " "${BFS[@]}"
+
+ local expr_started=
+ for arg; do
+ if [[ $arg == -[A-Z]* ]]; then
+ printf "${CYN}%q${RST} " "$arg"
+ elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then
+ expr_started=yes
+ printf "${RED}%q${RST} " "$arg"
+ elif [[ $expr_started && $arg == [\),] ]]; then
+ printf "${RED}%q${RST} " "$arg"
+ elif [[ $arg == -?* ]]; then
+ expr_started=yes
+ printf "${BLU}%q${RST} " "$arg"
+ elif [ "$expr_started" ]; then
+ printf "${BLD}%q${RST} " "$arg"
+ else
+ printf "${MAG}%q${RST} " "$arg"
+ fi
+ done
+
+ printf '\n'
+ } | color >&3
+)
+
+# Run the bfs we're testing
+invoke_bfs() {
+ bfs_verbose "$@"
+
+ local ret=0
+ # Close the logging fds
+ "${BFS[@]}" "$@" 3>&- 4>&- || ret=$?
+
+ # Allow bfs to fail, but not crash
+ if ((ret > 125)); then
+ exit "$ret"
+ else
+ return "$ret"
+ fi
+}
+
+if command -v unbuffer &>/dev/null; then
+ UNBUFFER=unbuffer
+elif command -v expect_unbuffer &>/dev/null; then
+ UNBUFFER=expect_unbuffer
+fi
+
+# Run bfs with a pseudo-terminal attached
+bfs_pty() {
+ test -n "${UNBUFFER:-}" || skip
+
+ bfs_verbose "$@"
+
+ local ret=0
+ "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" || ret=$?
+
+ if ((ret > 125)); then
+ exit "$ret"
+ else
+ return "$ret"
+ fi
+}
+
+# Create a directory tree with xattrs in scratch
+make_xattrs() {
+ clean_scratch
+
+ "$XTOUCH" scratch/{normal,xattr,xattr_2}
+ ln -s xattr scratch/link
+ ln -s normal scratch/xattr_link
+
+ case "$UNAME" in
+ Darwin)
+ xattr -w bfs_test true scratch/xattr \
+ && xattr -w bfs_test_2 true scratch/xattr_2 \
+ && xattr -s -w bfs_test true scratch/xattr_link
+ ;;
+ FreeBSD)
+ setextattr user bfs_test true scratch/xattr \
+ && setextattr user bfs_test_2 true scratch/xattr_2 \
+ && setextattr -h user bfs_test true scratch/xattr_link
+ ;;
+ *)
+ # Linux tmpfs doesn't support the user.* namespace, so we use the security.*
+ # namespace, which is writable by root and readable by others
+ bfs_sudo setfattr -n security.bfs_test scratch/xattr \
+ && bfs_sudo setfattr -n security.bfs_test_2 scratch/xattr_2 \
+ && bfs_sudo setfattr -h -n security.bfs_test scratch/xattr_link
+ ;;
+ esac
+}
+
+## Snapshot testing
+
+# Return value when a difference is detected
+EX_DIFF=20
+
+# Detect colored diff support
+if ((COLOR_STDERR)) && diff --color=always /dev/null /dev/null 2>/dev/null; then
+ DIFF="diff --color=always"
+else
+ DIFF="diff"
+fi
+
+# Sort the output file
+sort_output() {
+ sort -o "$OUT" "$OUT"
+}
+
+# Diff against the expected output
+diff_output() {
+ local GOLD="$TESTS/$TEST.out"
+
+ if ((UPDATE)); then
+ cp "$OUT" "$GOLD"
+ else
+ $DIFF -u "$GOLD" "$OUT" >&2
+ fi
+}
+
+# Run bfs, and diff it against the expected output
+bfs_diff() {
+ local ret=0
+ invoke_bfs "$@" >"$OUT" || ret=$?
+
+ sort_output
+ diff_output || exit $EX_DIFF
+
+ return $ret
+}