setup: add gentle version of read_gitfile
[gitweb.git] / t / test-lib.sh
index ca6bdef63d2ee9389729e5118f22461c887dc5ed..39da9c2d9950848cf97686656d80f0f7ad5e3548 100644 (file)
@@ -1,4 +1,4 @@
-#!/bin/sh
+# Test framework for git.  See t/README for usage.
 #
 # Copyright (c) 2005 Junio C Hamano
 #
@@ -26,6 +26,10 @@ then
        # outside of t/, e.g. for running tests on the test library
        # itself.
        TEST_DIRECTORY=$(pwd)
+else
+       # ensure that TEST_DIRECTORY is an absolute path so that it
+       # is valid even if the current working directory is changed
+       TEST_DIRECTORY=$(cd "$TEST_DIRECTORY" && pwd) || exit 1
 fi
 if test -z "$TEST_OUTPUT_DIRECTORY"
 then
@@ -54,8 +58,8 @@ done,*)
        # do not redirect again
        ;;
 *' --tee '*|*' --va'*)
-       mkdir -p test-results
-       BASE=test-results/$(basename "$0" .sh)
+       mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results"
+       BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
        (GIT_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1;
         echo $? > $BASE.exit) | tee $BASE.out
        test "$(cat $BASE.exit)" = 0
@@ -87,11 +91,13 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e '
                VALGRIND
                UNZIP
                PERF_
+               CURL_VERBOSE
        ));
        my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
        print join("\n", @vars);
 ')
 unset XDG_CONFIG_HOME
+unset GITPERLLIB
 GIT_AUTHOR_EMAIL=author@example.com
 GIT_AUTHOR_NAME='A U Thor'
 GIT_COMMITTER_EMAIL=committer@example.com
@@ -103,6 +109,16 @@ export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
 export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
 export EDITOR
 
+# Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output
+GIT_TRACE_BARE=1
+export GIT_TRACE_BARE
+
+if test -n "${TEST_GIT_INDEX_VERSION:+isset}"
+then
+       GIT_INDEX_VERSION="$TEST_GIT_INDEX_VERSION"
+       export GIT_INDEX_VERSION
+fi
+
 # Add libc MALLOC and MALLOC_PERTURB test
 # only if we are not executing the test with valgrind
 if expr " $GIT_TEST_OPTS " : ".* --valgrind " >/dev/null ||
@@ -124,6 +140,9 @@ else
        }
 fi
 
+: ${ASAN_OPTIONS=detect_leaks=0}
+export ASAN_OPTIONS
+
 # Protect ourselves from common misconfiguration to export
 # CDPATH into the environment
 unset CDPATH
@@ -133,10 +152,7 @@ unset UNZIP
 
 case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in
 1|2|true)
-       echo "* warning: Some tests will not work if GIT_TRACE" \
-               "is set as to trace on STDERR ! *"
-       echo "* warning: Please set GIT_TRACE to something" \
-               "other than 1, 2 or true ! *"
+       GIT_TRACE=4
        ;;
 esac
 
@@ -153,7 +169,11 @@ _z40=0000000000000000000000000000000000000000
 LF='
 '
 
-export _x05 _x40 _z40 LF
+# UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores
+# when case-folding filenames
+u200c=$(printf '\342\200\214')
+
+export _x05 _x40 _z40 LF u200c
 
 # Each test should start with something like this, after copyright notices:
 #
@@ -161,16 +181,8 @@ export _x05 _x40 _z40 LF
 # This test checks if command xyzzy does the right thing...
 # '
 # . ./test-lib.sh
-[ "x$ORIGINAL_TERM" != "xdumb" ] && (
-               TERM=$ORIGINAL_TERM &&
-               export TERM &&
-               [ -t 1 ] &&
-               tput bold >/dev/null 2>&1 &&
-               tput setaf 1 >/dev/null 2>&1 &&
-               tput sgr0 >/dev/null 2>&1
-       ) &&
-       color=t
 
+unset color
 while test "$#" -ne 0
 do
        case "$1" in
