1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00

tests/leaks.sh: redesign with a more robust testing algorithm

On modern operating systems, memory management is non-deterministic
(i.e. random, unpredictable) to varying degrees. This makes testing
for memory leaks a nightmare as the OS may decide to randomly grow
a process's memory allocation at any time for no apparent reason,
causing intermittent test failures that do not represent real
memory leaks. So far, the leaks test tried to cope with this by
using a large number of iterations plus a certain amount of bytes
of tolerance per iteration. This was inefficient and on some
systems still did not fully eliminate intermittent test failures.

This commit introduces a new testing algorithm that is designed to
cope with a large degree of unpredictability. Instead of a fixed
number of test iterations, it defines a maximum (16384), dividing
them in blocks of 128 iterations. It also defines a minimum number
of sequential "good" iteration blocks, counted if memory usage did
not increase from one block to the next. That minimum number is set
to 16. The theory is that if we can get 16 "good" iteration blocks
in a row, we can safely assume it's not a real memory leak, break
the loop, and consider the test succeeded. That "good" sequence is
allowed to occur at any point in the loop, creating a high built-in
tolerance for non-deterministic shenanigans. It also speeds up the
tests, as successful tests can bow out at 16 * 128 == 2048
iterations if they're lucky. If the OS decides to randomly grow the
memory heap, it may take more tries, but almost (?) certainly not
more than the maximum 16384 (128 blocks). If the counter reaches
that, then we assume a memory leak and throw a test failure.

We're also no longer testing with byte granularity in any case; the
randomness of memory management makes that pointless. All getmem()
function versions now return kibibytes (1024 bytes).

This should eliminate the need for workarounds such as initial
iterations to "steady the state" or a tolerance of a certain number
of bytes. I've experimentally determined the exact values
(max_iter, block_iter, min_good_blocks) that seem to work reliably
on all systems I've tested. They are easy to tweak if necessary.

To make all this manageable, this commit hides all the supporting
code in a triplet of aliases (TEST, DO, DONE) that, when used
correctly, create a grammatically robust shell code block: you can
add redirections, pipe into it, etc. as expected. This makes the
actual tests a great deal easier to read as well.

src/cmd/ksh93/tests/pty.sh:
- Implement new leaks testing framework as described and convert
  all the tests to it.
- Mark known leaks with a 'known' variable. Print non-fail warnings
  for all known leaks, but skip the tests by default. Test them
  only if DEBUG is exported. This is better than commenting them
  out as we will no longer be tempted to forget about these.
- Move the test for large command substitutions to subshell.sh --
  it's not in fact a leak test; instead, it checks that command
  substitutions don't lose data.

src/cmd/ksh93/tests/_common: err_exit():
- Since we're printing more warnings, clearly mark all test
  failures with 'FAIL:' to make them stand out.

src/cmd/ksh93/tests/shtests.
src/cmd/ksh93/tests/pty.sh:
- Special-case leaks.sh for counting tests; grep ^TEST.
- Special-case pty.sh as well while we're at it by grepping tst()
  calls. Remove all the dummy '# err_exit #' comments from pty.sh
  as they are now no longer used for counting the tests.
This commit is contained in:
Martijn Dekker 2021-12-28 17:47:29 +00:00
parent a9c6f77c3e
commit db3a3d8fc0
5 changed files with 259 additions and 369 deletions

View file

@ -21,7 +21,7 @@ _message()
} }
function err_exit function err_exit
{ {
_message "$@" _message "$1" "FAIL:" "${@:2}"
let Errors+=1 let Errors+=1
} }
alias err_exit='err_exit $LINENO' # inaccurate err_exit name kept for historical integrity :) alias err_exit='err_exit $LINENO' # inaccurate err_exit name kept for historical integrity :)

View file

