]> git.ipfire.org Git - thirdparty/autoconf.git/commitdiff
Autotest: add official way to execute code before all/each test.
authorZack Weinberg <zackw@panix.com>
Wed, 28 Oct 2020 21:04:22 +0000 (17:04 -0400)
committerZack Weinberg <zackw@panix.com>
Wed, 2 Dec 2020 15:29:25 +0000 (10:29 -0500)
Currently, there isn’t any documented way for an Autotest testsuite to
add custom code to be run either right before the main driver loop, or
at the point of each AT_SETUP.  For instance, there’s no good place to
put environment variable sanitization that should apply to the entire
testsuite (but isn’t universally relevant), or shell function
definitions to be used by custom test macros.

Autoconf’s test suite is poking shell functions directly into the
PREPARE_TESTS diversion, and doing environment variable sanitization
in each individual test.  Both of these are obviously undesirable.

This patch adds three new AT_* macros that can be used to do these
things in an officially-supported way: AT_PREPARE_TESTS adds code to
be run right before the main driver loop, AT_PREPARE_EACH_TEST adds
code to be run at the beginning of each test, and AT_TEST_HELPER_FN
defines a shell function that will be available to each test.  In
Autoconf’s test suite, I use AT_PREPARE_TESTS to factor out
environment variable sanitization that *ought* to apply across the
board, and AT_TEST_HELPER_FN for the helper function used by
AT_CHECK_ENV.

(This fixes the testsuite bug reported by Jannick at
https://lists.gnu.org/archive/html/autoconf/2020-10/msg00052.html :
CONFIG_SITE in the parent environment will no longer be visible to tests.)

It would be nice to give an example of when AT_PREPARE_EACH_TEST is
useful, in the documentation, but I didn’t find one in the autoconf
test suite.

* lib/autotest/general.m4 (AT_PREPARE_TESTS, AT_PREPARE_EACH_TEST)
  (AT_TEST_HELPER_FN): New macros.
  (AT_INIT, AT_TESTED): Emit the code to report tested programs only
  if it’s needed, and make sure it’s after any code added by
  AT_PREPARE_TESTS.

* tests/local.at: Add AT_PREPARE_TESTS block that ensures
  $MAKE is set sensibly and $MAKEFLAGS and $CONFIG_SITE are unset.
  Use AT_TEST_HELPER_FN for the helper function needed by AT_CHECK_ENV.
  (AT_CHECK_MAKE): No need to sanitize $MAKE or $MAKEFLAGS here.
* tests/base.at, tests/compile.at, tests/m4sh.at, tests/torture.at:
  No need to unset or neutralize $CONFIG_SITE in individual tests.
* tests/autotest.at: Add tests for new macros.

* doc/autoconf.texi, NEWS: Document new macros.

NEWS
doc/autoconf.texi
lib/autotest/general.m4
tests/autotest.at
tests/base.at
tests/compile.at
tests/local.at
tests/m4sh.at
tests/torture.at

diff --git a/NEWS b/NEWS
index 207d89b5a58f3e19c01540edf5d9d29eb33b95c6..d20b543a834beb1a07f64e15e8009e7519a94fe3 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -469,6 +469,11 @@ GNU Autoconf NEWS - User visible changes.
 *** AT_TESTED arguments can use variable or command substitutions, including
     in particular $EXEEXT
 