@@ -180,10 +192,21 @@ do
                immediate=t; shift ;;
        -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
                GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;;
+       -r)
+               shift; test "$#" -ne 0 || {
+                       echo 'error: -r requires an argument' >&2;
+                       exit 1;
+               }
+               run_list=$1; shift ;;
+       --run=*)
+               run_list=$(expr "z$1" : 'z[^=]*=\(.*\)'); shift ;;
        -h|--h|--he|--hel|--help)
                help=t; shift ;;
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
                verbose=t; shift ;;
+       --verbose-only=*)
+               verbose_only=$(expr "z$1" : 'z[^=]*=\(.*\)')
+               shift ;;
        -q|--q|--qu|--qui|--quie|--quiet)
                # Ignore --quiet under a TAP::Harness. Saying how many tests
                # passed without the ok/not ok details is always an error.
@@ -198,50 +221,36 @@ do
        --valgrind=*)
                valgrind=$(expr "z$1" : 'z[^=]*=\(.*\)')
                shift ;;
+       --valgrind-only=*)
+               valgrind_only=$(expr "z$1" : 'z[^=]*=\(.*\)')
+               shift ;;
        --tee)
                shift ;; # was handled already
        --root=*)
                root=$(expr "z$1" : 'z[^=]*=\(.*\)')
                shift ;;
+       --chain-lint)
+               GIT_TEST_CHAIN_LINT=1
+               shift ;;
+       --no-chain-lint)
+               GIT_TEST_CHAIN_LINT=0
+               shift ;;
+       -x)
+               trace=t
+               verbose=t
+               shift ;;
        *)
                echo "error: unknown test option '$1'" >&2; exit 1 ;;
        esac
 done
 
-test -n "$valgrind" && verbose=t
-
-if test -n "$color"
+if test -n "$valgrind_only"
 then