@ -16,6 +16,7 @@
# Florham Park NJ # # Florham Park NJ #
# # # #
# David Korn <dgk@research.att.com> # # David Korn <dgk@research.att.com> #
# Martijn Dekker <martijn@inlv.org> #
# # # #
######################################################################## ########################################################################
@ -27,31 +28,22 @@
if builtin vmstate 2>/dev/null && if builtin vmstate 2>/dev/null &&
n=$(vmstate --format='%(busy_size)u') && n=$(vmstate --format='%(busy_size)u') &&
let "($n) == ($n) && n > 0" # non-zero number? let "($n) == ($n) && n > 0" # non-zero number?
then N=512 # number of iterations for each test then vmalloc=enabled
unit=bytes getmem()
tolerance=$((4*N)) # tolerate 4 bytes per iteration to account for vmalloc artefacts
vmalloc=enabled
function getmem
{ {
vmstate --format='%(busy_size)u' print $(( $(vmstate --format='%(busy_size)u') / 1024 ))
} }
# On Linux, we can use /proc to get byte granularity for vsize (field 23). # On Linux, we can use /proc to get byte granularity for vsize (field 23).
elif [[ -f /proc/$$/stat && $(uname) == Linux ]] elif [[ -f /proc/$$/stat && $(uname) == Linux ]]
then N=4096 # number of iterations for each test then getmem()
unit=bytes
tolerance=$((16*N)) # tolerate 16 bytes per iteration to account for malloc artefacts
function getmem
{ {
cut -f 23 -d ' ' </proc/$$/stat print $(( $(cut -f 23 -d ' ' </proc/$$/stat ) / 1024 ))
} }
# On UnixWare, read the process virtual size with ps # On UnixWare, read the process virtual size with ps
elif [[ $(uname) == UnixWare ]] && elif [[ $(uname) == UnixWare ]] &&
n=$(ps -o vsz= -p "$$" 2>/dev/null) && n=$(ps -o vsz= -p "$$" 2>/dev/null) &&
let "($n) == ($n) && n > 0" let "($n) == ($n) && n > 0"
then N=16384 then getmem()
unit=KiB
tolerance=$((4*N/1024)) # tolerate 4 bytes per iteration to account for malloc artefacts
function getmem
{ {
ps -o vsz= -p "$$" ps -o vsz= -p "$$"
} }
@ -59,10 +51,7 @@ then N=16384
# of the 'ps' command (the standard 'vsz', virtual size, is not usable). # of the 'ps' command (the standard 'vsz', virtual size, is not usable).
elif n=$(ps -o rss= -p "$$" 2>/dev/null) && elif n=$(ps -o rss= -p "$$" 2>/dev/null) &&
let "($n) == ($n) && n > 0" let "($n) == ($n) && n > 0"
then N=16384 then getmem()
unit=KiB
tolerance=$((12*N/1024)) # tolerate 12 bytes per iteration to account for malloc/ps artefacts
function getmem
{ {
ps -o rss= -p "$$" ps -o rss= -p "$$"
} }
@ -70,79 +59,104 @@ else warning 'cannot find method to measure memory usage; skipping tests'
exit 0 exit 0
fi fi
# test for variable reset leak # # Parameters for test blocks.
# Intended to cope with all non-deterministic OS memory management artefacts.
#
# Theory: if we can get a sequence of $min_good_blocks blocks of $block_iter iterations without the memory state changing,
# then we can safely assume it's not a memory leak, break the loop, and consider the test succeeded. To allow for
# unpredictable OS memory management artefacts, that sequence is allowed to occur anywhere within $max_iter iterations.
# This speeds up the tests, as successful tests can bow out at $((min_good_blocks * block_iter)) iterations if they're
# lucky. If the OS decides to randomly grow the memory heap, it may take more tries, but hopefully not more than
# $max_iter iterations. If the loop counter reaches $max_iter, then we assume a memory leak and throw a test failure.
function test_reset typeset -ir max_iter=16384 block_iter=128 min_good_blocks=16
{
integer i N=$1
for ((i = 0; i < N; i++)) # Set up test block construct.
do u=$i # Known leaks can be marked known=y to turn them into non-fail warnings.
done #
} # Usage:
# TEST title='description' [ known=y [ url=<issue tracker URL> ] ]
# <optional preparatory commands>
# DO
# <test payload commands>
# DONE
#
# To avoid messing up $LINENO, aliases should not contain newline characters.
# To keep things readable, backslash line continuation is used instead.
# Initialise variables used below to avoid false leaks typeset .lt # create lt (leak test) namespace for internal variables
before=0 after=0 i=0 u=0 typeset -i .lt.before=0 .lt.after=0 .lt.total=0 .lt.good=0 .lt.i=0 .lt.j=0
alias TEST=\
'for .lt.i in 1; do '\
' unset -v known url; '
# optional preparatory commands go here
alias DO=\
' if [[ -v known && ! -v DEBUG ]]; '\
' then warning "skipping test for known leak \"$title\";" '\
' "export DEBUG=y to test" ${url:+"and help us fix it at: $url"}; '\
' break; '\
' fi; '\
' .lt.before=$(getmem) .lt.good=0 .lt.total=0; '\
' for ((.lt.i = 0; .lt.i < max_iter; .lt.i += block_iter)); '\
' do for ((.lt.j = 0; .lt.j < block_iter; .lt.j++)); '\
' do '
# test payload commands go here
alias DONE=\
' done; '\
' .lt.after=$(getmem); '\
' if ((.lt.after <= .lt.before)); '\
' then ((.lt.good++ == min_good_blocks)) && break; '\
' else ((.lt.good = 0)); '\
' ((.lt.total += (.lt.after - .lt.before))); '\
' .lt.before=$(getmem); '\
' fi; '\
' done; '\
' if ((.lt.i >= max_iter)); '\
' then if [[ -v known ]]; '\
' then err_exit "known leak: $title: leaked approx ${.lt.total} KiB after ${.lt.i} iterations" '\
' ${url:+"-- help us fix it at: $url"}; '\
' else err_exit "$title: leaked approx ${.lt.total} KiB after ${.lt.i} iterations"; '\
' fi; '\
' elif [[ -v known ]]; '\
' then warning "did not detect known leak \"$title\": succeeded after ${.lt.i} iterations)" ${url:+"-- see: $url"}; '\
' elif [[ -v DEBUG ]]; '\
' then _message "$LINENO" "[DEBUG] test \"$title\" succeeded after ${.lt.i} iterations"; '\
' fi; '\
'done'
# ____ Begin memory leak tests ____
# Check results. TEST title='variable value reset'
# The function has 'err_exit' in the name so that shtests counts each call as a test. integer i=0
function err_exit_if_leak u=foo
{ DO
if ((after > before + tolerance)) u=$((++i))
then err\_exit "$1" "$2 (leaked approx $((after - before)) $unit after $N iterations)" DONE
fi
}
alias err_exit_if_leak='err_exit_if_leak "$LINENO"'
# one round to get to steady state -- sensitive to -x
test_reset $N
test_reset $N
before=$(getmem)
test_reset $N
after=$(getmem)
err_exit_if_leak "variable value reset memory leak"
# buffer boundary tests
for exp in 65535 65536
do got=$($SHELL -c 'x=$(printf "%.*c" '$exp' x); print ${#x}' 2>&1)
[[ $got == $exp ]] || err_exit "large command substitution failed -- expected $exp, got $got"
done
# data for the next two tests...
data="(v=;sid=;di=;hi=;ti='1328244300';lv='o';id='172.3.161.178';var=(k='conn_num._total';u=;fr=;l='Number of Connections';n='22';t='number';))" data="(v=;sid=;di=;hi=;ti='1328244300';lv='o';id='172.3.161.178';var=(k='conn_num._total';u=;fr=;l='Number of Connections';n='22';t='number';))"
read -C stat <<< "$data" read -C stat <<< "$data"
for ((i=0; i < 8; i++)) # steady state first
do print -r -- "$data" | while read -u$n -C stat; do :; done {n}<&0-
done
before=$(getmem)
for ((i=0; i < N; i++))
do print -r -- "$data"
done | while read -u$n -C stat
do :
done {n}<&0-
after=$(getmem)
err_exit_if_leak "memory leak with read -C when deleting compound variable"
# extra 'read's to get to steady state while :
for ((i=0; i < 10; i++)) do print -r -- "$data"
do read -C stat <<< "$data" done | \
done TEST title='read -C when deleting compound variable'
before=$(getmem) DO read -u$n -C stat
for ((i=0; i < N; i++)) DONE {n}<&0-
do read -C stat <<< "$data"
done TEST title='read -C when using <<<'
after=$(getmem) DO
err_exit_if_leak "memory leak with read -C when using <<<" read -C stat <<< "$data"
DONE
unset data stat
# ====== # ======
# Unsetting an associative array shouldn't cause a memory leak # Unsetting an associative array shouldn't cause a memory leak
# See https://www.mail-archive.com/ast-users@lists.research.att.com/msg01016.html # See https://www.mail-archive.com/ast-users@lists.research.att.com/msg01016.html
typeset -A stuff TEST title='unset of associative array'
before=$(getmem) typeset -A stuff
for (( i=0; i < N; i++ )) DO
do
unset stuff[xyz] unset stuff[xyz]
typeset -A stuff[xyz] typeset -A stuff[xyz]
stuff[xyz][elem0]="data0" stuff[xyz][elem0]="data0"
@ -150,106 +164,82 @@ do
stuff[xyz][elem2]="data2" stuff[xyz][elem2]="data2"
stuff[xyz][elem3]="data3" stuff[xyz][elem3]="data3"
stuff[xyz][elem4]="data4" stuff[xyz][elem4]="data4"
done DONE
unset stuff
after=$(getmem) # https://github.com/ksh93/ksh/issues/94
err_exit_if_leak 'unset of associative array causes memory leak' TEST title='defining associative array in subshell' known=y url=https://github.com/ksh93/ksh/issues/94
DO
(typeset -A foo=([a]=1 [b]=2 [c]=3))
DONE
# ====== # ======
# Memory leak when resetting PATH and clearing hash table # Memory leak when resetting PATH and clearing hash table
# ...steady memory state:
command -v ls >/dev/null # add something to hash table
PATH=/dev/null true # set/restore PATH & clear hash table
# ...test for leak: # ...test for leak:
before=$(getmem) TEST title='PATH reset before PATH search'
for ((i=0; i < N; i++)) DO
do PATH=/dev/null true # set/restore PATH & clear hash table PATH=/dev/null true # set/restore PATH & clear hash table
command -v ls # do PATH search, add to hash table command -v ls # do PATH search, add to hash table
done >/dev/null DONE >/dev/null
after=$(getmem)
err_exit_if_leak 'memory leak on PATH reset before PATH search'
# ...test for another leak that only shows up when building with nmake: # ...test for another leak that only shows up when building with nmake:
before=$(getmem) TEST title='PATH reset'
for ((i=0; i < N; i++)) DO
do PATH=/dev/null true # set/restore PATH & clear hash table PATH=/dev/null true # set/restore PATH & clear hash table
done >/dev/null DONE >/dev/null
after=$(getmem)
err_exit_if_leak 'memory leak on PATH reset'
# ====== # ======
# Defining a function in a virtual subshell # Defining a function in a virtual subshell
# https://github.com/ksh93/ksh/issues/114 # https://github.com/ksh93/ksh/issues/114
unset -f foo TEST title='ksh function defined in virtual subshell'
before=$(getmem) unset -f foo
for ((i=0; i < N; i++)) DO
do (function foo { :; }; foo) (function foo { :; }; foo)
done DONE
after=$(getmem)
err_exit_if_leak 'ksh function defined in virtual subshell'
typeset -f foo >/dev/null && err_exit 'ksh function leaks out of subshell'
unset -f foo TEST title='POSIX function defined in virtual subshell'
before=$(getmem) unset -f foo
for ((i=0; i < N; i++)) DO
do (foo() { :; }; foo) (foo() { :; }; foo)
done DONE
after=$(getmem)
err_exit_if_leak 'POSIX function defined in virtual subshell'
typeset -f foo >/dev/null && err_exit 'POSIX function leaks out of subshell'
# Unsetting a function in a virtual subshell # Unsetting a function in a virtual subshell
function foo { echo bar; } TEST title='ksh function unset in virtual subshell'
before=$(getmem) function foo { echo bar; }
for ((i=0; i < N; i++)) DO
do (unset -f foo) (unset -f foo)
done DONE
after=$(getmem)
err_exit_if_leak 'ksh function unset in virtual subshell'
typeset -f foo >/dev/null || err_exit 'ksh function unset in subshell was unset in main shell'
foo() { echo bar; } TEST title='POSIX function unset in virtual subshell'
before=$(getmem) foo() { echo bar; }
for ((i=0; i < N; i++)) DO
do (unset -f foo) (unset -f foo)
done DONE
after=$(getmem)
err_exit_if_leak 'POSIX function unset in virtual subshell'
typeset -f foo >/dev/null || err_exit 'POSIX function unset in subshell was unset in main shell'
before=$(getmem) TEST title='ksh function defined and unset in virtual subshell'
for ((i=0; i < N; i++)) DO
do (function foo { echo baz; }; unset -f foo) (function foo { echo baz; }; unset -f foo)
done DONE
after=$(getmem)
err_exit_if_leak 'ksh function defined and unset in virtual subshell'
before=$(getmem) TEST title='POSIX function defined and unset in virtual subshell'
for ((i=0; i < N; i++)) DO
do (foo() { echo baz; }; unset -f foo) (foo() { echo baz; }; unset -f foo)
done DONE
after=$(getmem)
err_exit_if_leak 'POSIX function defined and unset in virtual subshell'
# ====== # ======
# Sourcing a dot script in a virtual subshell # Sourcing a dot script in a virtual subshell
echo 'echo "$@"' > $tmp/dot.sh TEST title='script dotted in virtual subshell'
before=$(getmem) echo 'echo "$@"' > $tmp/dot.sh
for ((i=0; i < N; i++)) DO
do (. "$tmp/dot.sh" dot one two three >/dev/null) (. "$tmp/dot.sh" dot one two three >/dev/null)
done DONE
after=$(getmem)
err_exit_if_leak 'script dotted in virtual subshell'
echo 'echo "$@"' > $tmp/dot.sh TEST title='script sourced in virtual subshell'
before=$(getmem) echo 'echo "$@"' > $tmp/dot.sh
for ((i=0; i < N; i++)) DO
do (source "$tmp/dot.sh" source four five six >/dev/null) (source "$tmp/dot.sh" source four five six >/dev/null)
done DONE
after=$(getmem)
err_exit_if_leak 'script sourced in virtual subshell'
# ====== # ======
# Multiple leaks when using arrays in functions (Red Hat #921455) # Multiple leaks when using arrays in functions (Red Hat #921455)
@ -259,31 +249,27 @@ err_exit_if_leak 'script sourced in virtual subshell'
# after the patch) when run in a non-C locale. # after the patch) when run in a non-C locale.
[[ $vmalloc == enabled ]] && saveLANG=$LANG && LANG=C # comment out to test remaining leak (1/2) [[ $vmalloc == enabled ]] && saveLANG=$LANG && LANG=C # comment out to test remaining leak (1/2)
function _hash TEST title='associative array in function'
{ function _hash
typeset w=([abc]=1 [def]=31534 [xyz]=42) {
print -u2 $w 2>&- typeset w=([abc]=1 [def]=31534 [xyz]=42)
# accessing the var will leak print -u2 $w 2>&-
} # accessing the var will leak
before=$(getmem) }
for ((i=0; i < N; i++)) DO
do _hash _hash
done DONE
after=$(getmem)
err_exit_if_leak 'associative array in function'
function _array TEST title='indexed array in function'
{ function _array
typeset w=(1 31534 42) {
print -u2 $w 2>&- typeset w=(1 31534 42)
# unset w will prevent leak print -u2 $w 2>&-
} # unset w will prevent leak
before=$(getmem) }
for ((i=0; i < N; i++)) DO
do _array _array
done DONE
after=$(getmem)
err_exit_if_leak 'indexed array in function'
[[ $vmalloc == enabled ]] && LANG=$saveLANG # comment out to test remaining leak (2/2) [[ $vmalloc == enabled ]] && LANG=$saveLANG # comment out to test remaining leak (2/2)
@ -292,175 +278,126 @@ err_exit_if_leak 'indexed array in function'
# Fix based on: https://src.fedoraproject.org/rpms/ksh/blob/642af4d6/f/ksh-20120801-memlik3.patch # Fix based on: https://src.fedoraproject.org/rpms/ksh/blob/642af4d6/f/ksh-20120801-memlik3.patch
# The fix was backported from ksh 93v- beta. # The fix was backported from ksh 93v- beta.
function myFunction TEST title='typeset in function called by command substitution'
{ function myFunction
typeset toPrint="something" {
echo "${toPrint}" typeset toPrint="something"
} echo "${toPrint}"
state=$(myFunction) }
before=$(getmem) DO
for ((i=0; i < N; i++)) state=$(myFunction)
do state=$(myFunction) DONE
done
after=$(getmem)
err_exit_if_leak 'typeset in function called by command substitution'
# ====== # ======
# Check that unsetting an alias frees both the node and its value # Check that unsetting an alias frees both the node and its value
before=$(getmem) TEST title='unalias'
for ((i=0; i < N; i++)) DO
do alias "test$i=command$i" alias "test$i=command$i"
unalias "test$i" unalias "test$i"
done DONE
after=$(getmem)
err_exit_if_leak 'unalias'
# ====== # ======
# Red Hat bug rhbz#982142: command substitution leaks # Red Hat bug rhbz#982142: command substitution leaks
# case1: Nested command substitutions # case1: Nested command substitutions
# (reportedly already fixed in 93u+, but let's keep the test) # (reportedly already fixed in 93u+, but let's keep the test)
before=$(getmem) TEST title='nested command substitutions'
for ((i=0; i < N; i++)) DO
do a=`true 1 + \`true 1 + 1\`` # was: a=`expr 1 + \`expr 1 + 1\`` a=`true 1 + \`true 1 + 1\`` # was: a=`expr 1 + \`expr 1 + 1\``
done DONE
after=$(getmem)
err_exit_if_leak 'nested command substitutions'
# case2: Command alias # case2: Command alias
alias ls='true -ltr' # was: alias ls='ls -ltr' TEST title='alias in command substitution'
before=$(getmem) alias ls='true -ltr' # was: alias ls='ls -ltr'
for ((i=0; i < N; i++)) DO
do eval 'a=`ls`' eval 'a=`ls`'
done DONE
after=$(getmem)
unalias ls
err_exit_if_leak 'alias in command substitution'
# case3: Function call via autoload # case3: Function call via autoload
cat >$tmp/func1 <<\EOF TEST title='function call via autoload in command substitution'
function func1 cat >$tmp/func1 <<-\EOF
{ function func1
echo "func1 call"; {
} echo "func1 call";
EOF }
FPATH=$tmp EOF
autoload func1 FPATH=$tmp
a=`func1` # steady memory state autoload func1
before=$(getmem) DO
for ((i=0; i < N; i++)) a=`func1`
do a=`func1` DONE
done
after=$(getmem)
unset -f func1
unset -v FPATH
err_exit_if_leak 'function call via autoload in command substitution'
# ====== # ======
# add some random utilities to the hash table to detect memory leak on hash table reset when changing PATH # add some random utilities to the hash table to detect memory leak on hash table reset when changing PATH
random_utils=(chmod cp mv awk sed diff comm cut sort uniq date env find mkdir rmdir pr sleep) random_utils=(chmod cp mv awk sed diff comm cut sort uniq date env find mkdir rmdir pr sleep)
save_PATH=$PATH save_PATH=$PATH
hash "${random_utils[@]}"
before=$(getmem)
for ((i=0; i < N; i++))
do hash -r
hash "${random_utils[@]}"
done
after=$(getmem)
err_exit_if_leak 'clear hash table (hash -r) in main shell'
before=$(getmem) TEST title='clear hash table (hash -r) in main shell'
for ((i=0; i < N; i++)) DO
do PATH=/dev/null hash -r
hash "${random_utils[@]}"
DONE
TEST title='set PATH value in main shell'
DO
PATH=/dev/null
PATH=$save_PATH PATH=$save_PATH
hash "${random_utils[@]}" hash "${random_utils[@]}"
done DONE
after=$(getmem)
err_exit_if_leak 'set PATH value in main shell'
before=$(getmem) TEST title='run command with preceding PATH assignment in main shell'
for ((i=0; i < N; i++)) DO
do PATH=/dev/null command true PATH=/dev/null command true
done DONE
after=$(getmem)
err_exit_if_leak 'run command with preceding PATH assignment in main shell'
: <<'disabled' # TODO: known leak (approx 73552 bytes after 512 iterations) TEST title='set PATH attribute in main shell' known=y url=https://github.com/ksh93/ksh/issues/405
before=$(getmem) DO
for ((i=0; i < N; i++)) typeset -A PATH
do typeset -A PATH
unset PATH unset PATH
PATH=$save_PATH PATH=$save_PATH
hash "${random_utils[@]}" hash "${random_utils[@]}"
done DONE
after=$(getmem)
err_exit_if_leak 'set PATH attribute in main shell'
disabled
: <<'disabled' # TODO: known leak (approx 99568 bytes after 512 iterations) TEST title='unset PATH in main shell' known=y url=https://github.com/ksh93/ksh/issues/405
before=$(getmem) DO
for ((i=0; i < N; i++)) unset PATH
do unset PATH
PATH=$save_PATH PATH=$save_PATH
hash "${random_utils[@]}" hash "${random_utils[@]}"
done DONE
after=$(getmem)
err_exit_if_leak 'unset PATH in main shell'
disabled
hash "${random_utils[@]}" TEST title='clear hash table (hash -r) in subshell'
before=$(getmem) hash "${random_utils[@]}"
for ((i=0; i < N; i++)) DO
do (hash -r) (hash -r)
done DONE
after=$(getmem)
err_exit_if_leak 'clear hash table (hash -r) in subshell'
: <<'disabled' # TODO: known leak (approx 123520 bytes after 512 iterations) TEST title='set PATH value in subshell' known=y url=https://github.com/ksh93/ksh/issues/405
before=$(getmem) DO
for ((i=0; i < N; i++)) (PATH=/dev/null)
do (PATH=/dev/null) DONE
done
after=$(getmem)
err_exit_if_leak 'set PATH value in subshell'
disabled
: <<'disabled' # TODO: known leak (approx 24544 bytes after 512 iterations) TEST title='run command with preceding PATH assignment in subshell' known=y url=https://github.com/ksh93/ksh/issues/405
before=$(getmem) DO
for ((i=0; i < N; i++)) (PATH=/dev/null command true)
do (PATH=/dev/null command true) DONE
done
after=$(getmem)
err_exit_if_leak 'run command with preceding PATH assignment in subshell'
disabled
: <<'disabled' # TODO: known leak (approx 131200 bytes after 512 iterations) TEST title='set PATH attribute in subshell' known=y url=https://github.com/ksh93/ksh/issues/405
before=$(getmem) DO
for ((i=0; i < N; i++)) (readonly PATH)
do (readonly PATH) DONE
done
after=$(getmem)
err_exit_if_leak 'set PATH attribute in subshell'
disabled
: <<'disabled' # TODO: known leak (approx 229440 bytes after 512 iterations) TEST title='unset PATH in subshell' known=y url=https://github.com/ksh93/ksh/issues/405
before=$(getmem) DO
for ((i=0; i < N; i++)) (unset PATH)
do (unset PATH) DONE
done
after=$(getmem)
err_exit_if_leak 'unset PATH in subshell'
disabled
# ====== # ======
# Test for a memory leak after 'cd' (in relation to $PWD and $OLDPWD) # Test for a memory leak after 'cd' (in relation to $PWD and $OLDPWD)
original_pwd=$PWD TEST title='PWD and/or OLDPWD changed by cd'
before=$(getmem) DO
for ((i=0; i < N; i++)) cd /tmp
do cd /tmp
cd - > /dev/null cd - > /dev/null
PWD=/foo PWD=/foo
OLDPWD=/bar OLDPWD=/bar
@ -471,23 +408,14 @@ do cd /tmp
cd - > /dev/null cd - > /dev/null
unset OLDPWD PWD unset OLDPWD PWD
cd /bin cd /bin
cd /tmp cd "$tmp"
done DONE
after=$(getmem)
err_exit_if_leak 'PWD and/or OLDPWD changed by cd'
cd $original_pwd
# ====== # ======
# https://github.com/ksh93/ksh/issues/253#issuecomment-815308466 TEST title='variable with discipline function in subshell' known=y url=https://github.com/ksh93/ksh/issues/404
: <<'disabled' # TODO: known leak(s) (approx 1008 KiB after 16384 iterations) DO
before=$(getmem)
for ((i=0; i < N; i++))
do
(SECONDS=1; LANG=C) (SECONDS=1; LANG=C)
done DONE
after=$(getmem)
err_exit_if_leak 'Variable with discipline function in subshell causes memory leak'
disabled
# ====== # ======
exit $((Errors<125?Errors:125)) exit $((Errors<125?Errors:125))

