123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 | #!/bin/sh
# End-to-end tests for the command-line interfaces Tomo generates. A program's
# argument parsing, `--help`, and manpage only exist once it is compiled, so
# these build the fixtures in test/cli and drive the real programs.
#
# Usage: cli_tests.sh TOMO
set -e
tomo=$1
if [ -z "$tomo" ]; then
echo "usage: $0 TOMO" >&2
exit 1
fi
# Help text is colorized only for a terminal, which this isn't, but a
# developer's COLOR=1 would bake escapes into the output:
COLOR=0
LC_ALL=C
export COLOR LC_ALL
repo="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)"
# The fixtures are built from their own directory, so the compiler needs an
# absolute path:
case $tomo in
/*) ;;
*) tomo="$(CDPATH= cd -- "$(dirname -- "$tomo")" && pwd -P)/$(basename -- "$tomo")" ;;
esac
cd "$repo/test/cli"
checked=0
failed=0
report() { # report DESCRIPTION EXPECTED ACTUAL
checked=$((checked + 1))
if [ "$3" = "$2" ]; then
printf ' \033[32mok\033[m %s\n' "$1"
else
failed=$((failed + 1))
printf ' \033[31mFAIL\033[m %s\n' "$1"
printf ' expected: %s\n' "$(printf '%s' "$2" | tr '\n' '|')"
printf ' actual: %s\n' "$(printf '%s' "$3" | tr '\n' '|')"
fi
}
# run PROGRAM.tm ARGS... -- the program's output, with its arguments after `--`
# so they go to the program instead of to `tomo`
run() {
program=$1
shift
"$tomo" "$program" -- "$@" 2>&1 || true
}
# check DESCRIPTION EXPECTED_OUTPUT PROGRAM.tm ARGS...
check() {
description=$1
expected=$2
shift 2
report "$description" "$expected" "$(run "$@")"
}
# check_contains DESCRIPTION SUBSTRING PROGRAM.tm ARGS...
check_contains() {
description=$1
substring=$2
shift 2
checked=$((checked + 1))
if run "$@" | grep -qF "$substring"; then
printf ' \033[32mok\033[m %s\n' "$description"
else
failed=$((failed + 1))
printf ' \033[31mFAIL\033[m %s\n' "$description"
printf ' expected output containing: %s\n' "$substring"
fi
}
# check_grep DESCRIPTION PATTERN PROGRAM.tm ARGS... -- the matching lines
check_grep() {
description=$1
pattern=$2
shift 2
report "$description" "$pattern" "$(run "$@" | grep -F "$pattern" | head -n 1 | sed 's/^ *//;s/ *$//')"
}
# A `use`d file's subcommand is dispatchable, with its own flags, defaults, and
# doc comment, exactly like one the program defines itself:
check 'a `use`d file'"'"'s subcommand runs' 'add ["a.txt", "b.txt"] force=yes' \
tool.tm add --files a.txt b.txt --force
check 'its defaults apply' 'fetch origin' tool.tm fetch
check 'its flags are parsed' 'fetch upstream' tool.tm fetch --remote=upstream
check 'a nested imported subcommand runs' 'plugin init ["vendor/foo"]' \
tool.tm plugin init --paths vendor/foo
check 'the program'"'"'s own subcommand runs' 'add ["everything"] force=no
commit hello' tool.tm commit -m hello
check 'a plain main() still runs with no subcommand' 'status short=yes' tool.tm -s
# The root listing covers both files' commands, each with its summary:
check_grep 'imported subcommands are listed in --help' \
'add Add file contents to the index' tool.tm --help
check_grep 'so are their sub-subcommands' 'plugin <command> ...' tool.tm --help
check_grep 'and the program'"'"'s own' 'commit Record changes to the repository' \
tool.tm --help
check_contains 'an imported subcommand has its own --help' \
'add --files text1 text2... [--force|-f]' tool.tm add --help
# Importing a module under a name keeps its subcommands out of the program's
# interface (they stay reachable as `commands.main.add(...)`):
check 'a named import contributes no subcommands' 'add ["direct"] force=yes' named_import.tm
report 'a named import'"'"'s commands are not in --help' '' \
"$(run named_import.tm --help | grep -F 'Commands:' || true)"
# Every subcommand is documented in the manpage, wherever it was defined. This
# installs into a scratch prefix, since an installed man/man1/ is where a
# manpage is meant to be read from:
prefix=$(mktemp -d)
trap 'rm -rf "$prefix"' EXIT HUP INT TERM
"$tomo" build tool.tm --install --prefix "$prefix" --yes -o "$prefix/tool" >/dev/null 2>&1
for command in 'tool add' 'tool plugin init' 'tool commit'; do
checked=$((checked + 1))
if grep -qF "$command" "$prefix/man/man1/tool.1"; then
printf ' \033[32mok\033[m manpage documents `%s`\n' "$command"
else
failed=$((failed + 1))
printf ' \033[31mFAIL\033[m manpage is missing `%s`\n' "$command"
fi
done
if [ "$failed" -gt 0 ]; then
printf '\033[31;1m%d of %d CLI tests failed\033[m\n' "$failed" "$checked"
exit 1
fi
printf 'All %d CLI tests passed.\n' "$checked"
|