+*** New macros AT_PREPARE_TESTS, AT_PREPARE_EACH_TEST, and AT_TEST_HELPER_FN.
+
+   These provide an official way to define testsuite-specific
+   initialization code and shell functions.
+
 * Noteworthy changes in release 2.69 (2012-04-24) [stable]
 
 ** Autoconf now requires perl 5.6 or better (but generated configure
index d0865c1e25bb8db92ec985d910dfe721c16ba700..afeca05654586192c28702fa1c4e104b18bd827e 100644 (file)
@@ -25592,6 +25592,41 @@ will still use shell variable expansion (@samp{$}), command substitution
 via @file{atlocal} or @file{atconfig}.
 @end defmac
 
+@defmac AT_PREPARE_TESTS (@var{shell-code})
+@atindex{PREPARE_TESTS}
+Execute @var{shell-code} in the main testsuite process,
+after initializing the test suite and processing command-line options,
+but before running any tests.  If this macro is used several times,
+all of the @var{shell-code}s will be executed,
+in the order they appeared in @file{testsuite.at}.
+
+One reason to use @code{AT_PREPARE_TESTS} is when the programs under
+test are sensitive to environment variables: you can unset all these
+variables or reset them to safe values in @var{shell-code}.
+
+@var{shell-code} is only executed if at least one test is going to be
+run.  In particular, it will not be executed if any of the @option{--help},
+@option{--version}, @option{--list}, or @option{--clean} options are
+given to @command{testsuite} (@pxref{testsuite Invocation}).
+@end defmac
+
+@defmac AT_PREPARE_EACH_TEST (@var{shell-code})
+@atindex{AT_PREPARE_EACH_TEST}
+Execute @var{shell-code} in each test group's subshell, at the point of
+the @code{AT_SETUP} that starts the test group.
+@end defmac
+
+@defmac AT_TEST_HELPER_FN (@var{name}, @var{args}, @var{description}, @var{code})
+Define a shell function that will be available to the code for each test
+group.  Its name will be @code{ath_fn_@var{name}}, and its body will be
+@var{code}.  (The prefix prevents name conflicts with shell functions
+defined by M4sh and Autotest.)
+
+@var{args} should describe the function's arguments and @var{description}
+what it does; these are used only for documentation comments in the
+generated testsuite script.
+@end defmac
+
 @sp 1
 
 @defmac AT_BANNER (@var{test-category-name})
index f29ce49014ba96fdf57009bf568e89fcfeab76e7..955ed0d9863f204f7313a66c257f9e18a72ea139 100644 (file)
@@ -429,9 +429,6 @@ m4_divert_text([DEFAULTS],
 [
 # Whether to enable colored test results.
 at_color=m4_ifdef([AT_color], [AT_color], [no])
-# List of the tested programs.
-at_tested='m4_ifdef([AT_tested],
-  [m4_translit(m4_dquote(m4_defn([AT_tested])), [ ], m4_newline)])'
 # As many question marks as there are digits in the last test group number.
 # Used to normalize the test group numbers so that `ls' lists them in
 # numerical order.
@@ -992,33 +989,7 @@ AS_BOX(m4_defn([AT_TESTSUITE_NAME])[.])
 } >&AS_MESSAGE_LOG_FD
 
 m4_divert_pop([TESTS_BEGIN])dnl
-m4_divert_push([PREPARE_TESTS])dnl
-{
-  AS_BOX([Tested programs.])
-  echo
-} >&AS_MESSAGE_LOG_FD
-
-# Report what programs are being tested.
-for at_program in : `eval echo $at_tested`
-do
-  test "$at_program" = : && continue
-  case $at_program in
-    [[\\/]* | ?:[\\/]* ) $at_program_=$at_program ;;]
-    * )
-    _AS_PATH_WALK([$PATH], [test -f "$as_dir$at_program" && break])
-    at_program_=$as_dir$at_program ;;
-  esac
-  if test -f "$at_program_"; then
-    {
-      AS_ECHO(["$at_srcdir/AT_LINE: $at_program_ --version"])
-      "$at_program_" --version </dev/null
-      echo
-    } >&AS_MESSAGE_LOG_FD 2>&1
-  else
-    AS_ERROR([cannot find $at_program])
-  fi
-done
-
+m4_divert_push([TESTS])dnl
 {
   AS_BOX([Running the tests.])
 } >&AS_MESSAGE_LOG_FD
@@ -1026,8 +997,6 @@ done
 at_start_date=`date`
 at_start_time=`date +%s 2>/dev/null`
 AS_ECHO(["$as_me: starting at: $at_start_date"]) >&AS_MESSAGE_LOG_FD
-m4_divert_pop([PREPARE_TESTS])dnl
-m4_divert_push([TESTS])dnl
 
 # Create the master directory if it doesn't already exist.
 AS_MKDIR_P(["$at_suite_dir"]) ||
