#! /bin/sh # $NetBSD: test.subr,v 1.18 2021/09/09 18:23:44 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 <&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 <&2 exit 1 ;; esac done cat <&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 < "$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 --equals \n' 1>&2 exit 1 esac }