# $NetBSD: t_redir.sh,v 1.14 2021/11/21 20:50:35 kre Exp $ # # Copyright (c) 2016 The NetBSD Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # the implementation of "sh" to test : ${TEST_SH:="/bin/sh"} # Any failures in this first test means it is not worth bothering looking # for causes of failures in any other tests, make this one work first. # Problems with this test usually mean inadequate ATF_SHELL used for testing. # (though if all pass but the last, it might be a TEST_SH problem.) atf_test_case basic_test_method_test basic_test_method_test_head() { atf_set "descr" "Tests that test method works as expected" } basic_test_method_test_body() { cat <<- 'DONE' | DONE atf_check -s exit:0 -o empty -e empty ${TEST_SH} || atf_fail 'empty piped input' cat <<- 'DONE' | DONE atf_check -s exit:0 -o match:0 -e empty ${TEST_SH} -c 'wc -l' || atf_fail 'empty piped input line count' cat <<- 'DONE' | echo hello DONE atf_check -s exit:0 -o match:hello -e empty ${TEST_SH} || atf_fail 'piped hello' cat <<- 'DONE' | echo hello DONE atf_check -s exit:0 -o match:1 -e empty ${TEST_SH} -c 'wc -l' || atf_fail 'piped hello line count' cat <<- 'DONE' | echo hello\ world DONE atf_check -s exit:0 -o match:helloworld -e empty ${TEST_SH} || atf_fail 'piped hello world' cat <<- 'DONE' | echo hello\ world DONE atf_check -s exit:0 -o match:2 -e empty ${TEST_SH} -c 'wc -l' || atf_fail 'piped hello world line check' printf '%s\n%s\n%s\n' Line1 Line2 Line3 > File atf_check -s exit:0 -o inline:'Line1\nLine2\nLine3\n' -e empty \ ${TEST_SH} -c 'cat File' cat <<- 'DONE' | set -- X "" '' Y echo ARGS="${#}" echo '' -$1- -$2- -$3- -$4- cat <File atf_check -s exit:0 -e empty \ -o inline:'First Line\nSecond Line\nLine 3\nEND\n' \ ${TEST_SH} -c 'cat < File' atf_check -s exit:0 -e empty \ -o inline:'First Line\nSecond Line\nLine 3\nEND\n' \ ${TEST_SH} -c 'cat /dev/null || : test -f Output && atf_fail "Unable to remove Output file" #1 i; atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '> Output' test -f Output || atf_fail "#$T: Did not make Output file" #2 rm -f Output 2>/dev/null || : i; atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '>> Output' test -f Output || atf_fail "#$T: Did not make Output file" #3 rm -f Output 2>/dev/null || : i; atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '>| Output' test -f Output || atf_fail "#$T: Did not make Output file" #4 rm -f Output 2>/dev/null || : i atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c 'echo Hello >Output' test -s Output || atf_fail "#$T: Did not make non-empty Output file" test "$(cat Output)" = "Hello" || atf_fail "#$T: Incorrect Output: Should be 'Hello' is '$(cat Output)'" #5 i atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c 'echo Hello>!Output' test -s Output || atf_fail "#$T: Did not make non-empty Output file" test "$(cat Output)" = "Hello" || atf_fail "#$T: Incorrect Output: Should be 'Hello' is '$(cat Output)'" #6 i atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c 'echo Bye >>Output' test -s Output || atf_fail "#$T: Removed Output file" test "$(cat Output)" = "Hello${nl}Bye" || atf_fail \ "#$T: Incorrect Output: Should be 'Hello\\nBye' is '$(cat Output)'" #7 i; atf_check -s exit:0 -o inline:'line 1\nline 2\n' -e empty \ ${TEST_SH} -c \ 'echo line 1 > Output; echo line 2 >> Output; cat Output' test "$(cat Output)" = "line 1${nl}line 2" || atf_fail \ "#$T: Incorrect Output: Should be 'line 1\\nline 2' is '$(cat Output)'" #8 i; atf_check -s exit:0 -o inline:'line 2\n' -e empty \ ${TEST_SH} -c 'echo line 1 > Output; echo line 2' test "$(cat Output)" = "line 1" || atf_fail \ "#$T: Incorrect Output: Should be 'line 1' is '$(cat Output)'" #9 i; atf_check -s exit:0 -o empty -e empty \ ${TEST_SH} -c '(echo line 1; echo line 2 > Out2) > Out1' test "$(cat Out1)" = "line 1" || atf_fail \ "#$T: Incorrect Out1: Should be 'line 1' is '$(cat Out1)'" test "$(cat Out2)" = "line 2" || atf_fail \ "#$T: Incorrect Out2: Should be 'line 2' is '$(cat Out2)'" #10 i; atf_check -s exit:0 -o empty -e empty \ ${TEST_SH} -c '{ echo line 1; echo line 2 > Out2;} > Out1' test "$(cat Out1)" = "line 1" || atf_fail \ "#$T: Incorrect Out1: Should be 'line 1' is '$(cat Out1)'" test "$(cat Out2)" = "line 2" || atf_fail \ "#$T: Incorrect Out2: Should be 'line 2' is '$(cat Out2)'" #11 i; rm -f Out1 Out2 2>/dev/null || : cat <<- 'EOF' | for arg in 'line 1' 'line 2' 'line 3' do echo "$arg" echo "$arg" > Out1 done > Out2 EOF atf_check -s exit:0 -o empty -e empty ${TEST_SH} || atf_fail "#$T: not empty/empty/0" test "$(cat Out1)" = "line 3" || atf_fail \ "#$T: Incorrect Out1: Should be 'line 3' is '$(cat Out1)'" test "$(cat Out2)" = "line 1${nl}line 2${nl}line 3" || atf_fail \ "#$T: Incorrect Out2: Should be 'line 1\\nline 2\\nline 3' is '$(cat Out2)'" #12 i; rm -f Out1 Out2 2>/dev/null || : cat <<- 'EOF' | for arg in 'line 1' 'line 2' 'line 3' do echo "$arg" echo "$arg" >> Out1 done > Out2 EOF atf_check -s exit:0 -o empty -e empty ${TEST_SH} || atf_fail "#$T: not empty/empty/0" test "$(cat Out1)" = "line 1${nl}line 2${nl}line 3" || atf_fail \ "#$T: Incorrect Out1: Should be 'line 1\\nline 2\\nline 3' is '$(cat Out1)'" test "$(cat Out2)" = "line 1${nl}line 2${nl}line 3" || atf_fail \ "#$T: Incorrect Out2: Should be 'line 1\\nline 2\\nline 3' is '$(cat Out2)'" } atf_test_case do_redirect_input_output do_redirect_input_output_head() { atf_set "descr" "Test Input+Output (BiDir) redirections" } do_redirect_input_output_body() { nl=' ' T=0 i() { T=$(expr "$T" + 1); } rm -f Output 2>/dev/null || : test -f Output && atf_fail "Unable to remove Output file" #1 i; atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c '<> Output' test -f Output || atf_fail "#$T: Did not make Output file" #2 echo data >Output 2>/dev/null || : i atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \ '<>Output' test -f Output || atf_fail "#$T: Removed Output file" test -s Output || atf_fail "#$T: Did not keep data in Output file" test "$(cat Output)" = "data" || atf_fail "#$T: Incorrect Output: Should be 'data' is '$(cat Output)'" #3 rm -f Output 2>/dev/null || : i atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \ 'echo Hello 1<>Output' test -s Output || atf_fail "#$T: Did not keep non-empty Output file" test "$(cat Output)" = "Hello" || atf_fail "#$T: Incorrect Output: Should be 'Hello' is '$(cat Output)'" #4 printf data >Output 2>/dev/null || : i atf_check -s exit:0 -o inline:'data' -e empty ${TEST_SH} -c \ 'cat <>Output' test -f Output || atf_fail "#$T: Removed Output file" test -s Output || atf_fail "#$T: Did not keep data in Output file" test "$(cat Output)" = "data" || atf_fail "#$T: Incorrect Output: Should be 'data' is '$(cat Output)'" #5 echo data >Output 2>/dev/null || : i atf_check -s exit:0 -o empty -e empty ${TEST_SH} -c \ 'echo Hello 1<>Output' test -s Output || atf_fail "#$T: Did not make non-empty Output file" test "$(cat Output)" = "Hello" || atf_fail "#$T: Incorrect Output: Should be 'Hello' is '$(cat Output)'" #6 printf data >Output 2>/dev/null || : i atf_check -s exit:0 -o inline:data -e empty ${TEST_SH} -c \ '{ cat >&3; printf file; } <>Output 3>&1 >&0' test -f Output || atf_fail "#$T: Removed Output file" test -s Output || atf_fail "#$T: Did not keep data in Output file" test "$(cat Output)" = "datafile" || atf_fail \ "#$T: Incorrect Output: Should be 'datafile' is '$(cat Output)'" } atf_test_case fd_redirections fd_redirections_head() { atf_set "descr" "Tests redirections to/from specific descriptors" } fd_redirections_body() { atf_require_prog /bin/echo cat <<- 'DONE' > helper.sh f() { /bin/echo nothing "$1" >& "$1" } for n do eval "f $n $n"'> file-$n' done DONE cat <<- 'DONE' > reread.sh f() { (read -r var; echo "${var}") <&"$1" } for n do x=$( eval "f $n $n"'< file-$n' ) test "${x}" = "nothing $n" || echo "$n" done DONE validate() { for n do test -e "file-$n" || atf_fail "file-$n not created" C=$(cat file-"$n") test "$C" = "nothing $n" || atf_fail "file-$n contains '$C' not 'nothing $n'" done } atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} helper.sh 1 2 3 4 5 6 7 8 9 validate 1 2 3 4 5 6 7 8 9 atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} reread.sh 3 4 5 6 7 8 9 L=$(ulimit -n) if [ "$L" -ge 30 ] then atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} helper.sh 10 15 19 20 25 29 validate 10 15 19 20 25 29 atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} reread.sh 10 15 19 20 25 29 fi if [ "$L" -ge 100 ] then atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} helper.sh 32 33 49 50 51 63 64 65 77 88 99 validate 32 33 49 50 51 63 64 65 77 88 99 atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} reread.sh 32 33 49 50 51 63 64 65 77 88 99 fi if [ "$L" -ge 500 ] then atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} helper.sh 100 101 199 200 222 333 444 499 validate 100 101 199 200 222 333 444 499 atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} reread.sh 100 101 199 200 222 333 444 499 fi if [ "$L" -gt 1005 ] then atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} helper.sh 1000 1001 1002 1003 1004 1005 validate 1000 1001 1002 1003 1004 1005 atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} reread.sh 1000 1001 1002 1003 1004 1005 fi } atf_test_case local_redirections local_redirections_head() { atf_set "descr" \ "Tests that exec can reassign file descriptors in the shell itself" } local_redirections_body() { cat <<- 'DONE' > helper.sh for f do eval "exec $f"'> file-$f' done for f do printf '%s\n' "Hello $f" >&"$f" done for f do eval "exec $f"'>&-' done for f do eval "exec $f"'< file-$f' done for f do exec <& "$f" read -r var || echo >&2 "No data in file-$f" read -r x && echo >&2 "Too much data in file-${f}: $x" test "${var}" = "Hello $f" || echo >&2 "file-$f contains '${var}' not 'Hello $f'" done DONE atf_check -s exit:0 -o empty -e empty \ ${TEST_SH} helper.sh 3 4 5 6 7 8 9 L=$(ulimit -n) if [ "$L" -ge 30 ] then atf_check -s exit:0 -o empty -e empty \ ${TEST_SH} helper.sh 10 11 13 15 16 19 20 28 29 fi if [ "$L" -ge 100 ] then atf_check -s exit:0 -o empty -e empty \ ${TEST_SH} helper.sh 30 31 32 63 64 65 77 88 99 fi if [ "$L" -ge 500 ] then atf_check -s exit:0 -o empty -e empty \ ${TEST_SH} helper.sh 100 101 111 199 200 201 222 333 499 fi if [ "$L" -ge 1005 ] then atf_check -s exit:0 -o empty -e empty \ ${TEST_SH} helper.sh 1000 1001 1002 1003 1004 1005 fi } atf_test_case named_fd_redirections named_fd_redirections_head() { atf_set "descr" "Tests redirections to /dev/stdout (etc)" } named_fd_redirections_body() { if test -c /dev/stdout then atf_check -s exit:0 -o inline:'OK\n' -e empty \ ${TEST_SH} -c 'echo OK >/dev/stdout' atf_check -s exit:0 -o inline:'OK\n' -e empty \ ${TEST_SH} -c '/bin/echo OK >/dev/stdout' fi if test -c /dev/stdin then atf_require_prog cat echo GOOD | atf_check -s exit:0 -o inline:'GOOD\n' -e empty \ ${TEST_SH} -c 'read var /dev/stderr >&2' atf_check -s exit:0 -e inline:'OK\n' -o empty \ ${TEST_SH} -c '/bin/echo OK 2>/dev/stderr >&2' fi if test -c /dev/fd/8 && test -c /dev/fd/9 then atf_check -s exit:0 -o inline:'EIGHT\n' -e empty \ ${TEST_SH} -c 'printf "%s\n" EIGHT 8>&1 >/dev/fd/8 | cat 9<&0 foo;; esac' atf_check -s exit:0 -o empty -e empty \ ${TEST_SH} -c 'case x in (whatever) >foo 2>&1;; esac' atf_check -s exit:0 -o empty -e empty \ ${TEST_SH} -c 'case x in (whatever) >foo 2>&1 ${somewhere};; esac' } atf_test_case incorrect_redirections incorrect_redirections_head() { atf_set "descr" "Tests that sh(1) correctly ignores non-redirections" } incorrect_redirections_body() { atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c 'echo foo>' atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c 'read foo<' atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c 'echo foo<>' atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \ 'echo x > '"$nl" atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \ 'read x < '"$nl" atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \ 'echo x <> '"$nl" atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \ 'echo x >< anything' atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \ 'echo x >>< anything' atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \ 'echo x >|< anything' atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \ 'echo x > ; read x < /dev/null || echo bad' atf_check -s not-exit:0 -o empty -e not-empty ${TEST_SH} -c \ 'read x < & echo y > /dev/null; wait && echo bad' rm -f Output 2>/dev/null || : atf_check -s exit:0 -e empty -o inline:'A Line > Output\n' \ ${TEST_SH} -c 'echo A Line \> Output' test -f Output && atf_file "File 'Output' appeared and should not have" rm -f Output 2>/dev/null || : atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} -c 'echo A Line \>> Output' test -f Output || atf_file "File 'Output' not created when it should" test "$(cat Output)" = 'A Line >' || atf_fail \ "Output file contains '$(cat Output)' instead of '"'A Line >'\' rm -f Output \> 2>/dev/null || : atf_check -s exit:0 -e empty -o empty \ ${TEST_SH} -c 'echo A Line >\> Output' test -f Output && atf_file "File 'Output' appeared and should not have" test -f '>' || atf_file "File '>' not created when it should" test "$(cat '>')" = 'A Line Output' || atf_fail \ "Output file ('>') contains '$(cat '>')' instead of 'A Line Output'" rm -fr OutDir atf-check -s not-exit:0 -o empty -e not-empty \ ${TEST_SH} -c ': > OutDir/stdout; printf foo' atf-check -s not-exit:0 -o empty -e not-empty \ ${TEST_SH} -c ': > OutDir/stdout || printf foo; printf bar' atf-check -s exit:0 -o inline:bar -e not-empty \ ${TEST_SH} -c '> OutDir/stdout; printf bar' atf-check -s exit:0 -o inline:foobar -e not-empty \ ${TEST_SH} -c '> OutDir/stdout || printf foo; printf bar' atf-check -s exit:0 -o inline:bar -e not-empty \ ${TEST_SH} -c 'command : > OutDir/stdout; printf bar' atf-check -s exit:0 -o inline:foobar -e not-empty ${TEST_SH} -c \ 'command : > OutDir/stdout || printf foo; printf bar' atf-check -s not-exit:0 -o empty -e not-empty \ ${TEST_SH} -c ': <> OutDir/stdout; printf foo' atf-check -s not-exit:0 -o empty -e not-empty \ ${TEST_SH} -c ': >&8 ; printf foo' atf-check -s not-exit:0 -o empty -e not-empty \ ${TEST_SH} -c ': >&8 || printf foo; printf bar' atf-check -s exit:0 -o inline:bar -e not-empty \ ${TEST_SH} -c '>&8 ; printf bar' atf-check -s exit:0 -o inline:foobar -e not-empty \ ${TEST_SH} -c '>&8 || printf foo; printf bar' atf-check -s exit:0 -o inline:bar -e not-empty \ ${TEST_SH} -c 'command : >&7; printf bar' atf-check -s exit:0 -o inline:foobar -e not-empty ${TEST_SH} -c \ 'command : >&7 || printf foo; printf bar' return 0 } # Many more tests in t_here, so here we have just rudimentary checks atf_test_case redir_here_doc redir_here_doc_head() { atf_set "descr" "Tests that sh(1) correctly processes 'here' doc " \ "input redirections" } redir_here_doc_body() { # nb: the printf is not executed, it is data cat <<- 'DONE' | cat <output-file ( printf "hello\n" >&6 ) exec 8&2 Hello ) ( printf "bye-bye\n" >&6 ) ( exec 8<&- ) read bye <&8 || echo >&2 "Closed?" echo Bye="$bye" DONE atf_check -s exit:0 -o match:Bye=bye-bye -e empty \ ${TEST_SH} || atf_fail 'bye-bye failure' cat <<- 'DONE' | for arg in one-4 two-24 three-14 do fd=${arg#*-} file=${arg%-*} eval "exec ${fd}>${file}" done for arg in one-5 two-7 three-19 do fd=${arg#*-} file=${arg%-*} eval "exec ${fd}<${file}" done ( echo line-1 >&4 echo line-2 >&24 echo line-3 >&14 echo go ) | ( read go read x <&5 read y <&7 read z <&19 printf "%s\n" "${x}" "${y}" "${z}" ) DONE atf_check -s exit:0 -o inline:'line-1\nline-2\nline-3\n' \ -e empty ${TEST_SH} || atf_fail 'complex 3 line redirects' cat <<- 'DONE' | for arg in one-4-5 two-6-7 three-8-9 four-11-10 five-3-12 do ofd=${arg##*-} file=${arg%-*} ifd=${file#*-} file=${file%-*} eval "exec ${ofd}>${file}" eval "exec ${ifd}<${file}" done ( ( ( echo line-1 >& 13 ) 13>&12 ) 12>&5 ) >stdout 2>errout ( ( ( echo line-2 >& 4) 13>&12 ) 4>&7 ) >>stdout 2>>errout ( ( ( echo line-3 >& 6) 8>&1 6>&11 >&12) 11>&9 >&7 ) >>stdout ( ( ( cat <&13 >&12 ) 13<&8 12>&10 ) 10>&1 8<&6 ) 6<&4 ( ( ( cat <&4 ) <&4 6<&8 8<&11 ) <&4 4<&6 6<&8 8<&11 ) <&4 4<&6 6<&8 8<&11 11<&3 ( ( ( cat <&7 >&1 ) 7<&6 >&10 ) 10>&2 6<&8 ) 2>&1 DONE atf_check -s exit:0 -o inline:'line-1\nline-2\nline-3\n' \ -e empty ${TEST_SH} || atf_fail 'absurd 3 line redirects' } atf_test_case ulimit_redirection_interaction ulimit_redirection_interaction_head() { atf_set "descr" "Tests interactions between redirect and ulimit -n " } ulimit_redirection_interaction_body() { atf_require_prog ls cat <<- 'DONE' > helper.sh oLIM=$(ulimit -n) HRD=$(ulimit -H -n) test "${oLIM}" -lt "${HRD}" && ulimit -n "${HRD}" LIM=$(ulimit -n) FDs= LFD=-1 while [ ${LIM} -gt 16 ] do FD=$(( ${LIM} - 1 )) if [ "${FD}" -eq "${LFD}" ]; then echo >&2 "Infinite loop... (busted $(( )) ??)" exit 1 fi LFD="${FD}" eval "exec ${FD}"'> /dev/null' FDs="${FD}${FDs:+ }${FDs}" ( FD=$(( ${LIM} + 1 )) eval "exec ${FD}"'> /dev/null' echo "Reached unreachable command" ) 2>/dev/null && echo >&2 "Opened beyond limit!" (eval 'ls 2>&1 3>&1 4>&1 5>&1 '"${FD}"'>&1') >&"${FD}" LIM=$(( ${LIM} / 2 )) ulimit -S -n "${LIM}" done # Even though ulimit has been reduced, open fds should work for FD in ${FDs} do echo ${FD} in ${FDs} >&"${FD}" || exit 1 done ulimit -S -n "${oLIM}" # maybe more later... DONE atf_check -s exit:0 -o empty -e empty ${TEST_SH} helper.sh } atf_test_case validate_fn_redirects validate_fn_redirects_head() { # These test cases inspired by PR bin/48875 and the sh # changes that were required to fix it. atf_set "descr" "Tests various redirections applied to functions " \ "See PR bin/48875" } validate_fn_redirects_body() { cat <<- 'DONE' > f-def f() { printf '%s\n' In-Func } DONE atf_check -s exit:0 -o inline:'In-Func\nsuccess1\n' -e empty \ ${TEST_SH} -c ". ./f-def; f ; printf '%s\n' success1" atf_check -s exit:0 -o inline:'success2\n' -e empty \ ${TEST_SH} -c ". ./f-def; f >/dev/null; printf '%s\n' success2" atf_check -s exit:0 -o inline:'success3\n' -e not-empty \ ${TEST_SH} -c ". ./f-def; f >&- ; printf '%s\n' success3" atf_check -s exit:0 -o inline:'In-Func\nsuccess4\n' -e empty \ ${TEST_SH} -c ". ./f-def; f & wait; printf '%s\n' success4" atf_check -s exit:0 -o inline:'success5\n' -e not-empty \ ${TEST_SH} -c ". ./f-def; f >&- & wait; printf '%s\n' success5" atf_check -s exit:0 -o inline:'In-Func\nIn-Func\nsuccess6\n' -e empty \ ${TEST_SH} -c ". ./f-def; f;f; printf '%s\n' success6" atf_check -s exit:0 -o inline:'In-Func\nIn-Func\nsuccess7\n' -e empty \ ${TEST_SH} -c ". ./f-def; { f;f;}; printf '%s\n' success7" atf_check -s exit:0 -o inline:'In-Func\nIn-Func\nsuccess8\n' -e empty \ ${TEST_SH} -c ". ./f-def; { f;f;}& wait; printf '%s\n' success8" atf_check -s exit:0 -o inline:'In-Func\nsuccess9\n' -e empty \ ${TEST_SH} -c \ ". ./f-def; { f>/dev/null;f;}& wait; printf '%s\n' success9" atf_check -s exit:0 -o inline:'In-Func\nsuccess10\n' -e empty \ ${TEST_SH} -c \ ". ./f-def; { f;f>/dev/null;}& wait; printf '%s\n' success10" # This one tests the issue etcupdate had with the original 48875 fix atf_check -s exit:0 -o inline:'Func a\nFunc b\nFunc c\n' -e empty \ ${TEST_SH} -c ' f() { echo Func "$1" } exec 3<&0 4>&1 ( echo x-a; echo y-b; echo z-c ) | while read A do B=${A#?-} f "$B" <&3 >&4 done >&2' # And this tests a similar condition with that same fix cat <<- 'DONE' >Script f() { printf '%s' " hello $1" } exec 3>&1 echo $( for i in a b c do printf '%s' @$i; f $i >&3; done >foo ) printf '%s\n' foo=$(cat foo) DONE atf_check -s exit:0 -e empty \ -o inline:' hello a hello b hello c\nfoo=@a@b@c\n' \ ${TEST_SH} Script # Tests with sh reading stdin, which is not quite the same internal # mechanism. echo ". ./f-def || echo >&2 FAIL f printf '%s\n' stdin1 " | atf_check -s exit:0 -o inline:'In-Func\nstdin1\n' -e empty \ ${TEST_SH} || atf_fail "stdin1 test failure" echo ' . ./f-def || echo >&2 FAIL f >&- 2>/dev/null printf "%s\n" stdin2 ' | atf_check -s exit:0 -o inline:'stdin2\n' -e empty ${TEST_SH} || atf_fail "stdin2 test failure" cat <<- 'DONE' > fgh.def f() { echo -n f >&3 sleep 4 echo -n F >&3 } g() { echo -n g >&3 sleep 2 echo -n G >&3 } h() { echo -n h >&3 } DONE atf_check -s exit:0 -o inline:'fFgGh' -e empty \ ${TEST_SH} -c '. ./fgh.def || echo >&2 FAIL exec 3>&1 f; g; h' atf_check -s exit:0 -o inline:'fghGF' -e empty \ ${TEST_SH} -c '. ./fgh.def || echo >&2 FAIL exec 3>&1 f & sleep 1; g & sleep 1; h; wait' atf_check -s exit:0 -o inline:'fFgGhX Y\n' -e empty \ ${TEST_SH} -c '. ./fgh.def || echo >&2 FAIL exec 3>&1 echo X $( f ; g ; h ) Y' # This one is the real test for PR bin/48875. If the # cmdsub does not complete before f g (and h) exit, # then the 'F' & 'G' will precede 'X Y' in the output. # If the cmdsub finishes while f & g are still running, # then the X Y will appear before the F and G. # The trailing "sleep 3" is just so we catch all the # output (otherwise atf_check will be finished while # f & g are still sleeping). atf_check -s exit:0 -o inline:'fghX Y\nGF' -e empty \ ${TEST_SH} -c '. ./fgh.def || echo >&2 FAIL exec 3>&1 echo X $( f >&- & sleep 1; g >&- & sleep 1 ; h ) Y sleep 3 exec 4>&1 || echo FD_FAIL ' # Do the test again to verify it also all works reading stdin # (which is a slightly different path through the shell) echo ' . ./fgh.def || echo >&2 FAIL exec 3>&1 echo X $( f >&- & sleep 1; g >&- & sleep 1 ; h ) Y sleep 3 exec 4>&1 || echo FD_FAIL ' | atf_check -s exit:0 -o inline:'fghX Y\nGF' -e empty ${TEST_SH} || atf_fail "48875 stdin variant failure" } atf_init_test_cases() { atf_add_test_case basic_test_method_test atf_add_test_case do_input_redirections atf_add_test_case do_output_redirections atf_add_test_case do_redirect_input_output atf_add_test_case fd_redirections atf_add_test_case local_redirections atf_add_test_case incorrect_redirections atf_add_test_case named_fd_redirections atf_add_test_case redir_here_doc atf_add_test_case redir_in_case atf_add_test_case subshell_redirections atf_add_test_case ulimit_redirection_interaction atf_add_test_case validate_fn_redirects }