@@ -1794,10 +1763,88 @@ m4_defun([AT_ARG_OPTION_ARG],[_AT_ARG_OPTION([$1],[$2],1,[$3],[$4])])
 # versions are logged, and in the case of embedded test suite, they
 # must correspond to the version of the package.  PATH should be
 # already preset so the proper executable will be selected.
-m4_define([AT_TESTED],
+m4_defun([AT_TESTED],
+[m4_require([_AT_TESTED])]dnl
 [m4_foreach_w([AT_test], [$1],
   [m4_append_uniq([AT_tested], "m4_defn([AT_test])", [ ])])])
 
+m4_defun([_AT_TESTED],
+[m4_wrap([m4_divert_text([DEFAULTS],
+[# List of the tested programs.
+at_tested='m4_translit(m4_dquote(m4_defn([AT_tested])), [ ], m4_newline)'
+])]dnl
+[m4_divert_text([PREPARE_TESTS],
+[{
+  AS_BOX([Tested programs.])
+  echo
+} >&AS_MESSAGE_LOG_FD
+
+# Report what programs are being tested.
+for at_program in : `eval echo $at_tested`
+do
+  AS_CASE([$at_program],
+    [:], [continue],
+    [[[\\/]* | ?:[\\/]*]], [$at_program_=$at_program],
+    [_AS_PATH_WALK([$PATH], [test -f "$as_dir$at_program" && break])
+    at_program_=$as_dir$at_program])
+
+  if test -f "$at_program_"; then
+    {
+      AS_ECHO(["$at_srcdir/AT_LINE: $at_program_ --version"])
+      "$at_program_" --version </dev/null
+      echo
+    } >&AS_MESSAGE_LOG_FD 2>&1
+  else
+    AS_ERROR([cannot find $at_program])
+  fi
+done
+])])])
+
+
+# AT_PREPARE_TESTS(SHELL-CODE)
+# ----------------------------
+# Execute @var{shell-code} in the main testsuite process,
+# after initializing the test suite and processing command-line options,
+# but before running any tests.
+m4_define([AT_PREPARE_TESTS],
+[m4_divert_once([PREPARE_TESTS],
+[m4_text_box([Prepare for this testsuite.])
+])]dnl
+[m4_divert_text([PREPARE_TESTS], [$1])])
+
+
+# AT_PREPARE_EACH_TEST([SHELL-CODE])
+# ----------------------------------
+# Execute @var{shell-code} in each test group's subshell,
+# at the point of the AT_SETUP that starts each test group.
+m4_define([AT_PREPARE_EACH_TEST],
+[m4_append([AT_prepare_each_test], [$1], [
+])])
+
+
+# AT_TEST_HELPER_FN(NAME, ARGS, DESCRIPTION, CODE)
+# ------------------------------------------------
+# Define a shell function that will be available to the code for each test
+# group.  Its name will be ath_fn_NAME, and its body will be CODE.
+#
+# Implementation note: you might think this would use AT_PREPARE_EACH_TEST,
+# but shell functions defined in AT_PREPARE_TESTS *are* (currently) available
+# to test group subshells, and this way the code is only emitted once, not
+# once for each test group.
+m4_define([AT_TEST_HELPER_FN],
+[AS_LITERAL_WORD_IF([$1], [],
+  [m4_fatal([invalid shell function name "$1"])])]dnl
+[m4_ifdef([ATH_fn_$1_defined],
+  [m4_fatal([helper function "$1" defined twice])])]dnl
+[m4_define([ATH_fn_$1_defined])]dnl
+[AT_PREPARE_TESTS([
+AS_FUNCTION_DESCRIBE([ath_fn_$1], [$2], [$3])
+ath_fn_$1 ()
+{
+  $4
+}
+])])
+
 
 # AT_COPYRIGHT(TEXT, [FILTER = m4_newline])
 # -----------------------------------------
@@ -1837,6 +1884,8 @@ at_fn_group_banner AT_ordinal 'm4_defn([AT_line])' \
   "AS_ESCAPE(m4_dquote(m4_defn([AT_description])))" m4_format(["%*s"],
   m4_max(0, m4_eval(47 - m4_qlen(m4_defn([AT_description])))), [])m4_if(
   AT_banner_ordinal, [0], [], [ AT_banner_ordinal])
+m4_ifset([AT_prepare_each_test], [AT_prepare_each_test
+])dnl
 m4_divert_push([TEST_SCRIPT])dnl
 ])
 
