# source this file; set up for tests # Copyright (C) 2009-2013 Free Software Foundation, Inc. # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Using this file in a test # ========================= # # The typical skeleton of a test looks like this: # # #!/bin/sh # . "${srcdir=.}/init.sh"; path_prepend_ . # Execute some commands. # Note that these commands are executed in a subdirectory, therefore you # need to prepend "../" to relative filenames in the build directory. # Note that the "path_prepend_ ." is useful only if the body of your # test invokes programs residing in the initial directory. # For example, if the programs you want to test are in src/, and this test # script is named tests/test-1, then you would use "path_prepend_ ../src", # or perhaps export PATH='$(abs_top_builddir)/src$(PATH_SEPARATOR)'"$$PATH" # to all tests via automake's TESTS_ENVIRONMENT. # Set the exit code 0 for success, 77 for skipped, or 1 or other for failure. # Use the skip_ and fail_ functions to print a diagnostic and then exit # with the corresponding exit code. # Exit $? # Executing a test that uses this file # ==================================== # # Running a single test: # $ make check TESTS=test-foo.sh # # Running a single test, with verbose output: # $ make check TESTS=test-foo.sh VERBOSE=yes # # Running a single test, with single-stepping: # 1. Go into a sub-shell: # $ bash # 2. Set relevant environment variables from TESTS_ENVIRONMENT in the # Makefile: # $ export srcdir=../../tests # this is an example # 3. Execute the commands from the test, copy&pasting them one by one: # $ . "$srcdir/init.sh"; path_prepend_ . # ... # 4. Finally # $ exit setglobal ME_ = $[expr "./$0" : '.*/\(.*\)$] # We use a trap below for cleanup. This requires us to go through # hoops to get the right exit status transported through the handler. # So use 'Exit STATUS' instead of 'exit STATUS' inside of the tests. # Turn off errexit here so that we don't trip the bug with OSF1/Tru64 # sh inside this function. proc Exit { set +e; shell {exit $1}; exit $1; } # Print warnings (e.g., about skipped and failed tests) to this file number. # Override by defining to say, 9, in init.cfg, and putting say, # export ...ENVVAR_SETTINGS...; $(SHELL) 9>&2 # in the definition of TESTS_ENVIRONMENT in your tests/Makefile.am file. # This is useful when using automake's parallel tests mode, to print # the reason for skip/failure to console, rather than to the .log files. : $(stderr_fileno_=2) # Note that correct expansion of "$*" depends on IFS starting with ' '. # Always write the full diagnostic to stderr. # When stderr_fileno_ is not 2, also emit the first line of the # diagnostic to that file descriptor. proc warn_ { # If IFS does not start with ' ', set it and emit the warning in a subshell. match $IFS { with ' '* printf '%s\n' "$ifsjoin(Argv)" > !2 test $stderr_fileno_ = 2 \ || do { printf '%s\n' "$ifsjoin(Argv)" | sed 1q > !$stderr_fileno_ ; } with * shell {setglobal IFS = '' ''; warn_ @Argv} } } proc fail_ { warn_ "$ME_: failed test: $ifsjoin(Argv)"; Exit 1; } proc skip_ { warn_ "$ME_: skipped test: $ifsjoin(Argv)"; Exit 77; } proc fatal_ { warn_ "$ME_: hard error: $ifsjoin(Argv)"; Exit 99; } proc framework_failure_ { warn_ "$ME_: set-up failure: $ifsjoin(Argv)"; Exit 99; } # Sanitize this shell to POSIX mode, if possible. setglobal DUALCASE = '1'; export DUALCASE if test -n $(ZSH_VERSION+set) && shell {emulate sh} >/dev/null 2>&1 { emulate sh setglobal NULLCMD = ':' alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST } else { match $[shell {set -o}] { with *posix* set -o posix } } # We require $(...) support unconditionally. # We require a few additional shell features only when $EXEEXT is nonempty, # in order to support automatic $EXEEXT emulation: # - hyphen-containing alias names # - we prefer to use ${var#...} substitution, rather than having # to work around lack of support for that feature. # The following code attempts to find a shell with support for these features. # If the current shell passes the test, we're done. Otherwise, test other # shells until we find one that passes. If one is found, re-exec it. # If no acceptable shell is found, skip the current test. # # The "...set -x; P=1 true 2>err..." test is to disqualify any shell that # emits "P=1" into err, as /bin/sh from SunOS 5.11 and OpenBSD 4.7 do. # # Use "9" to indicate success (rather than 0), in case some shell acts # like Solaris 10's /bin/sh but exits successfully instead of with status 2. # Eval this code in a subshell to determine a shell's suitability. # 10 - passes all tests; ok to use # 9 - ok, but enabling "set -x" corrupts app stderr; prefer higher score # ? - not ok setglobal gl_shell_test_script_ = '' test $(echo y) = y || exit 1 score_=10 if test "$VERBOSE" = yes; then test -n "$( (exec 3>&1; set -x; P=1 true 2>&3) 2> /dev/null)" && score_=9 fi test -z "$EXEEXT" && exit $score_ shopt -s expand_aliases alias a-b="echo zoo" v=abx test ${v%x} = ab \ && test ${v#a} = bx \ && test $(a-b) = zoo \ && exit $score_ '' if test "x$1" = "x--no-reexec" { shift } else { # Assume a working shell. Export to subshells (setup_ needs this). setglobal gl_set_x_corrupts_stderr_ = 'false' export gl_set_x_corrupts_stderr_ # Record the first marginally acceptable shell. setglobal marginal_ = '' # Search for a shell that meets our requirements. for re_shell_ in [__current__ $(CONFIG_SHELL:-no_shell) \ /bin/sh bash dash zsh pdksh fail] { test $re_shell_ = no_shell && continue # If we've made it all the way to the sentinel, "fail" without # finding even a marginal shell, skip this test. if test $re_shell_ = fail { test -z $marginal_ && skip_ failed to find an adequate shell setglobal re_shell_ = $marginal_ break } # When testing the current shell, simply "eval" the test code. # Otherwise, run it via $re_shell_ -c ... if test $re_shell_ = __current__ { # 'eval'ing this code makes Solaris 10's /bin/sh exit with # $? set to 2. It does not evaluate any of the code after the # "unexpected" first '('. Thus, we must run it in a subshell. shell { eval $gl_shell_test_script_ } > /dev/null 2>&1 } else { $re_shell_ -c $gl_shell_test_script_ !2 >/dev/null } setglobal st_ = $Status # $re_shell_ works just fine. Use it. if test $st_ = 10 { setglobal gl_set_x_corrupts_stderr_ = 'false' break } # If this is our first marginally acceptable shell, remember it. if test "$st_:$marginal_" = 9: { setglobal marginal_ = $re_shell_ setglobal gl_set_x_corrupts_stderr_ = 'true' } } if test $re_shell_ != __current__ { # Found a usable shell. Preserve -v and -x. match $Flags { with *v*x* | *x*v* setglobal opts_ = '-vx' with *v* setglobal opts_ = '-v' with *x* setglobal opts_ = '-x' with * setglobal opts_ = '' } exec $re_shell_ $opts_ $0 --no-reexec @Argv echo "$ME_: exec failed" !1 > !2 exit 127 } } # If this is bash, turn off all aliases. test -n $BASH_VERSION && unalias -a # Note that when supporting $EXEEXT (transparently mapping from PROG_NAME to # PROG_NAME.exe), we want to support hyphen-containing names like test-acos. # That is part of the shell-selection test above. Why use aliases rather # than functions? Because support for hyphen-containing aliases is more # widespread than that for hyphen-containing function names. test -n $EXEEXT && shopt -s expand_aliases # Enable glibc's malloc-perturbing option. # This is useful for exposing code that depends on the fact that # malloc-related functions often return memory that is mostly zeroed. # If you have the time and cycles, use valgrind to do an even better job. : $(MALLOC_PERTURB_=87) export MALLOC_PERTURB_ # This is a stub function that is run upon trap (upon regular exit and # interrupt). Override it with a per-test function, e.g., to unmount # a partition, or to undo any other global state changes. proc cleanup_ { :; } # Emit a header similar to that from diff -u; Print the simulated "diff" # command so that the order of arguments is clear. Don't bother with @@ lines. proc emit_diff_u_header_ { printf '%s\n' "diff -u $ifsjoin(Argv)" \ "--- $1 1970-01-01" \ "+++ $2 1970-01-01" } # Arrange not to let diff or cmp operate on /dev/null, # since on some systems (at least OSF/1 5.1), that doesn't work. # When there are not two arguments, or no argument is /dev/null, return 2. # When one argument is /dev/null and the other is not empty, # cat the nonempty file to stderr and return 1. # Otherwise, return 0. proc compare_dev_null_ { test $Argc = 2 || return 2 if test "x$1" = x/dev/null { test -s $2 || return 0 emit_diff_u_header_ @Argv; sed 's/^/+/' $2 return 1 } if test "x$2" = x/dev/null { test -s $1 || return 0 emit_diff_u_header_ @Argv; sed 's/^/-/' $1 return 1 } return 2 } if setglobal diff_out_ = $[exec !2 >/dev/null; diff -u $0 $0 < /dev/null] \ && diff -u Makefile $0 !2 >/dev/null | grep '^[+]#!' >/dev/null { # diff accepts the -u option and does not (like AIX 7 'diff') produce an # extra space on column 1 of every content line. if test -z $diff_out_ { proc compare_ { diff -u @Argv; } } else { proc compare_ { if diff -u @Argv > diff.out { # No differences were found, but Solaris 'diff' produces output # "No differences encountered". Hide this output. rm -f diff.out true } else { cat diff.out rm -f diff.out false } } } } elif setglobal diff_out_ = $[exec !2 >/dev/null; diff -c $0 $0 < /dev/null] { if test -z $diff_out_ { proc compare_ { diff -c @Argv; } } else { proc compare_ { if diff -c @Argv > diff.out { # No differences were found, but AIX and HP-UX 'diff' produce output # "No differences encountered" or "There are no differences between the # files.". Hide this output. rm -f diff.out true } else { cat diff.out rm -f diff.out false } } } } elif shell { cmp --version < /dev/null !2 > !1 | grep GNU } > /dev/null 2>&1 { proc compare_ { cmp -s @Argv; } } else { proc compare_ { cmp @Argv; } } # Usage: compare EXPECTED ACTUAL # # Given compare_dev_null_'s preprocessing, defer to compare_ if 2 or more. # Otherwise, propagate $? to caller: any diffs have already been printed. proc compare { # This looks like it can be factored to use a simple "case $?" # after unchecked compare_dev_null_ invocation, but that would # fail in a "set -e" environment. if compare_dev_null_ @Argv { return 0 } else { match $Status { with 1 return 1 with * compare_ @Argv } } } # An arbitrary prefix to help distinguish test directories. proc testdir_prefix_ { printf gt; } # Run the user-overridable cleanup_ function, remove the temporary # directory and exit with the incoming value of $?. proc remove_tmp_ { setglobal __st = $Status cleanup_ # cd out of the directory we're about to remove cd $initial_cwd_ || cd / || cd /tmp chmod -R u+rwx $test_dir_ # If removal fails and exit status was to be 0, then change it to 1. rm -rf $test_dir_ || do { test $__st = 0 && setglobal __st = '1'; } exit $__st } # Given a directory name, DIR, if every entry in it that matches *.exe # contains only the specified bytes (see the case stmt below), then print # a space-separated list of those names and return 0. Otherwise, don't # print anything and return 1. Naming constraints apply also to DIR. proc find_exe_basenames_ { setglobal feb_dir_ = $1 setglobal feb_fail_ = '0' setglobal feb_result_ = '' setglobal feb_sp_ = '' for feb_file_ in [$feb_dir_/*.exe] { # If there was no *.exe file, or there existed a file named "*.exe" that # was deleted between the above glob expansion and the existence test # below, just skip it. test "x$feb_file_" = "x$feb_dir_/*.exe" && test ! -f $feb_file_ \ && continue # Exempt [.exe, since we can't create a function by that name, yet # we can't invoke [ by PATH search anyways due to shell builtins. test "x$feb_file_" = "x$feb_dir_/[.exe" && continue match $feb_file_ { with *[!-a-zA-Z/0-9_.+]* setglobal feb_fail_ = '1'; break with * # Remove leading file name components as well as the .exe suffix. setglobal feb_file_ = $(feb_file_##*/) setglobal feb_file_ = $(feb_file_%.exe) setglobal feb_result_ = ""$feb_result_$feb_sp_$feb_file_"" } setglobal feb_sp_ = '' '' } test $feb_fail_ = 0 && printf %s $feb_result_ return $feb_fail_ } # Consider the files in directory, $1. # For each file name of the form PROG.exe, create an alias named # PROG that simply invokes PROG.exe, then return 0. If any selected # file name or the directory name, $1, contains an unexpected character, # define no alias and return 1. proc create_exe_shims_ { match $EXEEXT { with '' return 0 with .exe with * echo "$0: unexpected \$EXEEXT value: $EXEEXT" !1 > !2; return 1 } setglobal base_names_ = $[find_exe_basenames_ $1] \ || do { echo "$0 (exe_shim): skipping directory: $1" !1 > !2; return 0; } if test -n $base_names_ { for base_ in [$base_names_] { alias "$base_"="$base_$EXEEXT" } } return 0 } # Use this function to prepend to PATH an absolute name for each # specified, possibly-$initial_cwd_-relative, directory. proc path_prepend_ { while test $Argc != 0 { setglobal path_dir_ = $1 match $path_dir_ { with '' fail_ "invalid path dir: '$1'" with /* setglobal abs_path_dir_ = $path_dir_ with * setglobal abs_path_dir_ = "$initial_cwd_/$path_dir_" } match $abs_path_dir_ { with *:* fail_ "invalid path dir: '$abs_path_dir_'" } setglobal PATH = ""$abs_path_dir_:$PATH"" # Create an alias, FOO, for each FOO.exe in this directory. create_exe_shims_ $abs_path_dir_ \ || fail_ "something failed (above): $abs_path_dir_" shift } export PATH } proc setup_ { if test $VERBOSE = yes { # Test whether set -x may cause the selected shell to corrupt an # application's stderr. Many do, including zsh-4.3.10 and the /bin/sh # from SunOS 5.11, OpenBSD 4.7 and Irix 5.x and 6.5. # If enabling verbose output this way would cause trouble, simply # issue a warning and refrain. if $gl_set_x_corrupts_stderr_ { warn_ "using SHELL=$SHELL with 'set -x' corrupts stderr" } else { set -x } } setglobal initial_cwd_ = $PWD setglobal fail = '0' setglobal pfx_ = $[testdir_prefix_] setglobal test_dir_ = $[mktempd_ $initial_cwd_ "$pfx_-$ME_.XXXX] \ || fail_ "failed to create temporary directory in $initial_cwd_" cd $test_dir_ || fail_ "failed to cd to temporary directory" # As autoconf-generated configure scripts do, ensure that IFS # is defined initially, so that saving and restoring $IFS works. setglobal gl_init_sh_nl_ = '' '' setglobal IFS = "" "" $gl_init_sh_nl_"" # This trap statement, along with a trap on 0 below, ensure that the # temporary directory, $test_dir_, is removed upon exit as well as # upon receipt of any of the listed signals. for sig_ in [1 2 3 13 15] { eval "trap 'Exit $[expr $sig_ + 128]' $sig_" } } # Create a temporary directory, much like mktemp -d does. # Written by Jim Meyering. # # Usage: mktempd_ /tmp phoey.XXXXXXXXXX # # First, try to use the mktemp program. # Failing that, we'll roll our own mktemp-like function: # - try to get random bytes from /dev/urandom # - failing that, generate output from a combination of quickly-varying # sources and gzip. Ignore non-varying gzip header, and extract # "random" bits from there. # - given those bits, map to file-name bytes using tr, and try to create # the desired directory. # - make only $MAX_TRIES_ attempts # Helper function. Print $N pseudo-random bytes from a-zA-Z0-9. proc rand_bytes_ { setglobal n_ = $1 # Maybe try openssl rand -base64 $n_prime_|tr '+/=\012' abcd first? # But if they have openssl, they probably have mktemp, too. setglobal chars_ = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' setglobal dev_rand_ = '/dev/urandom' if test -r $dev_rand_ { # Note: 256-length($chars_) == 194; 3 copies of $chars_ is 186 + 8 = 194. dd ibs=$n_ count=1 if=$dev_rand_ !2 >/dev/null \ | env LC_ALL=C tr -c $chars_ 01234567$chars_$chars_$chars_ return } setglobal n_plus_50_ = $[expr $n_ + 50] setglobal cmds_ = ''date; date +%N; free; who -a; w; ps auxww; ps ef; netstat -n'' setglobal data_ = $[ shell {eval $cmds_} 2>&1 | gzip] # Ensure that $data_ has length at least 50+$n_ while : { setglobal len_ = $[echo $data_|wc -c] test $n_plus_50_ -le $len_ && break; setglobal data_ = $[ shell {echo $data_; eval $cmds_} 2>&1 | gzip] } echo $data_ \ | dd bs=1 skip=50 count=$n_ !2 >/dev/null \ | env LC_ALL=C tr -c $chars_ 01234567$chars_$chars_$chars_ } proc mktempd_ { match $Argc { with 2 with * fail_ "Usage: mktempd_ DIR TEMPLATE" } setglobal destdir_ = $1 setglobal template_ = $2 setglobal MAX_TRIES_ = '4' # Disallow any trailing slash on specified destdir: # it would subvert the post-mktemp "case"-based destdir test. match $destdir_ { with / with */ fail_ "invalid destination dir: remove trailing slash(es)" } match $template_ { with *XXXX with * fail_ \ "invalid template: $template_ (must have a suffix of at least 4 X's)" } # First, try to use mktemp. setglobal d = $[unset TMPDIR; do { mktemp -d -t -p $destdir_ $template_] \ || setglobal fail = '1' # The resulting name must be in the specified directory. match $d { with "$destdir_"* with * setglobal fail = '1' } # It must have created the directory. test -d $d || setglobal fail = '1' # It must have 0700 permissions. Handle sticky "S" bits. setglobal perms = $[ls -dgo $d !2 >/dev/null|tr S -] || setglobal fail = '1' match $perms { with drwx------* with * setglobal fail = '1' } test $fail = 0 && do { echo $d return } # If we reach this point, we'll have to create a directory manually. # Get a copy of the template without its suffix of X's. setglobal base_template_ = $[echo $template_|sed 's/XX*$//] # Calculate how many X's we've just removed. setglobal template_length_ = $[echo $template_ | wc -c] setglobal nx_ = $[echo $base_template_ | wc -c] setglobal nx_ = $[expr $template_length_ - $nx_] setglobal err_ = '' setglobal i_ = '1' while : { setglobal X_ = $[rand_bytes_ $nx_] setglobal candidate_dir_ = ""$destdir_/$base_template_$X_"" setglobal err_ = $[mkdir -m 0700 $candidate_dir_ !2 > !1] \ && do { echo $candidate_dir_; return; } test $MAX_TRIES_ -le $i_ && break; setglobal i_ = $[expr $i_ + 1] } fail_ $err_ } # If you want to override the testdir_prefix_ function, # or to add more utility functions, use this file. test -f "$srcdir/init.cfg" \ && source "$srcdir/init.cfg" setup_ @Argv # This trap is here, rather than in the setup_ function, because some # shells run the exit trap at shell function exit, rather than script exit. trap remove_tmp_ 0