-       say_color () {
-               (
-               TERM=$ORIGINAL_TERM
-               export TERM
-               case "$1" in
-               error)
-                       tput bold; tput setaf 1;; # bold red
-               skip)
-                       tput setaf 4;; # blue
-               warn)
-                       tput setaf 3;; # brown/yellow
-               pass)
-                       tput setaf 2;; # green
-               info)
-                       tput setaf 6;; # cyan
-               *)
-                       test -n "$quiet" && return;;
-               esac
-               shift
-               printf "%s" "$*"
-               tput sgr0
-               echo
-               )
-       }
-else
-       say_color() {
-               test -z "$1" && test -n "$quiet" && return
-               shift
-               printf "%s\n" "$*"
-       }
+       test -z "$valgrind" && valgrind=memcheck
+       test -z "$verbose" && verbose_only="$valgrind_only"
+elif test -n "$valgrind"
+then
+       verbose=t
 fi
 
 error () {
@@ -259,7 +268,7 @@ error "Test script did not set test_description."
 
 if test "$help" = "t"
 then
-       echo "$test_description"
+       printf '%s\n' "$test_description"
        exit 0
 fi
 
@@ -293,13 +302,14 @@ die () {
 
 GIT_EXIT_OK=
 trap 'die' EXIT
+trap 'exit $?' INT
 
 # The user-facing functions are loaded from a separate file so that
 # test_perf subshells can have them too
 . "$TEST_DIRECTORY/test-lib-functions.sh"
 
 # You are not expected to call test_ok_ and test_failure_ directly, use
-# the text_expect_* functions instead.
+# the test_expect_* functions instead.
 
 test_ok_ () {
        test_success=$(($test_success + 1))
@@ -310,7 +320,7 @@ test_failure_ () {
        test_failure=$(($test_failure + 1))
        say_color error "not ok $test_count - $1"
        shift
-       echo "$@" | sed -e 's/^/#       /'
+       printf '%s\n' "$*" | sed -e 's/^/#      /'
        test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; }
 }
 
@@ -328,19 +338,213 @@ test_debug () {
        test "$debug" = "" || eval "$1"
 }
 
+match_pattern_list () {
+       arg="$1"
+       shift
+       test -z "$*" && return 1
+       for pattern_
+       do
+               case "$arg" in
+               $pattern_)
+                       return 0
+               esac
+       done
+       return 1
+}
+
+match_test_selector_list () {
+       title="$1"
+       shift
+       arg="$1"
+       shift
+       test -z "$1" && return 0
+
+       # Both commas and whitespace are accepted as separators.
+       OLDIFS=$IFS
+       IFS='   ,'
+       set -- $1
+       IFS=$OLDIFS
+
+       # If the first selector is negative we include by default.
+       include=
+       case "$1" in
+               !*) include=t ;;
+       esac
+
+       for selector
+       do
+               orig_selector=$selector
+
+               positive=t
+               case "$selector" in
+                       !*)
+                               positive=
+                               selector=${selector##?}
+                               ;;
+               esac
+
+               test -z "$selector" && continue
+
+               case "$selector" in
+                       *-*)
+                               if expr "z${selector%%-*}" : "z[0-9]*[^0-9]" >/dev/null
+                               then
+                                       echo "error: $title: invalid non-numeric in range" \
+                                               "start: '$orig_selector'" >&2
+                                       exit 1
+                               fi
+                               if expr "z${selector#*-}" : "z[0-9]*[^0-9]" >/dev/null
+                               then
+                                       echo "error: $title: invalid non-numeric in range" \
+                                               "end: '$orig_selector'" >&2
+                                       exit 1
+                               fi
+                               ;;
+                       *)
+                               if expr "z$selector" : "z[0-9]*[^0-9]" >/dev/null
+                               then
+                                       echo "error: $title: invalid non-numeric in test" \
+                                               "selector: '$orig_selector'" >&2
+                                       exit 1
+                               fi
+               esac
+
+               # Short cut for "obvious" cases
+               test -z "$include" && test -z "$positive" && continue
+               test -n "$include" && test -n "$positive" && continue
+
+               case "$selector" in
+                       -*)
+                               if test $arg -le ${selector#-}
+                               then
+                                       include=$positive
+                               fi
+                               ;;
+                       *-)
+                               if test $arg -ge ${selector%-}
+                               then
+                                       include=$positive
+                               fi
+                               ;;
+                       *-*)
+                               if test ${selector%%-*} -le $arg \
+                                       && test $arg -le ${selector#*-}
+                               then
+                                       include=$positive
+                               fi
+                               ;;
+                       *)
+                               if test $arg -eq $selector
+                               then
+                                       include=$positive
+                               fi
+                               ;;
+               esac
+       done
+
+       test -n "$include"
+}
+
+maybe_teardown_verbose () {
+       test -z "$verbose_only" && return
+       exec 4>/dev/null 3>/dev/null
+       verbose=
+}
+
+last_verbose=t
+maybe_setup_verbose () {
+       test -z "$verbose_only" && return
+       if match_pattern_list $test_count $verbose_only
+       then
+               exec 4>&2 3>&1
+               # Emit a delimiting blank line when going from
+               # non-verbose to verbose.  Within verbose mode the
+               # delimiter is printed by test_expect_*.  The choice
+               # of the initial $last_verbose is such that before
+               # test 1, we do not print it.
+               test -z "$last_verbose" && echo >&3 ""
+               verbose=t
+       else
+               exec 4>/dev/null 3>/dev/null
+               verbose=
+       fi
+       last_verbose=$verbose
+}
+
+maybe_teardown_valgrind () {
+       test -z "$GIT_VALGRIND" && return
+       GIT_VALGRIND_ENABLED=
+}
+
+maybe_setup_valgrind () {
+       test -z "$GIT_VALGRIND" && return
+       if test -z "$valgrind_only"
+       then
+               GIT_VALGRIND_ENABLED=t
+               return
+       fi
+       GIT_VALGRIND_ENABLED=
+       if match_pattern_list $test_count $valgrind_only
+       then
+               GIT_VALGRIND_ENABLED=t
+       fi
+}
+
+# This is a separate function because some tests use
+# "return" to end a test_expect_success block early
+# (and we want to make sure we run any cleanup like
+# "set +x").
+test_eval_inner_ () {
+       # Do not add anything extra (including LF) after '$*'
+       eval "
+               test \"$trace\" = t && set -x
+               $*"
+}
+
 test_eval_ () {
-       # This is a separate function because some tests use
-       # "return" to end a test_expect_success block early.
-       eval </dev/null >&3 2>&4 "$*"
+       # We run this block with stderr redirected to avoid extra cruft
+       # during a "-x" trace. Once in "set -x" mode, we cannot prevent
+       # the shell from printing the "set +x" to turn it off (nor the saving
+       # of $? before that). But we can make sure that the output goes to
+       # /dev/null.
+       #
+       # The test itself is run with stderr put back to &4 (so either to
+       # /dev/null, or to the original stderr if --verbose was used).
+       {
+               test_eval_inner_ "$@" </dev/null >&3 2>&4
+               test_eval_ret_=$?
+               if test "$trace" = t
+               then
+                       set +x
+                       if test "$test_eval_ret_" != 0
+                       then
+                               say_color error >&4 "error: last command exited with \$?=$test_eval_ret_"
+                       fi
+               fi
+       } 2>/dev/null
+       return $test_eval_ret_
 }
 
 test_run_ () {
        test_cleanup=:
        expecting_failure=$2
+
+       if test "${GIT_TEST_CHAIN_LINT:-1}" != 0; then
+               # 117 is magic because it is unlikely to match the exit
+               # code of other programs
+               test_eval_ "(exit 117) && $1"
+               if test "$?" != 117; then
+                       error "bug in the test script: broken &&-chain: $1"
+               fi
+       fi
+
+       setup_malloc_check
        test_eval_ "$1"
        eval_ret=$?
+       teardown_malloc_check
 
-       if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"
+       if test -z "$immediate" || test $eval_ret = 0 ||
+          test -n "$expecting_failure" && test "$test_cleanup" != ":"
        then
                setup_malloc_check
                test_eval_ "$test_cleanup"
@@ -353,32 +557,49 @@ test_run_ () {
        return "$eval_ret"
 }
 
-test_skip () {
+test_start_ () {
        test_count=$(($test_count+1))
+       maybe_setup_verbose
+       maybe_setup_valgrind
+}
+
+test_finish_ () {
+       echo >&3 ""
+       maybe_teardown_valgrind
+       maybe_teardown_verbose
+}
+
+test_skip () {
        to_skip=
-       for skp in $GIT_SKIP_TESTS
-       do
-               case $this_test.$test_count in
-               $skp)
-                       to_skip=t
-                       break
-               esac
-       done
+       skipped_reason=
+       if match_pattern_list $this_test.$test_count $GIT_SKIP_TESTS
+       then
+               to_skip=t
+               skipped_reason="GIT_SKIP_TESTS"
+       fi
        if test -z "$to_skip" && test -n "$test_prereq" &&
           ! test_have_prereq "$test_prereq"
        then
                to_skip=t
-       fi
-       case "$to_skip" in
-       t)
+
                of_prereq=
                if test "$missing_prereq" != "$test_prereq"
                then
                        of_prereq=" of $test_prereq"
                fi
+               skipped_reason="missing $missing_prereq${of_prereq}"
+       fi
+       if test -z "$to_skip" && test -n "$run_list" &&
+               ! match_test_selector_list '--run' $test_count "$run_list"
+       then
+               to_skip=t
+               skipped_reason="--run"
+       fi
 
+       case "$to_skip" in
+       t)
                say_color skip >&3 "skipping test: $@"
