From 785a3f2d777627f39bed44f4ae7a0180d5184109 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Oct 2023 16:37:47 -0400 Subject: tests: Refactor implementation into separate files --- tests/tests.sh | 824 +-------------------------------------------------------- 1 file changed, 12 insertions(+), 812 deletions(-) (limited to 'tests/tests.sh') diff --git a/tests/tests.sh b/tests/tests.sh index dcef28e..3890243 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -6,815 +6,15 @@ set -euP umask 022 -export LC_ALL=C -export TZ=UTC0 - -SAN_OPTIONS="halt_on_error=1:log_to_syslog=0" -export ASAN_OPTIONS="$SAN_OPTIONS" -export LSAN_OPTIONS="$SAN_OPTIONS" -export MSAN_OPTIONS="$SAN_OPTIONS" -export TSAN_OPTIONS="$SAN_OPTIONS" -export UBSAN_OPTIONS="$SAN_OPTIONS" - -export LS_COLORS="" -unset BFS_COLORS - -BLD=$'\e[01m' -RED=$'\e[01;31m' -GRN=$'\e[01;32m' -YLW=$'\e[01;33m' -BLU=$'\e[01;34m' -MAG=$'\e[01;35m' -CYN=$'\e[01;36m' -RST=$'\e[0m' - -function color_fd() { - [ -z "${NO_COLOR:-}" ] && [ -t "$1" ] -} - -color_fd 1 && COLOR_STDOUT=1 || COLOR_STDOUT=0 -color_fd 2 && COLOR_STDERR=1 || COLOR_STDERR=0 - -# Filter out escape sequences if necessary -function color() { - if color_fd 1; then - cat - else - sed $'s/\e\\[[^m]*m//g' - fi -} - -# printf with auto-detected color support -function cprintf() { - printf "$@" | color -} - -UNAME=$(uname) - -if [ "$UNAME" = Darwin ]; then - # ASan on macOS likes to report - # - # malloc: nano zone abandoned due to inability to preallocate reserved vm space. - # - # to syslog, which as a side effect opens a socket which might take the - # place of one of the standard streams if the process is launched with it - # closed. This environment variable avoids the message. - export MallocNanoZone=0 -fi - -if command -v capsh &>/dev/null; then - if capsh --has-p=cap_dac_override &>/dev/null || capsh --has-p=cap_dac_read_search &>/dev/null; then - if [ -n "${BFS_TRIED_DROP:-}" ]; then - color >&2 <&2 <&2 <&2 - usage >&2 - exit 1 - ;; - *) - PATTERNS+=("$arg") - ;; - esac -done - -function _realpath() { - ( - cd "$(dirname -- "$1")" - echo "$PWD/$(basename -- "$1")" - ) -} - -TESTS=$(_realpath "$(dirname -- "${BASH_SOURCE[0]}")") - -if [ "${BUILDDIR-}" ]; then - BIN=$(_realpath "$BUILDDIR/bin") -else - BIN=$(_realpath "$TESTS/../bin") -fi -MKSOCK="$BIN/tests/mksock" -XTOUCH="$BIN/tests/xtouch" - -# Try to resolve the path to $BFS before we cd, while also supporting -# --bfs="./bin/bfs -S ids" -read -a BFS <<<"${BFS:-$BIN/bfs}" -BFS[0]=$(_realpath "$(command -v "${BFS[0]}")") - -# The temporary directory that will hold our test data -TMP=$(mktemp -d "${TMPDIR:-/tmp}"/bfs.XXXXXXXXXX) -chown "$(id -u):$(id -g)" "$TMP" - -cd "$TESTS" - -if ((${#PATTERNS[@]} == 0)); then - PATTERNS=("*") -fi - -TEST_CASES=() -for TEST in {posix,common,bsd,gnu,bfs}/*.sh; do - TEST="${TEST%.sh}" - for PATTERN in "${PATTERNS[@]}"; do - if [[ $TEST == $PATTERN ]]; then - TEST_CASES+=("$TEST") - break - fi - done -done - -if ((${#TEST_CASES[@]} == 0)); then - cprintf "${RED}error:${RST} No tests matched" >&2 - cprintf " ${BLD}%s${RST}" "${PATTERNS[@]}" >&2 - cprintf ".\n\n" >&2 - usage >&2 - exit 1 -fi - -function quote() { - printf '%q' "$1" - shift - if (($# > 0)); then - printf ' %q' "$@" - fi -} - -# Run a command when this (sub)shell exits -function defer() { - trap -- KILL - if ! trap -p EXIT | grep -q pop_defers; then - DEFER_CMDS=() - DEFER_LINES=() - DEFER_FILES=() - trap pop_defers EXIT - fi - - DEFER_CMDS+=("$(quote "$@")") - - local line file - read -r line file < <(caller) - DEFER_LINES+=("$line") - DEFER_FILES+=("$file") -} - -function report_err() { - local file="${1/#*\/tests\//tests\/}" - set -- "$file" "${@:2}" - - if ((COLOR_STDERR)); then - printf "${BLD}%s:%d:${RST} ${RED}error %d:${RST}\n %s\n" "$@" >&2 - else - printf "%s:%d: error %d:\n %s\n" "$@" >&2 - fi -} - -function pop_defer() { - local cmd="${DEFER_CMDS[-1]}" - local file="${DEFER_FILES[-1]}" - local line="${DEFER_LINES[-1]}" - unset "DEFER_CMDS[-1]" - unset "DEFER_FILES[-1]" - unset "DEFER_LINES[-1]" - - local ret=0 - eval "$cmd" || ret=$? - - if ((ret != 0)); then - report_err "$file" $line $ret "defer $cmd" - fi - - return $ret -} - -function pop_defers() { - local ret=0 - - while ((${#DEFER_CMDS[@]} > 0)); do - pop_defer || ret=$? - done - - return $ret -} - -function bfs_sudo() { - if ((${#SUDO[@]})); then - "${SUDO[@]}" "$@" - else - return 1 - fi -} - -function clean_scratch() { - if [ -e "$TMP/scratch" ]; then - # Try to unmount anything left behind - if ((${#SUDO[@]})) && command -v mountpoint &>/dev/null; then - for path in "$TMP"/scratch/*; do - if mountpoint -q "$path"; then - sudo umount "$path" - fi - done - fi - - # Reset any modified permissions - chmod -R +rX "$TMP/scratch" - - rm -rf "$TMP/scratch" - fi - - mkdir "$TMP/scratch" -} - -# Clean up temporary directories on exit -function cleanup() { - # Don't force rm to deal with long paths - for dir in "$TMP"/deep/*/*; do - if [ -d "$dir" ]; then - (cd "$dir" && rm -rf *) - fi - done - - # In case a test left anything weird in scratch/ - clean_scratch - - rm -rf "$TMP" -} - -if ((CLEAN)); then - defer cleanup -else - echo "Test files saved to $TMP" -fi - -# Creates a simple file+directory structure for tests -function make_basic() { - "$XTOUCH" -p "$1"/{a,b,c/d,e/f,g/h/,i/} - "$XTOUCH" -p "$1"/{j/foo,k/foo/bar,l/foo/bar/baz} - echo baz >"$1/l/foo/bar/baz" -} -make_basic "$TMP/basic" - -# Creates a file+directory structure with various permissions for tests -function make_perms() { - "$XTOUCH" -p -M000 "$1/0" - "$XTOUCH" -p -M444 "$1/r" - "$XTOUCH" -p -M222 "$1/w" - "$XTOUCH" -p -M644 "$1/rw" - "$XTOUCH" -p -M555 "$1/rx" - "$XTOUCH" -p -M311 "$1/wx" - "$XTOUCH" -p -M755 "$1/rwx" -} -make_perms "$TMP/perms" - -# Creates a file+directory structure with various symbolic and hard links -function make_links() { - "$XTOUCH" -p "$1/file" - ln -s file "$1/symlink" - ln "$1/file" "$1/hardlink" - ln -s nowhere "$1/broken" - ln -s symlink/file "$1/notdir" - "$XTOUCH" -p "$1/deeply/nested"/{dir/,file} - ln -s file "$1/deeply/nested/link" - ln -s nowhere "$1/deeply/nested/broken" - ln -s deeply/nested "$1/skip" -} -make_links "$TMP/links" - -# Creates a file+directory structure with symbolic link loops -function make_loops() { - "$XTOUCH" -p "$1/file" - ln -s file "$1/symlink" - ln -s nowhere "$1/broken" - ln -s symlink/file "$1/notdir" - ln -s loop "$1/loop" - mkdir -p "$1/deeply/nested/dir" - ln -s ../../deeply "$1/deeply/nested/loop" - ln -s deeply/nested/loop/nested "$1/skip" -} -make_loops "$TMP/loops" - -# Creates a file+directory structure with varying timestamps -function make_times() { - "$XTOUCH" -p -t "1991-12-14 00:00" "$1/a" - "$XTOUCH" -p -t "1991-12-14 00:01" "$1/b" - "$XTOUCH" -p -t "1991-12-14 00:02" "$1/c" - ln -s a "$1/l" - "$XTOUCH" -p -h -t "1991-12-14 00:03" "$1/l" - "$XTOUCH" -p -t "1991-12-14 00:04" "$1" -} -make_times "$TMP/times" - -# Creates a file+directory structure with various weird file/directory names -function make_weirdnames() { - "$XTOUCH" -p "$1/-/a" - "$XTOUCH" -p "$1/(/b" - "$XTOUCH" -p "$1/(-/c" - "$XTOUCH" -p "$1/!/d" - "$XTOUCH" -p "$1/!-/e" - "$XTOUCH" -p "$1/,/f" - "$XTOUCH" -p "$1/)/g" - "$XTOUCH" -p "$1/.../h" - "$XTOUCH" -p "$1/\\/i" - "$XTOUCH" -p "$1/ /j" - "$XTOUCH" -p "$1/[/k" -} -make_weirdnames "$TMP/weirdnames" - -# Creates a very deep directory structure for testing PATH_MAX handling -function make_deep() { - mkdir -p "$1" - - # $name will be 255 characters, aka _XOPEN_NAME_MAX - local name="0123456789ABCDEF" - name="${name}${name}${name}${name}" - name="${name}${name}${name}${name}" - name="${name:0:255}" - - for i in {0..9} A B C D E F; do - "$XTOUCH" -p "$1/$i/$name" - - ( - cd "$1/$i" - - # 8 * 512 == 4096 >= PATH_MAX - for _ in {1..8}; do - mv "$name" .. - mkdir -p "$name/$name" - mv "../$name" "$name/$name/" - done - ) - done -} -make_deep "$TMP/deep" - -# Creates a directory structure with many different types, and therefore colors -function make_rainbow() { - "$XTOUCH" -p "$1/file.txt" - "$XTOUCH" -p "$1/file.dat" - "$XTOUCH" -p "$1/lower".{gz,tar,tar.gz} - "$XTOUCH" -p "$1/upper".{GZ,TAR,TAR.GZ} - "$XTOUCH" -p "$1/lu.tar.GZ" "$1/ul.TAR.gz" - ln -s file.txt "$1/link.txt" - "$XTOUCH" -p "$1/mh1" - ln "$1/mh1" "$1/mh2" - mkfifo "$1/pipe" - # TODO: block - ln -s /dev/null "$1/chardev_link" - ln -s nowhere "$1/broken" - "$MKSOCK" "$1/socket" - "$XTOUCH" -p "$1"/s{u,g,ug}id - chmod u+s "$1"/su{,g}id - chmod g+s "$1"/s{u,}gid - mkdir "$1/ow" "$1"/sticky{,_ow} - chmod o+w "$1"/*ow - chmod +t "$1"/sticky* - "$XTOUCH" -p "$1"/exec.sh - chmod +x "$1"/exec.sh - "$XTOUCH" -p "$1/"$'\e[1m/\e[0m' -} -make_rainbow "$TMP/rainbow" - -mkdir "$TMP/scratch" - -# Close stdin so bfs doesn't think we're interactive -exec &1 -fi - -function bfs_verbose() { - if ((!VERBOSE_COMMANDS)); then - return - fi - - { - 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 -} - -function invoke_bfs() { - bfs_verbose "$@" - - local ret=0 - "${BFS[@]}" "$@" || 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 - -function 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 -} - -function check_exit() { - local expected="$1" - local actual="0" - shift - "$@" || actual="$?" - ((actual == expected)) -} - -# 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 - -# Return value when a difference is detected -EX_DIFF=20 -# Return value when a test is skipped -EX_SKIP=77 - -function sort_output() { - sort -o "$OUT" "$OUT" -} - -function diff_output() { - local GOLD="$TESTS/$TEST.out" - - if ((UPDATE)); then - cp "$OUT" "$GOLD" - else - $DIFF -u "$GOLD" "$OUT" >&2 - fi -} - -function bfs_diff() ( - bfs_verbose "$@" - - # Close the dup()'d stdout to make sure we have enough fd's for the process - # substitution, even with low ulimit -n - exec 3>&- - - "${BFS[@]}" "$@" | sort >"$OUT" - local status="${PIPESTATUS[0]}" - - diff_output || exit $EX_DIFF - return "$status" -) - -function skip() { - if ((VERBOSE_SKIPPED)); then - caller | { - read -r line file - cprintf "${BOL}${CYN}%s skipped!${RST} (%s)\n" "$TEST" "$(awk "NR == $line" "$file")" - } - elif ((VERBOSE_TESTS)); then - cprintf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" - fi - - exit $EX_SKIP -} - -function closefrom() { - if [ -d /proc/self/fd ]; then - local fds=/proc/self/fd - else - local fds=/dev/fd - fi - - for fd in "$fds"/*; do - if [ ! -e "$fd" ]; then - continue - fi - - local fd="${fd##*/}" - if ((fd >= $1)); then - eval "exec ${fd}<&-" - fi - done -} - -function inum() { - ls -id "$@" | awk '{ print $1 }' -} - -function 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 -} - -function 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 -} - -cd "$TMP" -set +e - -BOL='\n' -EOL='\n' - -function 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="\\033[${cols}G " -} - -if ((VERBOSE_TESTS)); then - BOL='' -elif ((COLOR_STDOUT)); then - BOL='\r\033[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 - -function callers() { - local frame=0 - while caller $frame; do - ((++frame)) - done -} - -function 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)" || : - report_err "$file" $line $ret "$cmd" - break - fi - done -} - -function run_test() ( - set -eE - trap debug_err ERR - source "$@" -) - -passed=0 -failed=0 -skipped=0 - -if ((COLOR_STDOUT || VERBOSE_TESTS)); then - TEST_FMT="${BOL}${YLW}%s${RST}${EOL}" -else - TEST_FMT="." -fi - -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 +TESTS="$(dirname -- "${BASH_SOURCE[0]}")" +. "$TESTS/util.sh" +. "$TESTS/color.sh" +. "$TESTS/stddirs.sh" +. "$TESTS/getopts.sh" +. "$TESTS/run.sh" + +stdenv +drop_root "$@" +parse_args "$@" +make_stddirs +run_tests -- cgit v1.2.3