#!/usr/bin/env bash # This section should be copied to any script that uses it. # BEGIN UTILS # Utility functions ⚠ Do not edit ⚠ Automatically inserted by scripts/utils.sh # ============================================================================== # Does not capture stdout, prints helpful info to stderr function show_call_passthrough { # passthrough mode info "$(blue "run") $(quote "$@") ... " "${@}" if test "$?" -gt 0; then info "$(paint B "... " N "$(quote "$@") -> " R "FAILED")" return 1 else info "$(paint B "... " N "$(quote "$@") -> " G "OK")" fi } # Does not capture stdout, only prints command if it failed. function show_call_candid { # candid "${@}" if test "$?" -gt 0; then info "$(quote "$@") -> $(red "FAILED")" return 1 fi } # Execute a command and print helpful things about the executed command and its # arguments. function show_call { case "$CHECK_CALL_MODE" in candid) show_call_candid "$@" ;; passthrough) show_call_passthrough "$@" ;; ""|quiet) # default mode local output info -n "$(blue "run") $(quote "$@") ... " if ! output="$("$@" 2>&1)"; then info "$(red "FAILED")" info "Error: $output" return 1 else info "$(green "OK")" fi ;; *) info "Invalid CHECK_CALL_MODE: ${CHECK_CALL_MODE}" exit 3 ;; esac } # Works like show_call, but exits the entire script if the command fails. function check_call { if ! show_call "$@" ; then info "$(red "Command failed. Aborting script.")" exit 1 fi } # Works like show_call_passthrough, but exits the entire script if the command fails. function check_call_passthrough { if ! show_call_passthrough "$@"; then info "$(red "Command failed. Aborting script.")" exit 1 fi } # Usage: paint R "this is red " B "this is blue " N "this is normal" function paint { local color local text while test "$#" -gt 0; do color="$1" text="$2" shift shift case "$color" in R) red "$text" ;; B) blue "$text" ;; G) green "$text" ;; Y) yellow "$text" ;; N) printf "%s" "$text" ;; *) echo "Invalid color: $color"; exit 1 ;; esac done } function quote { declare -a quoted_items quoted_items=() for item in "$@"; do local token local quoted token="$(printf '"%s"' "$item")" quoted="$(sed -E 's/^"([a-zA-Z0-9:._*/-]+)"$/\1/g' <<<"$token")" quoted_items=("${quoted_items[@]}" "$quoted") done echo "${quoted_items[@]}" } function info { echo "$@" >&2; } # like echo, but prints to stderr function red { printf '\x1b[31m%s\x1b[0m' "$@"; } function green { printf '\x1b[32m%s\x1b[0m' "$@"; } function yellow { printf '\x1b[33m%s\x1b[0m' "$@"; } function blue { printf '\x1b[34m%s\x1b[0m' "$@"; } # Usage: join_by TEXT [ ITEMS... ] # Example: join_by ", " "foo" "bar" # -> "foo, bar" function join_by { local d="$1"; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; } # Usage: repeat NUM CHARACTER # Example: repeat 3 "-" # => --- function repeat { for ((i=0; i<"${1:?}"; i++)); do printf '%s' "${2:?}"; done; } # END UTILS ################################################################################ # Anything below this point exists only for this "meta-utils" script ################################################################################ # Functions # ============================================================================== UTILS_SCRIPT_FILE="$0" function replace_utils_section { local src local dst src="${1:?}" dst="${2:?}" sed -E -e '/^# BEGIN UTILS/,/^# END UTILS/{r '<(sed -n -E '/^# BEGIN UTILS/,/^# END UTILS/p' < "$UTILS_SCRIPT_FILE") -e 'd}' "$src" > "$dst" } # Testing utilities # ============================================================================== function run_test { local code_to_test local script_seed local test_script local utils_section code_to_test="${1:?}" # Create a "seed.sh" script file with "code to test" in it and an empty utils # section test_dir="$(mktemp -t -d utils-self-test.XXXXX)" # We need to hide the utils section here to avoid it being snapped up from # the script we're in an the moment. utils_section="# BEGIN UTILS"$'\n'"# END UTILS" tee "$test_dir/seed.sh" > /dev/null < ${test_dir}/stdout \\ 2> ${test_dir}/stderr # Will not execute if check_call does "exit 1" echo \$? > ${test_dir}/retval SEED # Fill in the utils section and store it as "script.sh" replace_utils_section "$test_dir/seed.sh" "$test_dir/script.sh" info $'\n'"$(paint B "#### " G "Running test ${code_to_test@Q}")" bash "$test_dir/script.sh" # Assign effects to global state variable TEST_RESULT=( [exitcode]="$?" [stderr]="$(cat "$test_dir/stderr")" [stdout]="$(cat "$test_dir/stdout")" [retval]="$(test -f "$test_dir/retval" && cat "$test_dir/retval")" ) # Check $test_dir is set & sane, then remove $test_dir grep "utils-self-test." <<<"$test_dir" > /dev/null && rm -r "$test_dir" info $'\n'"$(paint G "Observed effects: ")" for key in "${!TEST_RESULT[@]}"; do info "$(paint B "$key " N " = ${TEST_RESULT[$key]@Q}")" done # We write the "Checks:" here in the expectation that, following this, checks # will be performed through "expect_result". info $'\n'"$(paint G "Checks: ")" } function expect_result { local key local expected local actual key="${1:?}" expected="${2?}" actual="${TEST_RESULT[$key]}" if test "$actual" = "$expected"; then info "$(paint G "expect_result " B "$key" N " = " N "${expected@Q}" G " ✅")" else info "$(paint R "expect_result " B "$key" N " = " N "${expected@Q}" R " ❌")" info "expected: ${expected@Q}" info " got: ${actual@Q}" exit 53 fi } # Test entrypoint # ============================================================================== function utils_self_test { run_test "show_call_passthrough echo \"example stdout\"" expect_result stdout "example stdout" run_test "check_call false" expect_result retval "" expect_result stdout "" expect_result stderr $'\E[34mrun\E[0m false ... \E[31mFAILED\E[0m\nError: \n\E[31mCommand failed. Aborting script.\E[0m' expect_result exitcode "1" run_test "show_call_passthrough true" expect_result retval "0" expect_result stdout "" expect_result exitcode "0" run_test "show_call_passthrough false" expect_result retval "1" expect_result stdout "" expect_result exitcode "0" run_test "CHECK_CALL_MODE=passthrough show_call false" expect_result retval "1" expect_result stdout "" expect_result exitcode "0" run_test "CHECK_CALL_MODE=passthrough check_call echo hello" expect_result retval "0" expect_result stdout "hello" expect_result exitcode "0" run_test "CHECK_CALL_MODE=candid check_call echo hello" expect_result retval "0" expect_result stdout "hello" expect_result stderr "" run_test "CHECK_CALL_MODE=candid check_call bash -c 'echo hello; exit 1'" expect_result retval "" expect_result exitcode "1" expect_result stdout "hello" expect_result stderr $'bash -c "echo hello; exit 1" -> \E[31mFAILED\E[0m\n\E[31mCommand failed. Aborting script.\E[0m' } ################################################################################ # Entrypoints ################################################################################ USAGE="$(cat <