View file

@ -27,8 +27,6 @@
# read the pty manual by running: arch/*/bin/pty --man # read the pty manual by running: arch/*/bin/pty --man
# #
# Do not globally set the locale; these tests must pass for all locales. # Do not globally set the locale; these tests must pass for all locales.
#
# The # err_exit # comments are to enable shtests to count the tests.
# the trickiest part of the tests is avoiding typeahead # the trickiest part of the tests is avoiding typeahead
# in the pty dialogue # in the pty dialogue
@ -101,7 +99,6 @@ then warning "pty command hangs on $bintrue -- tests skipped"
exit 0 exit 0
fi fi
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L POSIX sh 026(C) L POSIX sh 026(C)
@ -125,7 +122,6 @@ w wait
u (Killed|Done) u (Killed|Done)
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L POSIX sh 028(C) L POSIX sh 028(C)
@ -148,7 +144,6 @@ w wait
u (Killed|Done) u (Killed|Done)
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L POSIX sh 029(C) L POSIX sh 029(C)
@ -171,7 +166,6 @@ w wait
u (Killed|Done) u (Killed|Done)
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L POSIX sh 091(C) L POSIX sh 091(C)
@ -188,7 +182,6 @@ w o
u ^hello\r?\n$ u ^hello\r?\n$
! !
# err_exit #
((SHOPT_VSH)) && tst $LINENO <<"!" ((SHOPT_VSH)) && tst $LINENO <<"!"
L POSIX sh 093(C) L POSIX sh 093(C)
@ -204,7 +197,6 @@ w e
u ^goodbye\r?\n$ u ^goodbye\r?\n$
! !
# err_exit #
((SHOPT_VSH)) && tst $LINENO <<"!" ((SHOPT_VSH)) && tst $LINENO <<"!"
L POSIX sh 094(C) L POSIX sh 094(C)
@ -221,7 +213,6 @@ u ^hello\r?\n$
if [[ $(id -u) == 0 ]] if [[ $(id -u) == 0 ]]
then warning "running as root: skipping test POSIX sh 096(C)" then warning "running as root: skipping test POSIX sh 096(C)"
else else
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L POSIX sh 096(C) L POSIX sh 096(C)
@ -251,7 +242,6 @@ r history
! !
fi fi
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L POSIX sh 097(C) L POSIX sh 097(C)
@ -267,7 +257,6 @@ u ^ok\r?\n$
if [[ $(id -u) == 0 ]] if [[ $(id -u) == 0 ]]
then warning "running as root: skipping test POSIX sh 099(C)" then warning "running as root: skipping test POSIX sh 099(C)"
else else
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L POSIX sh 099(C) L POSIX sh 099(C)
@ -297,7 +286,6 @@ r history
! !
fi fi
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L POSIX sh 100(C) L POSIX sh 100(C)
@ -313,7 +301,6 @@ w echo ok
u ^ok\r?\n$ u ^ok\r?\n$
! !
# err_exit #
((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!" ((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!"
L POSIX sh 101(C) L POSIX sh 101(C)
@ -358,7 +345,6 @@ w echo interrupt=:\cV\cC:
u ^interrupt=:\cC:\r?\n$ u ^interrupt=:\cC:\r?\n$
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L POSIX sh 104(C) L POSIX sh 104(C)
@ -378,7 +364,6 @@ u ^done\r?\n$
if [[ $(id -u) == 0 ]] if [[ $(id -u) == 0 ]]
then warning "running as root: skipping test POSIX sh 111(C)" then warning "running as root: skipping test POSIX sh 111(C)"
else else
# err_exit #
((SHOPT_VSH)) && tst $LINENO <<"!" ((SHOPT_VSH)) && tst $LINENO <<"!"
L POSIX sh 111(C) L POSIX sh 111(C)
@ -402,7 +387,6 @@ fi
if [[ $(id -u) == 0 ]] if [[ $(id -u) == 0 ]]
then warning "running as root: skipping test POSIX sh 251(C)" then warning "running as root: skipping test POSIX sh 251(C)"
else else
# err_exit #
((SHOPT_VSH)) && tst $LINENO <<"!" ((SHOPT_VSH)) && tst $LINENO <<"!"
L POSIX sh 251(C) L POSIX sh 251(C)
@ -461,7 +445,6 @@ u yes-yes
! !
disabled disabled
# err_exit #
# Test file name completion in vi mode # Test file name completion in vi mode
if((SHOPT_VSH)); then if((SHOPT_VSH)); then
mkdir "/tmp/fakehome_$$" && tst $LINENO <<! mkdir "/tmp/fakehome_$$" && tst $LINENO <<!
@ -478,7 +461,6 @@ u ^/tmp/fakehome_$$/testfile_$$\r?\n$
rm -r "/tmp/fakehome_$$" rm -r "/tmp/fakehome_$$"
fi # SHOPT_VSH fi # SHOPT_VSH
# err_exit #
VISUAL='' tst $LINENO <<"!" VISUAL='' tst $LINENO <<"!"
L raw Bourne mode literal tab characters L raw Bourne mode literal tab characters
@ -494,7 +476,6 @@ r ^:test-1: true (/de\tv/nu\tl\tl|/de v/nu l l)\r\n$
p :test-2: p :test-2:
! !
# err_exit #
VISUAL='' tst $LINENO <<"!" VISUAL='' tst $LINENO <<"!"
L raw Bourne mode backslash handling L raw Bourne mode backslash handling
@ -511,8 +492,6 @@ w true incorrect\\\cXtrue correct
r ^:test-3: true correct\r\n$ r ^:test-3: true correct\r\n$
! !
# err_exit #
# err_exit #
set -- set --
((SHOPT_VSH)) && set -- "$@" vi ((SHOPT_VSH)) && set -- "$@" vi
((SHOPT_ESH)) && set -- "$@" emacs gmacs ((SHOPT_ESH)) && set -- "$@" emacs gmacs
@ -532,7 +511,6 @@ r ^:test-2: true string\\r\\n$
! !
done done
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L notify job state changes L notify job state changes
@ -543,7 +521,6 @@ w set -b; sleep .01 &
u Done u Done
! !
# err_exit #
# Tests for 'test -t'. These were moved here from bracket.sh because they require a tty. # Tests for 'test -t'. These were moved here from bracket.sh because they require a tty.
cat >test_t.sh <<"EOF" cat >test_t.sh <<"EOF"
integer n integer n
@ -604,7 +581,6 @@ r ^OK11\r\n$
r ^:test-2: r ^:test-2:
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L race condition while launching external commands L race condition while launching external commands
@ -623,7 +599,6 @@ r ^/dev/null\r\n$
r ^:test-2: r ^:test-2:
! !
# err_exit #
((SHOPT_ESH)) && [[ -o ?backslashctrl ]] && tst $LINENO <<"!" ((SHOPT_ESH)) && [[ -o ?backslashctrl ]] && tst $LINENO <<"!"
L nobackslashctrl in emacs L nobackslashctrl in emacs
@ -637,7 +612,6 @@ w \cR\\\cH\cH
r ^:test-2: \r\n$ r ^:test-2: \r\n$
! !
# err_exit #
((SHOPT_ESH)) && tst $LINENO <<"!" ((SHOPT_ESH)) && tst $LINENO <<"!"
L emacs backslash escaping L emacs backslash escaping
@ -655,7 +629,6 @@ w true \\\cC
r true \^C r true \^C
! !
# err_exit #
((SHOPT_VSH)) && touch vi_completion_A_file vi_completion_B_file && tst $LINENO <<"!" ((SHOPT_VSH)) && touch vi_completion_A_file vi_completion_B_file && tst $LINENO <<"!"
L vi filename completion menu L vi filename completion menu
@ -690,7 +663,6 @@ r ^:test-3: ls vi_completion_A_file\r\n$
r ^vi_completion_A_file\r\n$ r ^vi_completion_A_file\r\n$
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L syntax error added to history file L syntax error added to history file
@ -706,7 +678,6 @@ r ^:test-2: fc -lN1\r\n$
r \tdo something\r\n$ r \tdo something\r\n$
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L value of $? after the shell uses a variable with a discipline function L value of $? after the shell uses a variable with a discipline function
@ -730,7 +701,6 @@ w echo "Exit status is: $?"
u Exit status is: 1 u Exit status is: 1
! !
# err_exit #
((SHOPT_ESH)) && ((SHOPT_VSH)) && tst $LINENO <<"!" ((SHOPT_ESH)) && ((SHOPT_VSH)) && tst $LINENO <<"!"
L crash after switching from emacs to vi mode L crash after switching from emacs to vi mode
@ -751,7 +721,6 @@ r ^:test-2: echo Success\r\n$
r ^Success\r\n$ r ^Success\r\n$
! !
# err_exit #
((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!" ((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!"
L value of $? after tilde expansion in tab completion L value of $? after tilde expansion in tab completion
@ -769,7 +738,6 @@ w echo $? ~\t
u 42 /tmp u 42 /tmp
! !
# err_exit #
((SHOPT_MULTIBYTE && (SHOPT_VSH || SHOPT_ESH))) && ((SHOPT_MULTIBYTE && (SHOPT_VSH || SHOPT_ESH))) &&
[[ ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} =~ [Uu][Tt][Ff]-?8 ]] && [[ ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} =~ [Uu][Tt][Ff]-?8 ]] &&
touch $'XXX\xc3\xa1' $'XXX\xc3\xab' && touch $'XXX\xc3\xa1' $'XXX\xc3\xab' &&
@ -783,7 +751,6 @@ w : XX\t
r ^:test-1: : XXX\r\n$ r ^:test-1: : XXX\r\n$
! !
# err_exit #
((SHOPT_VSH)) && tst $LINENO <<"!" ((SHOPT_VSH)) && tst $LINENO <<"!"
L Using b, B, w and W commands in vi mode L Using b, B, w and W commands in vi mode
# https://github.com/att/ast/issues/1467 # https://github.com/att/ast/issues/1467
@ -797,7 +764,6 @@ r ^:test-2: echo asdf\r\n$
r ^asdf\r\n$ r ^asdf\r\n$
! !
# err_exit #
((SHOPT_ESH)) && mkdir -p emacstest/123abc && VISUAL=emacs tst $LINENO <<"!" ((SHOPT_ESH)) && mkdir -p emacstest/123abc && VISUAL=emacs tst $LINENO <<"!"
L autocomplete stops numeric input L autocomplete stops numeric input
# https://github.com/ksh93/ksh/issues/198 # https://github.com/ksh93/ksh/issues/198
@ -808,7 +774,6 @@ w cd emacste\t123abc
r ^:test-1: cd emacstest/123abc\r\n$ r ^:test-1: cd emacstest/123abc\r\n$
! !
# err_exit #
echo '((' >$tmp/synerror echo '((' >$tmp/synerror
ENV=$tmp/synerror tst $LINENO <<"!" ENV=$tmp/synerror tst $LINENO <<"!"
L syntax error in profile causes exit on startup L syntax error in profile causes exit on startup
@ -822,7 +787,6 @@ r ^:test-1: echo ok\r\n$
r ^ok\r\n$ r ^ok\r\n$
! !
# err_exit #
((SHOPT_VSH)) && tst $LINENO <<"!" ((SHOPT_VSH)) && tst $LINENO <<"!"
L split on quoted whitespace when extracting words from command history L split on quoted whitespace when extracting words from command history
# https://github.com/ksh93/ksh/pull/291 # https://github.com/ksh93/ksh/pull/291
@ -836,7 +800,6 @@ w :\E_
r ^:test-2: : One\\ "Two Three"\$'Four Five'\.mp3\r\n$ r ^:test-2: : One\\ "Two Three"\$'Four Five'\.mp3\r\n$
! !
# err_exit #
((SHOPT_VSH)) && tst $LINENO <<"!" ((SHOPT_VSH)) && tst $LINENO <<"!"
L crash when entering comment into history file (vi mode) L crash when entering comment into history file (vi mode)
# https://github.com/att/ast/issues/798 # https://github.com/att/ast/issues/798
@ -851,7 +814,6 @@ r \t#foo\r\n$
r \thist -lnN 1\r\n$ r \thist -lnN 1\r\n$
! !
# err_exit #
((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!" ((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!"
L tab completion while expanding ${.sh.*} variables L tab completion while expanding ${.sh.*} variables
# https://github.com/att/ast/issues/1461 # https://github.com/att/ast/issues/1461
@ -862,7 +824,6 @@ w test \$\{.sh.level\t
r ^:test-1: test \$\{.sh.level\}\r\n$ r ^:test-1: test \$\{.sh.level\}\r\n$
! !
# err_exit #
((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!" ((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!"
L tab completion executes command substitutions L tab completion executes command substitutions
# https://github.com/ksh93/ksh/issues/268 # https://github.com/ksh93/ksh/issues/268
@ -876,7 +837,6 @@ w `echo true`\t
r ^:test-2: `echo true`\r\n$ r ^:test-2: `echo true`\r\n$
! !
# err_exit #
((SHOPT_ESH)) && VISUAL=emacs tst $LINENO <<"!" ((SHOPT_ESH)) && VISUAL=emacs tst $LINENO <<"!"
L emacs: keys with repeat parameters repeat extra steps L emacs: keys with repeat parameters repeat extra steps
# https://github.com/ksh93/ksh/issues/292 # https://github.com/ksh93/ksh/issues/292
@ -896,7 +856,6 @@ w : test_string\1\E6\E[C\4
r ^:test-4: : teststring\r\n$ r ^:test-4: : teststring\r\n$
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L crash with KEYBD trap after entering multi-line command substitution L crash with KEYBD trap after entering multi-line command substitution
# https://www.mail-archive.com/ast-users@lists.research.att.com/msg00313.html # https://www.mail-archive.com/ast-users@lists.research.att.com/msg00313.html
@ -907,7 +866,6 @@ w true); echo "Exit status is $?"
u Exit status is 0 u Exit status is 0
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L interrupted PS2 discipline function L interrupted PS2 discipline function
# https://github.com/ksh93/ksh/issues/347 # https://github.com/ksh93/ksh/issues/347
@ -929,7 +887,6 @@ r > \)
r one two three end r one two three end
! !
# err_exit #
((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!" ((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!"
L tab completion of '.' and '..' L tab completion of '.' and '..'
# https://github.com/ksh93/ksh/issues/372 # https://github.com/ksh93/ksh/issues/372
@ -948,7 +905,6 @@ w : ..\t
r : \.\./\r\n$ r : \.\./\r\n$
! !
# err_exit #
tst $LINENO <<"!" tst $LINENO <<"!"
L Ctrl+C with SIGINT ignored L Ctrl+C with SIGINT ignored
# https://github.com/ksh93/ksh/issues/343 # https://github.com/ksh93/ksh/issues/343

View file

@ -8,7 +8,7 @@ valgrindflags='--xml=yes --log-file=/dev/null --track-origins=yes --read-var-inf
USAGE=$' USAGE=$'
[-s8? [-s8?
@(#)$Id: shtests (ksh 93u+m) 2021-12-20 $ @(#)$Id: shtests (ksh 93u+m) 2021-12-28 $
] ]
[-author?David Korn <dgk@research.att.com>] [-author?David Korn <dgk@research.att.com>]
[-author?Glenn Fowler <gsf@research.att.com>] [-author?Glenn Fowler <gsf@research.att.com>]
@ -344,6 +344,8 @@ do [[ $i == *.sh ]] || i+='.sh'
fi fi
t=$( case $i in t=$( case $i in
glob.sh) grep -c '^[[:blank:]]*test_[a-z]\{3,\}' $i ;; glob.sh) grep -c '^[[:blank:]]*test_[a-z]\{3,\}' $i ;;
leaks.sh) grep -c ^TEST $i ;;
pty.sh) grep -c 'tst ' $i ;;
*) grep -c err_exit $i ;; *) grep -c err_exit $i ;;
esac ) esac )
tests[$i]=$t tests[$i]=$t

View file

@ -100,6 +100,10 @@ while whence $TEST_notfound >/dev/null 2>&1
do TEST_notfound=notfound-$RANDOM do TEST_notfound=notfound-$RANDOM
done done
for exp in 65535 65536
do got=$($SHELL -c 'x=$(printf "%.*c" '$exp' x); print ${#x}' 2>&1)
[[ $got == $exp ]] || err_exit "large command substitution failed -- expected $exp, got $got"
done
integer BS=1024 nb=64 ss=60 bs no integer BS=1024 nb=64 ss=60 bs no
for bs in $BS 1 for bs in $BS 1