371 lines
8.4 KiB
Bash
371 lines
8.4 KiB
Bash
#! /bin/sh
|
|
# $NetBSD: test.subr,v 1.16 2020/06/07 05:53:53 rillig Exp $
|
|
#
|
|
# This file defines utilities for testing Makefile fragments and shell
|
|
# programs from the pkgsrc infrastructure. While testing one part of the
|
|
# infrastructure, other parts can be mocked away.
|
|
#
|
|
# A test case is defined by the following functions:
|
|
#
|
|
# test_case_begin
|
|
# test_case_set_up
|
|
# test_case_tear_down
|
|
# test_case_end
|
|
#
|
|
# These functions form the structure for a test. To define a test, use the
|
|
# following pattern:
|
|
#
|
|
# if test_case_begin "description of the test"; then
|
|
# ...
|
|
# test_case_end
|
|
# fi
|
|
#
|
|
# The functions test_case_set_up and test_case_tear_down can be defined in the
|
|
# test file to provide a test fixture that is common to all test cases. These
|
|
# functions are called by test_case_begin and test_case_end, respectively.
|
|
#
|
|
# During a test case, the following variables are defined:
|
|
#
|
|
# tmpdir
|
|
# a directory for creating intermediate files
|
|
#
|
|
# pkgsrcdir
|
|
# the real pkgsrc directory
|
|
#
|
|
# mocked_pkgsrcdir
|
|
# the directory where the mock files are installed, which override
|
|
# the Makefile fragments from the real pkgsrc infrastructure
|
|
#
|
|
#
|
|
# Setting up a test
|
|
#
|
|
# mock_cmd
|
|
# Returns the path to a newly created shell program whose behavior
|
|
# (output and exit status) is specified by pairs of
|
|
# --when-args/--then-output or --when-args/--then-exit.
|
|
#
|
|
# Example:
|
|
#
|
|
# hello=$(mock_cmd mock-hello \
|
|
# --when-args "" --then-output "Hello, world!" \
|
|
# --when-args "-t" --then-output "hello, world" \
|
|
# --when-args "-?" --then-exit 1
|
|
# )
|
|
#
|
|
# create_file $filename <<EOF ... EOF
|
|
# Creates a file in the temporary directory. The filename is relative
|
|
# to the temporary directory.
|
|
#
|
|
# create_file_lines $filename $line1 $line2 ...
|
|
# Creates a file in the temporary directory containing the given lines.
|
|
#
|
|
# create_pkgsrc_file $filename <<EOF ... EOF
|
|
# Creates a file in the temporary pkgsrc directory. If it is a Makefile
|
|
# fragment, it will be included instead of the one in the real pkgsrc
|
|
# infrastructure.
|
|
#
|
|
# This is typically used for creating an empty file for mk/bsd.prefs.mk
|
|
# or similar files that are included by the file that is currently under
|
|
# test.
|
|
#
|
|
# Example:
|
|
#
|
|
# create_pkgsrc_file "mk/pkg-build-options.mk" <<EOF
|
|
# # nothing
|
|
# EOF
|
|
#
|
|
#
|
|
# Running the code to be tested
|
|
#
|
|
# run_bmake $filename $bmake_args...
|
|
# Runs bmake with the correct environment so that it picks up the mocked
|
|
# infrastructure files first and then the ones from the real pkgsrc
|
|
# installation. The filename is relative to the temporary directory.
|
|
#
|
|
#
|
|
# Checking the result
|
|
#
|
|
# assert_that $actual --equals $expected
|
|
# Complains loudly if the string $actual is not equal to $expected.
|
|
#
|
|
# assert_that $tmpfile --file-contains-exactly $content
|
|
# Complains loudly if the file in $tmpdir does not contain the $content,
|
|
# plus a trailing newline.
|
|
#
|
|
# assert_that $tmpfile1 --file-equals $tmpfile2
|
|
# Complains loudly if the two files in $tmpdir differ.
|
|
#
|
|
# assert_that $tmpfile --file-is-empty
|
|
# Complains loudly if the file in $tmpdir is not empty.
|
|
#
|
|
# assert_that $tmpfile --file-is-lines $lines...
|
|
# Complains loudly if the file in $tmpdir does not consist of exactly
|
|
# the given lines.
|
|
#
|
|
# assert_succeed
|
|
# Counts one succeeded assertion, for the verbose statistics.
|
|
#
|
|
# assert_fail $format $args...
|
|
# Marks the current test as failed but continues to execute it.
|
|
#
|
|
|
|
set -eu
|
|
|
|
cleanup='yes'
|
|
: "${make:=bmake}"
|
|
if_verbose=':'
|
|
tmpdir="${TMPDIR:-/tmp}/infra-unittests-$$"
|
|
mocked_pkgsrcdir="$tmpdir/pkgsrc"
|
|
test_case_filter='.*'
|
|
|
|
rm -rf "$tmpdir"
|
|
mkdir -p "$mocked_pkgsrcdir"
|
|
|
|
while getopts 'kvf:' opt; do
|
|
case "$opt" in
|
|
(f) test_case_filter="$OPTARG";;
|
|
(k) cleanup='no';;
|
|
(v) if_verbose='';;
|
|
(*) printf '%s\n' 1>&2 \
|
|
"usage: $0 [-kv] [-f filter]" \
|
|
' -f regex only run matching test cases' \
|
|
' -k keep the temporary files' \
|
|
' -v verbose mode'
|
|
exit 1;;
|
|
esac
|
|
done
|
|
|
|
pkgsrcdir=""
|
|
for relative_pkgsrcdir in . .. ../.. ../../..; do
|
|
if [ -f "$relative_pkgsrcdir/mk/bsd.pkg.mk" ]; then
|
|
pkgsrcdir="$PWD/$relative_pkgsrcdir"
|
|
break
|
|
fi
|
|
done
|
|
if [ -z "$pkgsrcdir" ]; then
|
|
printf 'error: must be run from somewhere inside the pkgsrc directory\n' 1>&2
|
|
exit 1
|
|
fi
|
|
|
|
verbose_printf() {
|
|
$if_verbose printf "$@"
|
|
}
|
|
|
|
test_case_name="unknown test"
|
|
test_case_begun=0
|
|
test_case_ended=0
|
|
|
|
test_case_begin() {
|
|
test_case_name="$1"
|
|
[ "$(expr "$test_case_name" : "$test_case_filter")" -gt 0 ] \
|
|
|| return 1
|
|
|
|
test_case_begun="`expr "$test_case_begun" + 1`"
|
|
verbose_printf 'running test case "%s"\n' "$test_case_name"
|
|
|
|
cd "$tmpdir" || exit 1
|
|
rm -rf 'work'
|
|
mkdir 'work' || exit 1
|
|
cd 'work' || exit 1
|
|
|
|
test_case_set_up
|
|
}
|
|
|
|
# Can be redefined by actual tests.
|
|
test_case_set_up() {
|
|
:
|
|
}
|
|
|
|
# Can be redefined by actual tests.
|
|
test_case_tear_down() {
|
|
:
|
|
}
|
|
|
|
test_case_end() {
|
|
test_case_tear_down
|
|
|
|
test_case_ended="`expr "$test_case_ended" + 1`"
|
|
test "$test_case_ended" = "$test_case_begun" \
|
|
|| assert_fail 'unbalanced test_case_begin (%d) and test_case_end (%d)\n' \
|
|
"$test_case_begun" "$test_case_ended"
|
|
|
|
test_case_name="unknown test"
|
|
}
|
|
|
|
test_subr_cleanup() {
|
|
exit_status=$?
|
|
if [ $exit_status -ne 0 ]; then
|
|
printf 'info: the test files are in %s\n' "$tmpdir" 1>&2
|
|
exit $exit_status
|
|
fi
|
|
|
|
[ "$cleanup" = "yes" ] && rm -rf "$tmpdir"
|
|
|
|
verbose_printf '%s%d assertions succeeded, %d failed\n' \
|
|
"$assert_fail_sep" "$assert_succeeded" "$assert_failed"
|
|
|
|
if [ "$assert_failed" != 0 ]; then
|
|
exit 1
|
|
fi
|
|
}
|
|
trap "test_subr_cleanup" EXIT
|
|
|
|
mock_cmd() {
|
|
cmdname="$1"
|
|
shift 1
|
|
|
|
. "$pkgsrcdir/mk/tools/shquote.sh"
|
|
|
|
{
|
|
printf '#! /bin/sh\n'
|
|
printf '\n'
|
|
|
|
while [ $# -ge 4 ]; do
|
|
case $1,$3 in
|
|
(--when-args,--then-output)
|
|
shquote "$2"; shquoted_arg="$shquoted"
|
|
shquote "$4"; shquoted_output="$shquoted"
|
|
cat <<EOF
|
|
[ x"\$*" = x$shquoted_arg ] && { printf '%s\n' $shquoted_output && exit 0; }
|
|
|
|
EOF
|
|
shift 4
|
|
;;
|
|
|
|
(--when-args,--then-exit)
|
|
shquote "$2"; shquoted_arg="$shquoted"
|
|
shquote "$4"; shquoted_exit="$shquoted"
|
|
cat <<EOF
|
|
[ x"\$*" = x$shquoted_arg ] && exit $shquoted_exit
|
|
|
|
EOF
|
|
shift 4
|
|
;;
|
|
*) printf 'error: invalid arguments to mock_cmd: %s\n' "$*" 1>&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
cat <<EOF
|
|
printf 'error: %s: no mock behavior defined for arguments "%s"\n' "\$0" "\$*" 1>&2
|
|
exit 1
|
|
EOF
|
|
} > "$tmpdir/$cmdname"
|
|
chmod +x "$tmpdir/$cmdname"
|
|
|
|
printf '%s\n' "$tmpdir/$cmdname"
|
|
}
|
|
|
|
create_file() {
|
|
assert_that "$#" --equals 1
|
|
mkdir -p "$(dirname -- "$1")"
|
|
cat > "$1"
|
|
}
|
|
|
|
create_file_lines() {
|
|
_cfl_filename="$1"; shift
|
|
mkdir -p "$(dirname -- "$_cfl_filename")"
|
|
printf '%s\n' "$@" > "$_cfl_filename"
|
|
}
|
|
|
|
create_pkgsrc_file() {
|
|
mkdir -p "$(dirname -- "$mocked_pkgsrcdir/$1")"
|
|
cat > "$mocked_pkgsrcdir/$1"
|
|
}
|
|
|
|
run_bmake() {
|
|
cat <<EOF > "$tmpdir/test.subr.main.mk"
|
|
PKGSRCDIR= $pkgsrcdir
|
|
.PATH: $mocked_pkgsrcdir
|
|
.PATH: $pkgsrcdir
|
|
.include "$1"
|
|
EOF
|
|
shift
|
|
|
|
"$make" -f "$tmpdir/test.subr.main.mk" ${1+"$@"}
|
|
}
|
|
|
|
assert_succeeded=0
|
|
assert_failed=0
|
|
assert_fail_sep=''
|
|
|
|
assert_succeed() {
|
|
assert_succeeded=`expr "$assert_succeeded" + 1`
|
|
}
|
|
|
|
assert_fail() {
|
|
printf '%s' "$assert_fail_sep" 1>&2
|
|
assert_fail_sep='
|
|
'
|
|
|
|
printf 'assertion failed in "%s": ' "$test_case_name" 1>&2
|
|
|
|
printf "$@" 1>&2
|
|
assert_failed=`expr "$assert_failed" + 1`
|
|
}
|
|
|
|
files_equal() {
|
|
(diff -u -- "$@" >/dev/null) || return 1
|
|
}
|
|
|
|
diff_without_timestamps() {
|
|
(diff -u -- "$@" || true) \
|
|
| awk '/^(---|[+][+][+]) / { print($1, $2); next } { print }' 1>&2
|
|
}
|
|
|
|
assert_that() {
|
|
case "$2" in
|
|
(--equals)
|
|
if [ "x$1" = "x$3" ]; then
|
|
assert_succeed
|
|
return 0
|
|
fi
|
|
assert_fail '\n expected: <%s>\n but was: <%s>\n' "$3" "$1"
|
|
;;
|
|
|
|
(--file-contains-exactly)
|
|
printf '%s\n' "$3" > "$tmpdir/expected"
|
|
if files_equal "$tmpdir/expected" "$1"; then
|
|
assert_succeed
|
|
return 0
|
|
fi
|
|
assert_fail 'file "%s" has unexpected content:\n' "$1"
|
|
diff_without_timestamps "$tmpdir/expected" "$1"
|
|
;;
|
|
|
|
(--file-equals)
|
|
if files_equal "$3" "$1"; then
|
|
assert_succeed
|
|
return 0
|
|
fi
|
|
assert_fail 'files "%s" and "%s" differ:\n' "$1" "$3"
|
|
diff_without_timestamps "$3" "$1"
|
|
;;
|
|
|
|
(--file-is-empty)
|
|
if files_equal "/dev/null" "$1"; then
|
|
assert_succeed
|
|
return 0
|
|
fi
|
|
assert_fail 'file "%s" is not empty:\n' "$1"
|
|
diff_without_timestamps "/dev/null" "$1"
|
|
;;
|
|
|
|
(--file-is-lines)
|
|
_assert_that_tmp_actual="$1"
|
|
_assert_that_filename="$1"; shift 2
|
|
|
|
printf '%s\n' "$@" > "$tmpdir/expected"
|
|
if files_equal "$tmpdir/expected" "$_assert_that_tmp_actual"; then
|
|
assert_succeed
|
|
return 0
|
|
fi
|
|
assert_fail 'file "%s" has unexpected content:\n' "$_assert_that_filename"
|
|
diff_without_timestamps "$tmpdir/expected" "$_assert_that_tmp_actual"
|
|
;;
|
|
|
|
(*)
|
|
printf 'usage: assert_that <expr> --equals <expr>\n' 1>&2
|
|
exit 1
|
|
esac
|
|
}
|