software/kisstdlib/./devscript/test-cli-lib.sh

A set of modules for the Python programming language designed for system programming with conceptually and algebraically simple primitives.

Files

Raw Source

Contents

#!/usr/bin/env bash
#
# A little shell library for testing CLI programs.
#
# This mainly exists for testing that given programs produce the same output
# between test invocations. I.e., mainly, for testing that given programs
# produce the same output at different versions.
#
# I.e., this is like integration tests, but for making sure the program plays
# well with itself across versions.

errors=0
task_errors=0

error() {
    echo -e "\e[1;31m" >&2
    echo -n "$*" >&2
    echo -e "\e[0m" >&2
    ((errors+=1))
    ((task_errors+=1))
}

die() {
    error "$*"
    exit 1
}

task_started=0
start() {
    echo -n "## $1"
    task_started=$(date +%s)
    task_errors=0
}

end() {
    local now=$(date +%s)
    echo ": $task_errors errors, $((now-task_started)) seconds"
}

finish() {
    echo "total: $errors errors"
    if ((errors > 0)); then
        exit 1
    fi
}

win32path() {
    sed 's%/%\\%g; s%^%Z:%' <<< "$1"
}

win32path0_many() {
    sed -z 's%/%\\%g; s%^%Z:%'
}

make_regular() {
    local dst="$1"
    local time="$2"
    local content="$3"

    echo -n "$content" > "$dst.part"
    mv "$dst.part" "$dst"
    touch -d "$time" "$dst"
}

make_symlink() {
    local dst="$1"
    local time="$2"
    local target="$3"

    ln -s "$target" "$dst.part"
    mv "$dst.part" "$dst"
    touch -h -d "$time" "$dst"
}

set_subtree_dir_mtimes() {
    find "$1" -type d -exec touch -h -d "$2" {} \;
}

equal_file() {
    if ! diff -U 0 "$1" "$2"; then
        error "equal_file \`$1\` \`$2\` failed"
    fi
}

equal_dir() {
    if ! diff -U 0 <(describe-forest "$1") <(describe-forest "$2"); then
        error "equal_dir \`$1\` \`$2\` failed"
    fi
}

fixed_file() {
    local src="$1"
    local got="$2"
    shift 2
    local bn="$(basename "$got")"
    local expected="$src.$bn"

    if ! [[ -e "$expected" ]]; then
        cp "$got" "$expected"
        echo " created \`$bn\` at \`$expected\`"
    elif ! diff -U 0 "$expected" "$got" ; then
        cp "$got" "$expected.new"
        error "fixed_file \`$bn\` failed"
    else
        rm -f "$expected.new"
    fi
}

fixed_dir() {
    describe-forest "$2" > "$2.describe-dir"
    fixed_file "$1" "$2.describe-dir"
}

ok() {
    "$@"
    code=$?
    if ((code != 0)); then
        die "$*: return code $code"
    fi
}

ok_stdio2() {
    local dst="$1"
    shift 1

    "$@" &> "$dst"
    code=$?
    if ((code != 0)); then
        cat "$dst" >&2
        die "$*: return code $code"
    fi
}

ok_no_stderr() {
    local tmp=$(mktemp --tmpdir test-cli-lib-stderr-XXXXXXXX)

    "$@" > /dev/null 2> "$tmp"
    code=$?
    if ((code != 0)); then
        cat "$tmp" >&2
        die "$*: return code $code"
    elif [[ -s "$tmp" ]]; then
        cat "$tmp" >&2
        error "$*: stderr is not empty"
    fi
    rm -r "$tmp"
}

any_stdio2() {
    local dst="$1"
    shift 1

    "$@" &> "$dst"
    code=$?
    if ((code != 0)); then
        echo "# \$? == $code" >> "$dst"
    fi
}

# temp dir
tmpdir=
# temp pid
tmppid=

set_tmpdir() {
    tmpdir=$(mktemp --tmpdir -d test-cli-lib-dir-XXXXXXXX)
    tmpdir=$(readlink -f "$tmpdir")
}

atexit_cleanup() {
    [[ -n "$tmpdir" ]] && rm -rf "$tmpdir"
    [[ -n "$tmppid" ]] && kill -9 "$tmppid"
}

umask 077
trap atexit_cleanup 0