-               say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})"
+               say_color skip "ok $test_count # skip $1 ($skipped_reason)"
                : true
                ;;
        *)
@@ -435,7 +656,7 @@ test_done () {
                then
                        error "Can't use skip_all after running some tests"
                fi
-               [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all"
+               test -z "$skip_all" || skip_all=" # SKIP $skip_all"
 
                if test $test_external_has_tap -eq 0
                then
@@ -489,11 +710,9 @@ then
 
        make_valgrind_symlink () {
                # handle only executables, unless they are shell libraries that
-               # need to be in the exec-path.  We will just use "#!" as a
-               # guess for a shell-script, since we have no idea what the user
-               # may have configured as the shell path.
+               # need to be in the exec-path.
                test -x "$1" ||
-               test "#!" = "$(head -c 2 <"$1")" ||
+               test "# " = "$(head -c 2 <"$1")" ||
                return;
 
                base=$(basename "$1")
@@ -538,6 +757,9 @@ then
        export GIT_VALGRIND
        GIT_VALGRIND_MODE="$valgrind"
        export GIT_VALGRIND_MODE
+       GIT_VALGRIND_ENABLED=t
+       test -n "$valgrind_only" && GIT_VALGRIND_ENABLED=
+       export GIT_VALGRIND_ENABLED
 elif test -n "$GIT_TEST_INSTALLED"
 then
        GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path)  ||
@@ -562,7 +784,6 @@ else # normal case, use ../bin-wrappers only unless $with_dashes:
        fi
 fi
 GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt
-unset GIT_CONFIG
 GIT_CONFIG_NOSYSTEM=1
 GIT_ATTR_NOSYSTEM=1
 export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM
@@ -583,15 +804,6 @@ test -d "$GIT_BUILD_DIR"/templates/blt || {
        error "You haven't built things yet, have you?"
 }
 
-if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON"
-then
-       GITPYTHONLIB="$GIT_BUILD_DIR/git_remote_helpers/build/lib"
-       export GITPYTHONLIB
-       test -d "$GIT_BUILD_DIR"/git_remote_helpers/build || {
-               error "You haven't built git_remote_helpers yet, have you?"
-       }
-fi
-
 if ! test -x "$GIT_BUILD_DIR"/test-chmtime
 then
        echo >&2 'You need to build test-chmtime:'
@@ -614,7 +826,54 @@ rm -fr "$TRASH_DIRECTORY" || {
 }
 
 HOME="$TRASH_DIRECTORY"
-export HOME
+GNUPGHOME="$HOME/gnupg-home-not-used"
+export HOME GNUPGHOME
+
+# run the tput tests *after* changing HOME (in case ncurses needs
+# ~/.terminfo for $TERM)
+test -n "${color+set}" || test "x$ORIGINAL_TERM" != "xdumb" && (
+               TERM=$ORIGINAL_TERM &&
+               export TERM &&
+               test -t 1 &&
+               tput bold >/dev/null 2>&1 &&
+               tput setaf 1 >/dev/null 2>&1 &&
+               tput sgr0 >/dev/null 2>&1
+       ) &&
+       color=t
+
+if test -n "$color"
+then
+       say_color () {
+               (
+               TERM=$ORIGINAL_TERM
+               export TERM
+               case "$1" in
+               error)
+                       tput bold; tput setaf 1;; # bold red
+               skip)
+                       tput setaf 4;; # blue
+               warn)
+                       tput setaf 3;; # brown/yellow
+               pass)
+                       tput setaf 2;; # green
+               info)
+                       tput setaf 6;; # cyan
+               *)
+                       test -n "$quiet" && return;;
+               esac
+               shift
+               printf "%s" "$*"
+               tput sgr0
+               echo
+               )
+       }
+else
+       say_color() {
+               test -z "$1" && test -n "$quiet" && return
+               shift
+               printf "%s\n" "$*"
+       }
+fi
 
 if test -z "$TEST_NO_CREATE_REPO"
 then