index 40acc01341a5add19ada9dc349e66f77a01302be..b5a9c4065a9b2bd3990b331320db0ac6ea3496cd 100644 (file)
@@ -195,6 +195,46 @@ AT_CHECK([echo file2 > file3])
 AT_CHECK([cmp file2 file3])
 ])
 
+## ----------------------------------------------------------- ##
+## AT_PREPARE_TESTS, AT_PREPARE_EACH_TEST, AT_TEST_HELPER_FN.  ##
+## ----------------------------------------------------------- ##
+
+AT_CHECK_AT([AT@&t@_PREPARE_TESTS],
+[[
+AT_INIT([artificial test suite])
+AT_PREPARE_TESTS([FOO=foo; export FOO])
+AT_SETUP([my only test])
+AT_CHECK([test x"$FOO" = xfoo])
+AT_CLEANUP
+]])
+
+AT_CHECK_AT([AT@&t@_PREPARE_EACH_TEST],
+[[
+AT_INIT([artificial test suite])
+AT_PREPARE_EACH_TEST([
+if test -z "$at_test_counter"
+then at_test_counter=1
+else at_test_counter=`expr $at_test_counter + 1`
+fi
+])
+AT_SETUP([test one])
+AT_CHECK([test "$at_test_counter" -eq 1])
+AT_CLEANUP
+AT_SETUP([test two])
+AT_CHECK([test "$at_test_counter" -eq 2])
+AT_CLEANUP
+]])
+
+AT_CHECK_AT([AT@&t@_TEST_HELPER_FN],
+[[
+AT_INIT([artificial test suite])
+AT_TEST_HELPER_FN([helper], [], [], [test x"$][1" = x"$][2"])
+AT_SETUP([my only test])
+AT_CHECK([ath_fn_helper same same])
+AT_CHECK([ath_fn_helper same other], [1])
+AT_CLEANUP
+]])
+
 
 ## ------------------ ##
 ## Empty test suite.  ##
@@ -296,6 +336,32 @@ AT_CHECK_AT_SYNTAX([Multiple AT@&t@_INIT],
 AT_INIT([repeat])
 ]], [AT@&t@_INIT: invoked multiple times])
 