@@ -628,15 +887,12 @@ cd -P "$TRASH_DIRECTORY" || exit 1
 
 this_test=${0##*/}
 this_test=${this_test%%-*}
-for skp in $GIT_SKIP_TESTS
-do
-       case "$this_test" in
-       $skp)
-               say_color info >&3 "skipping test $this_test altogether"
-               skip_all="skip all tests in $this_test"
-               test_done
-       esac
-done
+if match_pattern_list "$this_test" $GIT_SKIP_TESTS
+then
+       say_color info >&3 "skipping test $this_test altogether"
+       skip_all="skip all tests in $this_test"
+       test_done
+fi
 
 # Provide an implementation of the 'yes' utility
 yes () {
@@ -674,22 +930,22 @@ case $(uname -s) in
        # backslashes in pathspec are converted to '/'
        # exec does not inherit the PID
        test_set_prereq MINGW
-       test_set_prereq NOT_CYGWIN
+       test_set_prereq NATIVE_CRLF
        test_set_prereq SED_STRIPS_CR
+       test_set_prereq GREP_STRIPS_CR
+       GIT_TEST_CMP=mingw_test_cmp
        ;;
 *CYGWIN*)
        test_set_prereq POSIXPERM
        test_set_prereq EXECKEEPSPID
-       test_set_prereq NOT_MINGW
        test_set_prereq CYGWIN
        test_set_prereq SED_STRIPS_CR
+       test_set_prereq GREP_STRIPS_CR
        ;;
 *)
        test_set_prereq POSIXPERM
        test_set_prereq BSLASHPSPEC
        test_set_prereq EXECKEEPSPID
-       test_set_prereq NOT_MINGW
-       test_set_prereq NOT_CYGWIN
        ;;
 esac
 
@@ -737,7 +993,14 @@ test_i18ngrep () {
 
 test_lazy_prereq PIPE '
        # test whether the filesystem supports FIFOs
-       rm -f testfifo && mkfifo testfifo
+       case $(uname -s) in
+       CYGWIN*)
+               false
+               ;;
+       *)
+               rm -f testfifo && mkfifo testfifo
+               ;;
+       esac
 '
 
 test_lazy_prereq SYMLINKS '
@@ -745,6 +1008,10 @@ test_lazy_prereq SYMLINKS '
        ln -s x y && test -h y
 '
 
+test_lazy_prereq FILEMODE '
+       test "$(git config --bool core.filemode)" = true
+'
+
 test_lazy_prereq CASE_INSENSITIVE_FS '
        echo good >CamelCase &&
        echo bad >camelcase &&
@@ -770,12 +1037,50 @@ test_lazy_prereq AUTOIDENT '
        git var GIT_AUTHOR_IDENT
 '
 
-# When the tests are run as root, permission tests will report that
-# things are writable when they shouldn't be.
-test -w / || test_set_prereq SANITY
+test_lazy_prereq EXPENSIVE '
+       test -n "$GIT_TEST_LONG"
+'
+
+test_lazy_prereq USR_BIN_TIME '
+       test -x /usr/bin/time
+'
+
+test_lazy_prereq NOT_ROOT '
+       uid=$(id -u) &&
+       test "$uid" != 0
+'
+
+# On a filesystem that lacks SANITY, a file can be deleted even if
+# the containing directory doesn't have write permissions, or a file
+# can be accessed even if the containing directory doesn't have read
+# or execute permissions, causing our tests that validate that Git
+# works sensibly in such situations.
+test_lazy_prereq SANITY '
+       mkdir SANETESTD.1 SANETESTD.2 &&
+
+       chmod +w SANETESTD.1 SANETESTD.2 &&
+       >SANETESTD.1/x 2>SANETESTD.2/x &&
+       chmod -w SANETESTD.1 &&
+       chmod -rx SANETESTD.2 ||
+       error "bug in test sript: cannot prepare SANETESTD"
+
+       ! rm SANETESTD.1/x && ! test -f SANETESTD.2/x
+       status=$?
+
+       chmod +rwx SANETESTD.1 SANETESTD.2 &&
+       rm -rf SANETESTD.1 SANETESTD.2 ||
+       error "bug in test sript: cannot clean SANETESTD"
+       return $status
+'
 
 GIT_UNZIP=${GIT_UNZIP:-unzip}
 test_lazy_prereq UNZIP '
        "$GIT_UNZIP" -v
        test $? -ne 127
 '
+
+run_with_limited_cmdline () {
+       (ulimit -s 128 && "$@")
+}
+
+test_lazy_prereq CMDLINE_LIMIT 'run_with_limited_cmdline true'