+AT_CHECK_AT_SYNTAX([Invalid AT@&t@_TEST_HELPER_FN (spaces)],
+[[AT_INIT([buggy test suite])
+AT_TEST_HELPER_FN([bad name], [], [], [:])
+AT_SETUP([only test])
+AT_CHECK([:])
+AT_CLEANUP
+]], [invalid shell function name "bad name"])
+
+AT_CHECK_AT_SYNTAX([Invalid AT@&t@_TEST_HELPER_FN (substitutions)],
+[[AT_INIT([buggy test suite])
+AT_TEST_HELPER_FN([variable_${name}], [], [], [:])
+AT_SETUP([only test])
+AT_CHECK([:])
+AT_CLEANUP
+]], [invalid shell function name "variable_${name}"])
+
+AT_CHECK_AT_SYNTAX([Multiple AT@&t@_TEST_HELPER_FN],
+[[AT_INIT([buggy test suite])
+AT_TEST_HELPER_FN([repeated], [], [], [AS_ECHO([repeated 1])])
+# The duplicate check only cares about the name.
+AT_TEST_HELPER_FN([repeated], [args], [desc], [AS_ECHO([repeated 2])])
+AT_SETUP([only test])
+AT_CHECK([:])
+AT_CLEANUP
+]], [helper function "repeated" defined twice])
+
 # Check for tested programs.  autoconf should only appear once.
 AT_CHECK_AT([Tested programs],
 [[AT_INIT([programs test suite])
index 45b5142e5c73d96bdd8906fb298083c989562089..4e29efa28f048e24634cf6e89a9e8eccb0235932 100644 (file)
@@ -600,9 +600,6 @@ AT_CLEANUP
 AT_SETUP([AC_CACHE_CHECK])
 AT_KEYWORDS([CONFIG_SITE])
 
-# Don't let a config.site file affect this test.
-AS_UNSET([CONFIG_SITE])
-
 AT_DATA([configure.ac],
 [[AC_INIT
 # m4_define([ac_nothing], [ac_cv_absolutely_nothing])
@@ -887,9 +884,12 @@ cat <&AS@&t@_ORIGINAL_STDIN_FD >&AS@&t@_MESSAGE_FD
 AC_OUTPUT
 ]])
 AT_CHECK_AUTOCONF
-AT_CHECK([echo Hello | CONFIG_SITE=/dev/null ./configure $configure_options | grep -v '^configure: '],, [Hello
+AT_CHECK([echo Hello |
+  ./configure $configure_options |
+  grep -v '^configure: '],,
+[Hello
 ])
-AT_CHECK([echo Hello | CONFIG_SITE=/dev/null ./configure $configure_options --silent])
+AT_CHECK([echo Hello | ./configure $configure_options --silent])
 
 AT_CLEANUP
 
@@ -976,9 +976,6 @@ AT_CLEANUP
 
 AT_SETUP([configure directories])
 
-CONFIG_SITE=/dev/null
-export CONFIG_SITE
-
 AT_DATA([foo.in],
 [[prefix=@prefix@
 exec_prefix=@exec_prefix@
index 92faed8b93d5b57166dd455a56ed751d52dc4f5f..4847f11102a877836118eaa9230870198daf2dc1 100644 (file)
@@ -175,11 +175,6 @@ AT_CLEANUP
 
 AT_SETUP([AC_LANG_SOURCE example])
 
-# Set CONFIG_SITE to a nonexistent file, so that there are
-# no worries about configure output caused by sourcing a config.site.
-CONFIG_SITE=no-such-file
-export CONFIG_SITE
-
 AT_DATA([configure.ac],
 [[# Taken from autoconf.texi:Generating Sources.
 # The only change is to not fail if gcc doesn't work.
@@ -221,11 +216,6 @@ AT_CLEANUP
 
 AT_SETUP([AC_LANG_PROGRAM example])
 
-# Set CONFIG_SITE to a nonexistent file, so that there are
-# no worries about configure output caused by sourcing a config.site.
-CONFIG_SITE=no-such-file
-export CONFIG_SITE
-
 AT_DATA([configure.ac],
 [[# Taken from autoconf.texi:Generating Sources.
 # The only change is to not fail if gcc doesn't work.
index e32c3d472a575fd117a5b700064330d9dbdab774..a62ad240ebac2e1bfa1cd55a5312abe3ca082797 100644 (file)
@@ -27,6 +27,22 @@ AT_TESTED([autom4te autoconf autoheader autoupdate autoreconf ifnames])
 # Enable colored test output.
 AT_COLOR_TESTS
 
+# Sanitize the environment used for tests.
+AT_PREPARE_TESTS(
+[# These variables should not be inherited from the
+# parent environment.
+AS_UNSET([CONFIG_SITE])
+AS_UNSET([MAKEFLAGS])
+
+# Ensure MAKE is set to a useful value.  Unlike the above, we *do*
+# want to inherit this variable from the parent environment and/or
+# our command line.
+: "${MAKE=make}"
+export MAKE
+])
+
+
+
 ## ---------------- ##
 ## Utility macros.  ##
 ## ---------------- ##
@@ -334,16 +350,15 @@ m4_define([AT_CHECK_CONFIGURE],
 #      | '#'=0
 #      | '$'=6908
 #
-m4_define([AT_CHECK_ENV],
-[m4_divert_once([PREPARE_TESTS], [_AT_CHECK_ENV])dnl
-AT_CHECK([at_check_env])])
-m4_define([_AT_CHECK_ENV],
-[AS_FUNCTION_DESCRIBE([at_check_env], [],
+m4_defun([AT_CHECK_ENV],
+[m4_require([_AT_CHECK_ENV])]dnl
+[AT_CHECK([ath_fn_check_env])])
+
+m4_defun([_AT_CHECK_ENV],
+[AT_TEST_HELPER_FN([check_env], [],
 [Compare the directory and environment state both before and after a run,
-and return non-zero status if they differ inappropriately.])
-at_check_env ()
-{
-# Compare directory listings.
+and return non-zero status if they differ inappropriately.],
+[# Compare directory listings.
 test -f state-ls.before ||
   AS_ERROR([state-ls.before not present])
 test -f state-ls.after \
@@ -377,7 +392,7 @@ if test -f state-env.before && test -f state-env.after; then
       [ALLOCA|GETLOADAVG_LIBS|KMEM_GROUP|NEED_SETGID|POW_LIB],
       [AWK|LEX|LEXLIB|LEX_OUTPUT_ROOT|LN_S|M4|MKDIR_P|RANLIB|SET_MAKE|YACC],
       [GREP|[EF]GREP|SED],
-      [[_@]|.[*#?$].],
+      [[_@]|.[*@%:@?$].],
       [argv|ARGC|LINENO|BASH_ARGC|BASH_ARGV|OLDPWD|PIPESTATUS|RANDOM],
       [SECONDS|START_TIME|ToD|_AST_FEATURES]))=' \
      $act_file ||
@@ -391,7 +406,7 @@ if test -f state-env.before && test -f state-env.after; then
   $at_traceon
   $grep_failed || $at_diff clean-state-env.before clean-state-env.after
 fi
-} [#]at_check_env])
+])])
 
 
 # AT_CONFIG_CMP(VAR-FILE-A, VAR-FILE-B, [EXTRA-VARIANCE])
@@ -625,10 +640,7 @@ m4_define([AT_CHECK_AUTOUPDATE],
 # an acceptable result, because there are situations where BSD make will
 # exit with status 1 but GNU make will instead exit with status 2.
 m4_define([AT_CHECK_MAKE],
-[: "${MAKE=make}"
-export MAKE
-unset MAKEFLAGS
-AT_CHECK(
+[AT_CHECK(
   m4_if(m4_default([$2], [.]), [.], [],
         [cd "$2" && ])[$][MAKE]m4_ifnblank([$1],[ $1])[]m4_if([$3], [1], [[
 dnl pacify editors that don't understand sh case: ((
index 7de210ac9bbd363726d3df259e569df314b010da..98e612aed284e5e64922c624808862f836268a9e 100644 (file)
@@ -125,9 +125,7 @@ exec sh "@S|@@"
 chmod a+x cfg-sh
 
 AT_CAPTURE_FILE([config.log])
-# Export CONFIG_SITE to /dev/null to avoid spurious diffs in expected
-# stdout/stderr.
-AT_CHECK([env CONFIG_SITE=/dev/null CONFIG_SHELL=./cfg-sh ./configure],
+AT_CHECK([env CONFIG_SHELL=./cfg-sh ./configure],
          [0],
 [[configure: creating ./config.status
 ]], [])
index dd9eef9c66c8b3326ad5859f5eca532ba1658d2c..acf04b9418e9708068536f24311fece1528f63f2 100644 (file)
@@ -1463,11 +1463,6 @@ AT_CHECK([[grep '[1-9]\.[0-9]' stdout || exit 77]], [], [ignore])
 # It should understand configure.ac.
 AT_CHECK([[grep '[^0-9]1\.[01234][^0-9]' stdout && exit 77]], [1], [ignore])
 
-# Set CONFIG_SITE to a nonexistent file, so that there are
-# no worries about nonstandard values for 'prefix'.
-CONFIG_SITE=no-such-file
-export CONFIG_SITE
-
 # Prevent aclocal from reading third-party macros, in case they are buggy.
 mkdir empty
 ACLOCAL="aclocal --system-acdir=`cd empty && pwd`"