Merge nucleic/sleek-thistle-egret-fyej into dev
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
name: "and/or"
|
||||
cases:
|
||||
- name: "Basic &&"
|
||||
stdin: |
|
||||
false && echo 1
|
||||
true && echo 2
|
||||
|
||||
- name: "Basic ||"
|
||||
stdin: |
|
||||
false || echo 1
|
||||
true || echo 2
|
||||
|
||||
- name: "Longer chains"
|
||||
stdin: |
|
||||
false || false || false || echo "Got to the end"
|
||||
echo "1" && echo "2" && echo "3" && echo "4"
|
||||
|
||||
- name: "Mixed chains"
|
||||
stdin: |
|
||||
false && true || echo "1. Got to the end"
|
||||
false && false || echo "2. Got to the end"
|
||||
true && false || echo "3. Got to the end"
|
||||
|
||||
- name: "And with return in function"
|
||||
stdin: |
|
||||
f() {
|
||||
echo "Before" && return 42 && echo "After return"
|
||||
echo "After and-or"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "And with exit"
|
||||
stdin: |
|
||||
echo "Before" && exit 42 && echo "After exit"
|
||||
echo "After and-or"
|
||||
|
||||
- name: "And with break in loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Iteration $i" && break && echo "After break"
|
||||
echo "After and-or"
|
||||
done
|
||||
echo "After loop"
|
||||
|
||||
- name: "And with continue in loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Iteration $i" && continue && echo "After continue"
|
||||
echo "After and-or"
|
||||
done
|
||||
echo "After loop"
|
||||
|
||||
- name: "Or with return in function"
|
||||
stdin: |
|
||||
f() {
|
||||
false || return 42 || echo "After return"
|
||||
echo "After or-list"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "Or with exit"
|
||||
stdin: |
|
||||
false || exit 42 || echo "After exit"
|
||||
echo "After or-list"
|
||||
|
||||
- name: "Or with break in loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
false || break || echo "After break"
|
||||
echo "After or-list"
|
||||
done
|
||||
echo "After loop"
|
||||
|
||||
- name: "Or with continue in loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Iteration $i"
|
||||
false || continue || echo "After continue"
|
||||
echo "After or-list"
|
||||
done
|
||||
echo "After loop"
|
||||
@@ -0,0 +1,122 @@
|
||||
name: "Argument handling tests"
|
||||
common_test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo \"$0\" \"$1\" \"$2\" \"$@\"
|
||||
|
||||
cases:
|
||||
- name: "-c mode arguments without --"
|
||||
args:
|
||||
- "-c"
|
||||
- 'echo \"$0\" \"$1\" \"$2\" \"$@\"'
|
||||
- 1
|
||||
- "-2"
|
||||
- "3"
|
||||
|
||||
- name: "-c mode arguments with --"
|
||||
args:
|
||||
- "-c"
|
||||
- 'echo \"$0\" \"$1\" \"$2\" \"$@\"'
|
||||
- "--"
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
- name: "-c mode and arguments with +O"
|
||||
args:
|
||||
- "+O"
|
||||
- "nullglob"
|
||||
- "-c"
|
||||
- 'echo \"$0\" \"$1\" \"$2\" \"$@\"'
|
||||
- "--"
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
- name: "-c mode -- torture"
|
||||
args:
|
||||
- "-c"
|
||||
- 'echo \"$0\" \"$1\" \"$2\" \"$@\"'
|
||||
- --
|
||||
- --
|
||||
- -&-1
|
||||
- --!
|
||||
- "\"-2\""
|
||||
- "''--''"
|
||||
- 3--*
|
||||
|
||||
- name: "-c modeonly one --"
|
||||
args:
|
||||
- "-c"
|
||||
- 'echo \"$0\" \"$1\" \"$2\" \"$@\"'
|
||||
- --
|
||||
|
||||
- name: "script arguments without --"
|
||||
args:
|
||||
- script.sh
|
||||
- -1
|
||||
- -2
|
||||
- -3
|
||||
|
||||
- name: "script arguments with --"
|
||||
args:
|
||||
- script.sh
|
||||
- --
|
||||
- --1
|
||||
- -2
|
||||
- 3
|
||||
|
||||
- name: "script -- torture"
|
||||
args:
|
||||
- script.sh
|
||||
- --
|
||||
- "--"
|
||||
- --
|
||||
- -!-1*
|
||||
- "\"-2\""
|
||||
- --
|
||||
- 3--
|
||||
|
||||
- name: "script only one --"
|
||||
args:
|
||||
- script.sh
|
||||
- --
|
||||
|
||||
- name: "-c with -- separator and command"
|
||||
args:
|
||||
- "-c"
|
||||
- "--"
|
||||
- 'echo \"$0\" \"$1\" \"$2\" \"$@\"'
|
||||
- "1"
|
||||
- "-2"
|
||||
- "3"
|
||||
|
||||
- name: "-c with -- separator only"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- "--"
|
||||
|
||||
- name: "-c with -- separator where command is --"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- "--"
|
||||
- "--"
|
||||
- "echo"
|
||||
- "hi"
|
||||
|
||||
- name: "-ec with -- separator and command"
|
||||
args:
|
||||
- "-ec"
|
||||
- "--"
|
||||
- 'echo \"$0\" \"$1\" \"$2\" \"$@\"'
|
||||
- "1"
|
||||
- "-2"
|
||||
- "3"
|
||||
|
||||
- name: "-o with -- is not an option terminator"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-o"
|
||||
- "--"
|
||||
@@ -0,0 +1,362 @@
|
||||
name: "Arithmetic"
|
||||
cases:
|
||||
- name: "Empty expression"
|
||||
stdin: |
|
||||
echo $(())
|
||||
|
||||
- name: "Unquoted arithmetic"
|
||||
stdin: |
|
||||
echo $((1+1))
|
||||
|
||||
- name: "Arithmetic literals"
|
||||
stdin: |
|
||||
echo "$((0))"
|
||||
echo "$((1))"
|
||||
echo "$((10))"
|
||||
echo "$((010))"
|
||||
echo "$((0010))"
|
||||
echo "$((0x10))"
|
||||
echo "$((0x010))"
|
||||
|
||||
- name: "Arithmetic literals with explicit bases"
|
||||
stdin: |
|
||||
echo "$((2#1010))"
|
||||
|
||||
echo "$((8#16))"
|
||||
|
||||
echo "$((16#faf))"
|
||||
echo "$((16#FAF))"
|
||||
|
||||
echo "$((36#vvv))"
|
||||
echo "$((36#VVV))"
|
||||
|
||||
# Base 37: A-Z map to 36-61
|
||||
echo "$((37#a))"
|
||||
echo "$((37#a0))"
|
||||
echo "$((37#A))"
|
||||
|
||||
# Base 62: a-z=10-35, A-Z=36-61
|
||||
echo "$((62#A))"
|
||||
echo "$((62#a))"
|
||||
echo "$((62#Z))"
|
||||
echo "$((62#z))"
|
||||
|
||||
# Base 63: @ = 62
|
||||
echo "$((63#@))"
|
||||
echo "$((63#@9))"
|
||||
|
||||
# Base 64: _ = 63
|
||||
echo "$((64#_))"
|
||||
echo "$((64#@))"
|
||||
echo "$((64#_1))"
|
||||
echo "$((64#zzz))"
|
||||
|
||||
- name: "Unary arithmetic operators with space"
|
||||
stdin: |
|
||||
echo "$(( - 1 ))"
|
||||
echo "$(( + 1 ))"
|
||||
echo "$(( ~ 10 ))"
|
||||
echo "$(( ! 10 ))"
|
||||
|
||||
- name: "Parentheses"
|
||||
stdin: |
|
||||
echo "$(((10)))"
|
||||
echo "$((((10))))"
|
||||
echo "$(((((10)))))"
|
||||
|
||||
echo "$(( (10) ))"
|
||||
echo "$(( (1+2) ))"
|
||||
echo "$(( (1 + 2) ))"
|
||||
|
||||
echo "$(( ((1+2)) ))"
|
||||
echo "$(( ((1 + 2)) ))"
|
||||
|
||||
echo "$(((1+2)*3))"
|
||||
echo "$(((1 + 2) * 3))"
|
||||
echo "$(( (1 + 2) * 3 ))"
|
||||
|
||||
echo "$((((1+2)*3)))"
|
||||
echo "$(( ((1 + 2) * 3) ))"
|
||||
echo "$(( ( (1 + 2) * 3 ) ))"
|
||||
|
||||
- name: "Basic quoted arithmetic"
|
||||
stdin: |
|
||||
echo " 1 + 1 == $((1+1))"
|
||||
echo " 2 * 3 == $((2*3))"
|
||||
echo " 9 / 3 == $((9/3))"
|
||||
echo "10 % 3 == $((10%3))"
|
||||
echo " 2 ** 3 == $((2**3))"
|
||||
echo " 2 | 4 == $((2|4))"
|
||||
echo " 2 & 4 == $((2&4))"
|
||||
echo " 2 ^ 3 == $((2^3))"
|
||||
echo " 1 && 1 == $((1&&1))"
|
||||
echo " 1 && 0 == $((1&&0))"
|
||||
echo " 1 || 0 == $((1||0))"
|
||||
echo " 0 || 1 == $((1||0))"
|
||||
echo " 1 , 2 == $((1,2))"
|
||||
|
||||
- name: "Unary operators"
|
||||
stdin: |
|
||||
echo " -1 == $((-1))"
|
||||
echo " +1 == $((+1))"
|
||||
echo " ~10 == $((~10))"
|
||||
echo " !10 == $((!10))"
|
||||
|
||||
- name: "Conditional operator"
|
||||
stdin: |
|
||||
echo "1 ? 2 : 3 == $((1?2:3))"
|
||||
echo "0 ? 2 : 3 == $((0?2:3))"
|
||||
|
||||
- name: "Arithmetic with spacing"
|
||||
stdin: |
|
||||
echo $(( 75 + 68 ))
|
||||
|
||||
- name: "Divide by zero"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "1 / 0 == $((1/0))"
|
||||
echo "Result: $?"
|
||||
|
||||
echo "1 % 0 == $((1%0))"
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Shift arithmetic"
|
||||
stdin: |
|
||||
echo "32 >> 2 == $((32>>2))"
|
||||
echo " 1 << 4 == $((1<<4))"
|
||||
|
||||
- name: Shift arithmetic in conditional"
|
||||
stdin: |
|
||||
(( 1 >> 1 )) && echo "1. true"
|
||||
(( 1 << 1 )) && echo "2. true"
|
||||
|
||||
- name: "Variable references"
|
||||
stdin: |
|
||||
x=10
|
||||
echo "x => $((x))"
|
||||
echo "x + 1 => $((x+1))"
|
||||
|
||||
y=
|
||||
echo "y => $((y))"
|
||||
echo "y + 1 => $((y+1))"
|
||||
|
||||
z=notnumber
|
||||
echo "z => $((z))"
|
||||
echo "z + 1 => $((z+1))"
|
||||
|
||||
- name: "Complex variable references"
|
||||
stdin: |
|
||||
x=10
|
||||
y=20
|
||||
z=x+y
|
||||
a=z+z
|
||||
|
||||
echo "z => $((z))"
|
||||
echo "a => $((a))"
|
||||
|
||||
- name: "Nested expressions"
|
||||
stdin: |
|
||||
echo "1: $(($(echo -n 1; echo 2) + 37))"
|
||||
|
||||
op="+"
|
||||
echo "2: $((10 ${op} 20))"
|
||||
|
||||
expr="13 * 7"
|
||||
echo "3: $(($expr))"
|
||||
|
||||
- name: "Assignment arithmetic"
|
||||
stdin: |
|
||||
x=0
|
||||
|
||||
echo "x = 1 => $((x = 1))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "x += 3 => $((x += 3))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "x -= 1 => $((x -= 1))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "x *= 2 => $((x *= 2))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "x++ == $((x++))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "++x == $((++x))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "x-- == $((x--))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "--x == $((--x))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "x |= 3 => $((x |= 3))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "x &= 12 => $((x &= 12))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "x %= 8 => $((x %= 8))"
|
||||
echo "x is now $x"
|
||||
|
||||
echo "x ^= 7 => $((x ^= 7))"
|
||||
echo "x is now $x"
|
||||
|
||||
- name: "Assignment with references"
|
||||
stdin: |
|
||||
x=10+3
|
||||
echo "y = x => $((y = x))"
|
||||
echo "y is now $y"
|
||||
|
||||
- name: "Assignments in logical boolean expressions"
|
||||
stdin: |
|
||||
x=0
|
||||
echo "0 && x+=1 => $(( 0 && (x+=1) ))"
|
||||
echo "x: $x"
|
||||
|
||||
x=0
|
||||
echo "1 && x+=1 => $(( 1 && (x+=1) ))"
|
||||
echo "x: $x"
|
||||
|
||||
x=0
|
||||
echo "0 || x+=1 => $(( 0 || (x+=1) ))"
|
||||
echo "x: $x"
|
||||
|
||||
x=0
|
||||
echo "1 || x+=1 => $(( 1 || (x+=1) ))"
|
||||
echo "x: $x"
|
||||
|
||||
- name: "Array arithmetic"
|
||||
stdin: |
|
||||
a=(1 2 3)
|
||||
declare -p a
|
||||
echo "a[0]: $((a[0]))"
|
||||
echo "a[2]: $((a[2]))"
|
||||
echo "a[3]: $((a[3]))"
|
||||
|
||||
echo "b[2]: $((b[2]))"
|
||||
|
||||
echo "a[1]=4 => $((a[1]=4))"
|
||||
echo "a[1]: $((a[1]))"
|
||||
|
||||
echo "a[1]+=1 => $((a[1]+=1))"
|
||||
echo "a[1]: $((a[1]))"
|
||||
|
||||
echo "c[1] += 3 => $((c[1] += 3))"
|
||||
|
||||
- name: "Basic arithmetic comparison"
|
||||
stdin: |
|
||||
echo "0 < 1: $((0 < 1))"
|
||||
echo "0 <= 1: $((0 <= 1))"
|
||||
echo "0 == 1: $((0 == 1))"
|
||||
echo "0 != 1: $((0 != 1))"
|
||||
echo "0 > 1: $((0 > 1))"
|
||||
echo "0 >= 1: $((0 >= 1))"
|
||||
echo "0 < 0: $((0 < 0))"
|
||||
echo "0 <= 0: $((0 <= 0))"
|
||||
echo "0 == 0: $((0 == 0))"
|
||||
echo "0 != 0: $((0 != 0))"
|
||||
echo "0 > 0: $((0 > 0))"
|
||||
echo "0 >= 0: $((0 >= 0))"
|
||||
echo "1 < 0: $((1 < 0))"
|
||||
echo "1 <= 0: $((1 <= 0))"
|
||||
echo "1 == 0: $((1 == 0))"
|
||||
echo "1 != 0: $((1 != 0))"
|
||||
echo "1 > 0: $((1 > 0))"
|
||||
echo "1 >= 0: $((1 >= 0))"
|
||||
|
||||
- name: "Legacy bracket arithmetic syntax"
|
||||
stdin: |
|
||||
x=5
|
||||
x=$[x + 1]
|
||||
echo "x after increment: $x"
|
||||
echo "Direct: $[1 + 2]"
|
||||
|
||||
- name: "Legacy bracket arithmetic in extended test"
|
||||
stdin: |
|
||||
number=4
|
||||
[[ $[number % 2] == 0 ]] && echo "even" || echo "odd"
|
||||
number=5
|
||||
[[ $[number % 2] == 0 ]] && echo "even" || echo "odd"
|
||||
|
||||
- name: "Legacy bracket arithmetic in double-quoted string"
|
||||
stdin: |
|
||||
echo "Quoted: result: $[2 * 3]"
|
||||
|
||||
- name: "Legacy bracket arithmetic complex expression"
|
||||
stdin: |
|
||||
echo "Complex: $[1 + 2 * 3 - 4 / 2 % 2]"
|
||||
|
||||
- name: "Legacy bracket arithmetic with array subscript"
|
||||
stdin: |
|
||||
arr=(10 20 30)
|
||||
echo "Array elem: $[arr[1] + 5]"
|
||||
|
||||
- name: "Prefix increment/decrement with space before lvalue"
|
||||
stdin: |
|
||||
x=5
|
||||
echo "++ x => $(( ++ x ))"
|
||||
echo "x is now $x"
|
||||
echo "-- x => $(( -- x ))"
|
||||
echo "x is now $x"
|
||||
|
||||
# Also test in (( )) command form
|
||||
y=10
|
||||
(( ++ y ))
|
||||
echo "y after ++ y: $y"
|
||||
(( -- y ))
|
||||
echo "y after -- y: $y"
|
||||
|
||||
- name: "INT64_MIN and wrapping arithmetic"
|
||||
stdin: |
|
||||
# INT64_MIN via unary minus of the literal
|
||||
echo "$(( -9223372036854775808 ))"
|
||||
|
||||
# Wrapping negation of INT64_MIN
|
||||
echo "$(( - (-9223372036854775808) ))"
|
||||
|
||||
# INT64_MIN % -1 should be 0 (not crash)
|
||||
echo "$(( (-9223372036854775808) % (-1) ))"
|
||||
|
||||
# INT64_MIN / -1 wraps
|
||||
echo "$(( (-9223372036854775808) / (-1) ))"
|
||||
|
||||
# Wrapping increment/decrement near boundaries
|
||||
echo "$(( 9223372036854775807 + 1 ))" # wraps to INT64_MIN
|
||||
|
||||
- name: "Non-cyclic variable dereference chain"
|
||||
stdin: |
|
||||
a=b
|
||||
b=c
|
||||
c=42
|
||||
echo "$((a))"
|
||||
|
||||
- name: "Self-referencing variable in arithmetic"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
a=a
|
||||
echo "$(( a ))"
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "Recursive variable evaluation with depth limit"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
# Recursive variables should not cause stack overflow
|
||||
a=b
|
||||
b=a
|
||||
echo "$(( a ))"
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "Arithmetic with nameref variable"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
counter=10
|
||||
declare -n ref=counter
|
||||
echo "before: $counter"
|
||||
(( ref++ ))
|
||||
echo "after ++: $counter"
|
||||
(( ref += 5 ))
|
||||
echo "after +=5: $counter"
|
||||
echo "expr: $(( ref * 2 ))"
|
||||
@@ -0,0 +1,363 @@
|
||||
name: "Arrays"
|
||||
incompatible_configs: ["sh"]
|
||||
common_test_files:
|
||||
- path: "helpers.sh"
|
||||
contents: |
|
||||
stable_print_assoc_array() {
|
||||
# TODO(nameref): enable use of nameref when implemented; for now
|
||||
# we assume the name of the array is assoc_array
|
||||
# local -n assoc_array=$1
|
||||
local key
|
||||
|
||||
for key in $(printf "%s\n" "${!assoc_array[@]}" | sort -n); do
|
||||
echo "\"${key}\" => ${assoc_array[${key}]}"
|
||||
done
|
||||
}
|
||||
cases:
|
||||
- name: "Basic indexed array definition and access"
|
||||
stdin: |
|
||||
var=(a b c)
|
||||
echo "var: $var"
|
||||
echo "var: ${var}"
|
||||
echo "var[0]: ${var[0]}"
|
||||
echo "var[1]: ${var[1]}"
|
||||
echo "var[2]: ${var[2]}"
|
||||
echo "var[3]: ${var[3]}"
|
||||
echo "var[4]: ${var[4]}"
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Negative indexed array access"
|
||||
stdin: |
|
||||
var=(a b c)
|
||||
echo "var[-1]: ${var[-1]}"
|
||||
echo "var[-2]: ${var[-2]}"
|
||||
echo "var[-3]: ${var[-3]}"
|
||||
|
||||
- name: "Too large negative indexed array access"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
var=(a b c)
|
||||
echo "var[-4]: ${var[-4]}"
|
||||
|
||||
- name: "Negative indexed array access (set)"
|
||||
stdin: |
|
||||
var=(a b c d e f g)
|
||||
var[-1]=x
|
||||
var[-3]=z
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Too large negative indexed array access (set)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
var=(a b c)
|
||||
var[-4]=x
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Define array with expansion"
|
||||
stdin: |
|
||||
body='a b'
|
||||
|
||||
var=($body)
|
||||
declare -p var
|
||||
|
||||
declare -a var2=($body)
|
||||
declare -p var2
|
||||
|
||||
- name: "Arrays with empty values"
|
||||
stdin: |
|
||||
var1=(a "" c)
|
||||
declare -p var1
|
||||
|
||||
var2=(a "$nonexistent" c)
|
||||
declare -p var2
|
||||
|
||||
var3=("" "" "")
|
||||
declare -p var3
|
||||
|
||||
- name: "Basic associative array definition and access"
|
||||
stdin: |
|
||||
declare -A var=([0]=99 [a]=3 [b]=4 [c]=5)
|
||||
echo "var: $var"
|
||||
echo "var: ${var}"
|
||||
echo "var[0]: ${var[0]}"
|
||||
echo "var[a]: ${var[a]}"
|
||||
echo "var[b]: ${var[b]}"
|
||||
echo "var[c]: ${var[c]}"
|
||||
echo "var[d]: ${var[d]}"
|
||||
|
||||
key="a"
|
||||
echo "var[key]: ${var[${key}]}"
|
||||
|
||||
- name: "Array updating via index"
|
||||
stdin: |
|
||||
var=(a b c)
|
||||
var[1]="d"
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Array index assignment"
|
||||
stdin: |
|
||||
x=(3 2 1)
|
||||
y[${x[0]}]=10
|
||||
y[x[1]]=11
|
||||
declare -p y
|
||||
|
||||
- name: "Replacing array with string"
|
||||
stdin: |
|
||||
var=(a b c)
|
||||
var="x"
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Array replacing"
|
||||
stdin: |
|
||||
var=(a b c)
|
||||
var=(d e f)
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Appending array to indexed array"
|
||||
stdin: |
|
||||
var=(a b c)
|
||||
var+=(e f g)
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
var+=("" "" "")
|
||||
declare -p var
|
||||
|
||||
var+=(h "i" "$nonexistent" j)
|
||||
var+=(j k l)
|
||||
declare -p var
|
||||
|
||||
- name: "Appending array to declared-but-unset indexed array"
|
||||
stdin: |
|
||||
declare -a var
|
||||
var+=(e f g)
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Appending item to indexed array"
|
||||
stdin: |
|
||||
var=(a b c)
|
||||
var+=d
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Appending item to element of indexed array"
|
||||
stdin: |
|
||||
declare -a var=(1 2 3)
|
||||
var[1]+=2
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Appending item to element of int indexed array"
|
||||
stdin: |
|
||||
declare -ai var=(1 2 3)
|
||||
var[1]+=2
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Appending items to int indexed array"
|
||||
stdin: |
|
||||
declare -ai var=(1 2 3)
|
||||
var+=(4 value 6)
|
||||
declare -p var
|
||||
|
||||
- name: "Appending array to associative array"
|
||||
stdin: |
|
||||
source helpers.sh
|
||||
declare -A assoc_array=([a]=1 [b]=2 [c]=3)
|
||||
assoc_array+=([d]=4 [e]=5 [f]=6)
|
||||
stable_print_assoc_array assoc_array
|
||||
|
||||
- name: "Appending array to declared-but-unset associative array"
|
||||
stdin: |
|
||||
source helpers.sh
|
||||
declare -A assoc_array
|
||||
assoc_array+=([d]=4 [e]=5 [f]=6)
|
||||
stable_print_assoc_array assoc_array
|
||||
|
||||
- name: "Appending item to associative array"
|
||||
stdin: |
|
||||
source helpers.sh
|
||||
declare -A assoc_array=([a]=1 [b]=2 [c]=3)
|
||||
assoc_array+=d
|
||||
stable_print_assoc_array assoc_array
|
||||
|
||||
- name: "Appending item to element of associative array"
|
||||
stdin: |
|
||||
source helpers.sh
|
||||
declare -A assoc_array=([a]=1 [b]=2 [c]=3)
|
||||
assoc_array[b]+=2
|
||||
stable_print_assoc_array assoc_array
|
||||
|
||||
- name: "Appending item to element of int associative array"
|
||||
stdin: |
|
||||
source helpers.sh
|
||||
declare -Ai assoc_array=([a]=1 [b]=2 [c]=3)
|
||||
assoc_array[b]+=2
|
||||
stable_print_assoc_array assoc_array
|
||||
|
||||
- name: "Appending items to int associative array"
|
||||
stdin: |
|
||||
source helpers.sh
|
||||
declare -Ai assoc_array=([a]=1 [b]=2 [c]=3)
|
||||
assoc_array+=([d]=4 [e]=value [f]=6)
|
||||
stable_print_assoc_array assoc_array
|
||||
|
||||
- name: "Appending array to item"
|
||||
stdin: |
|
||||
var=x
|
||||
var+=(a b c)
|
||||
echo "var[*]: ${var[*]}"
|
||||
declare -p var
|
||||
|
||||
- name: "Fill associative array"
|
||||
stdin: |
|
||||
declare -Ag myarray
|
||||
|
||||
myfunc() {
|
||||
local myarg=$1 element
|
||||
shift
|
||||
for element in "$@"; do
|
||||
echo "Setting: myarray[$element]=$myarg"
|
||||
myarray[$element]="${myarg}${element}"
|
||||
done
|
||||
}
|
||||
|
||||
myfunc 'first' bunzip2 bzcat pbunzip2 pbzcat lbunzip2 lbzcat
|
||||
|
||||
- name: "Declare via array index"
|
||||
stdin: |
|
||||
declare -A assoc_array["key"]="value"
|
||||
declare -p assoc_array
|
||||
|
||||
declare -a indexed_array[5]="value"
|
||||
declare -p indexed_array
|
||||
|
||||
declare implicit_array[5]="value"
|
||||
declare -p implicit_array
|
||||
|
||||
declare -A no_value_assoc_array["key"]
|
||||
declare -p no_value_assoc_array
|
||||
|
||||
declare no_value_indexed_array[33]
|
||||
declare -p no_value_indexed_array
|
||||
|
||||
- name: "Create array via index update"
|
||||
stdin: |
|
||||
i=0
|
||||
my_array[i++]="c"
|
||||
echo "0: i=$i array=${my_array[@]}"
|
||||
my_array[i++]="b"
|
||||
echo "1: i=$i array=${my_array[@]}"
|
||||
my_array[i++]="a"
|
||||
echo "2: i=$i array=${my_array[@]}"
|
||||
|
||||
- name: "Expansion of array body"
|
||||
stdin: |
|
||||
elements="a b c"
|
||||
|
||||
array1=(${elements})
|
||||
declare -p array1
|
||||
|
||||
array2=("${elements}")
|
||||
declare -p array2
|
||||
|
||||
- name: "Copy array"
|
||||
stdin: |
|
||||
src=(a b c)
|
||||
|
||||
dest1=("${src[*]}")
|
||||
declare -p dest1
|
||||
|
||||
dest2=(${src[*]})
|
||||
declare -p dest2
|
||||
|
||||
dest3=("${src[@]}")
|
||||
declare -p dest3
|
||||
|
||||
dest4=(${src[@]})
|
||||
declare -p dest4
|
||||
|
||||
- name: "Multi-line definition of array"
|
||||
stdin: |
|
||||
array=(
|
||||
a
|
||||
b
|
||||
c
|
||||
)
|
||||
declare -p array
|
||||
|
||||
- name: "Dump values from non-existent array"
|
||||
stdin: |
|
||||
for key in ${NONEXISTENT[@]}; do
|
||||
echo "@-Key: ${key}"
|
||||
done
|
||||
for key in "${NONEXISTENT[@]}"; do
|
||||
echo "@-Quoted Key: ${key}"
|
||||
done
|
||||
|
||||
for key in ${NONEXISTENT[*]}; do
|
||||
echo "*-Key: ${key}"
|
||||
done
|
||||
for key in "${NONEXISTENT[*]}"; do
|
||||
echo "*-Quoted Key: ${key}"
|
||||
done
|
||||
|
||||
- name: "Use non-existent var in indexed array index"
|
||||
stdin: |
|
||||
declare -a myarray=(10)
|
||||
echo "Value: ${myarray[$non_existent_var]}"
|
||||
|
||||
- name: "Use non-existent var in associative array index"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
declare -A myarray=([0]="default" ["a"]="other")
|
||||
echo "Value: ${myarray[$non_existent_var]}"
|
||||
|
||||
- name: "Nested array reference"
|
||||
stdin: |
|
||||
x=(2 3 4)
|
||||
echo ${x[${x[0]}]}
|
||||
|
||||
- name: "Append array-resulting expansion to array"
|
||||
stdin: |
|
||||
declare -a otherarray=("a" "b" "c")
|
||||
declare -a ourarray=()
|
||||
|
||||
ourarray+=("${otherarray[@]/a/x}")
|
||||
declare -p otherarray
|
||||
declare -p ourarray
|
||||
|
||||
- name: "Array declaration in a function"
|
||||
stdin: |
|
||||
outer() {
|
||||
inner=()
|
||||
if test true; then
|
||||
echo true
|
||||
else
|
||||
echo false
|
||||
fi
|
||||
}
|
||||
|
||||
outer
|
||||
|
||||
- name: "Array element expansion with non-standard IFS"
|
||||
stdin: |
|
||||
IFS='|'
|
||||
|
||||
declare -a myarray=(abc 1 f)
|
||||
echo "\"\${myarray[@]}\": ${myarray[@]}"
|
||||
echo "\"\${myarray[*]}\": ${myarray[*]}"
|
||||
echo "\${myarray[@]}:" ${myarray[@]}
|
||||
echo "\${myarray[*]}:" ${myarray[*]}
|
||||
|
||||
- name: "Empty array with newline"
|
||||
stdin: |
|
||||
declare -a myarray=(
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
name: "Assignments"
|
||||
cases:
|
||||
- name: "First char is equals sign"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
=x
|
||||
|
||||
- name: "Basic assignment"
|
||||
stdin: |
|
||||
x=yz
|
||||
echo "x: ${x}"
|
||||
|
||||
- name: "Invalid variable name"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
@=something
|
||||
|
||||
- name: "Quoted equals sign"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
x"="3
|
||||
|
||||
- name: "Multiple equals signs"
|
||||
stdin: |
|
||||
x=y=z
|
||||
echo "x: ${x}"
|
||||
|
||||
- name: "Assignment with tilde expansion"
|
||||
env:
|
||||
HOME: /some/dir
|
||||
stdin: |
|
||||
# Basic tilde at start (currently works)
|
||||
var=~/file1.txt
|
||||
echo "~/file1.txt: ${var}"
|
||||
|
||||
# Tilde after colon in assignment (currently broken - doesn't expand second tilde)
|
||||
var=~/file1.txt:~/file2.txt
|
||||
echo "~/file1.txt:~/file2.txt: ${var}"
|
||||
|
||||
# Multiple tildes after colons
|
||||
var=~:~/bin:~/local/bin
|
||||
echo "~:~/bin:~/local/bin: ${var}"
|
||||
|
||||
# Mixed with regular paths
|
||||
PATH=/usr/bin:~:/bin:~/local
|
||||
echo "PATH-like: ${PATH}"
|
||||
|
||||
- name: "Array assignment with tilde expansion"
|
||||
env:
|
||||
HOME: /testhome
|
||||
stdin: |
|
||||
# Array elements with tildes
|
||||
arr=(~ ~/bin ~root)
|
||||
echo "Array elements:"
|
||||
for i in "${arr[@]}"; do
|
||||
echo " $i"
|
||||
done
|
||||
|
||||
# Array elements with colon-tilde
|
||||
arr2=(~:~/bin /usr/bin:~)
|
||||
echo "Array with colons:"
|
||||
for i in "${arr2[@]}"; do
|
||||
echo " $i"
|
||||
done
|
||||
|
||||
- name: "Append assignment with tilde expansion"
|
||||
env:
|
||||
HOME: /testhome
|
||||
stdin: |
|
||||
# Basic append
|
||||
PATH=/usr/bin
|
||||
PATH+=:~
|
||||
echo "After append tilde: ${PATH}"
|
||||
|
||||
# Append with colon-tilde
|
||||
PATH=/usr/bin
|
||||
PATH+=:~:~/local
|
||||
echo "After append colon-tilde: ${PATH}"
|
||||
@@ -0,0 +1,89 @@
|
||||
name: "Background jobs"
|
||||
cases:
|
||||
# === Implicit wait on exit: shell must wait for bg jobs before terminating ===
|
||||
# These use file side-effects rather than stdout to avoid racy output ordering.
|
||||
|
||||
- name: "Single background job via -c without explicit wait"
|
||||
skip: true # Issue #1079
|
||||
args:
|
||||
- "-c"
|
||||
- "touch bg_done &"
|
||||
|
||||
- name: "Single background job via stdin without explicit wait"
|
||||
skip: true # Issue #1079
|
||||
stdin: |
|
||||
touch bg_done &
|
||||
|
||||
- name: "Background compound command via -c without explicit wait"
|
||||
skip: true # Issue #1079
|
||||
args:
|
||||
- "-c"
|
||||
- "{ touch a_done; touch b_done; } &"
|
||||
|
||||
- name: "Background compound command via stdin without explicit wait"
|
||||
skip: true # Issue #1079
|
||||
stdin: |
|
||||
{ touch a_done; touch b_done; } &
|
||||
|
||||
- name: "Multiple background jobs via -c without explicit wait"
|
||||
skip: true # Issue #1079
|
||||
args:
|
||||
- "-c"
|
||||
- "touch one & touch two & touch three &"
|
||||
|
||||
- name: "Multiple background jobs via stdin without explicit wait"
|
||||
skip: true # Issue #1079
|
||||
stdin: |
|
||||
touch one & touch two & touch three &
|
||||
|
||||
- name: "Background and foreground via -c without explicit wait"
|
||||
skip: true # Issue #1079
|
||||
args:
|
||||
- "-c"
|
||||
- "touch bg_done & touch fg_done"
|
||||
|
||||
- name: "Background and foreground via stdin without explicit wait"
|
||||
skip: true # Issue #1079
|
||||
stdin: |
|
||||
touch bg_done & touch fg_done
|
||||
|
||||
# === Explicit wait ===
|
||||
|
||||
- name: "Background job side effect observed after wait via -c"
|
||||
args:
|
||||
- "-c"
|
||||
- "touch bg_done & wait; test -f bg_done && echo exists"
|
||||
|
||||
- name: "Background job side effect observed after wait via stdin"
|
||||
stdin: |
|
||||
touch bg_done &
|
||||
wait
|
||||
test -f bg_done && echo exists
|
||||
|
||||
- name: "Single background job with explicit wait via -c"
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
echo hello &
|
||||
wait
|
||||
|
||||
- name: "Single background job with explicit wait via stdin"
|
||||
stdin: |
|
||||
echo hello &
|
||||
wait
|
||||
|
||||
- name: "Two sequential background jobs with explicit wait via -c"
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
echo 1 &
|
||||
wait
|
||||
echo 2 &
|
||||
wait
|
||||
|
||||
- name: "Two sequential background jobs with explicit wait via stdin"
|
||||
stdin: |
|
||||
echo 1 &
|
||||
wait
|
||||
echo 2 &
|
||||
wait
|
||||
@@ -0,0 +1,132 @@
|
||||
name: "Basic tests"
|
||||
cases:
|
||||
- name: "Basic -c usage"
|
||||
args:
|
||||
- "-c"
|
||||
- "echo hi"
|
||||
|
||||
- name: "Basic -c usage: output"
|
||||
stdin: |
|
||||
# Capture raw output of command
|
||||
raw_output=$($0 -c 'echo hi')
|
||||
|
||||
# Dump raw output as characters
|
||||
echo "[Character output]"
|
||||
for ((i = 0; i < ${#raw_output}; i++)); do
|
||||
char="${raw_output:i:1}"
|
||||
printf '%c' "$char"
|
||||
done
|
||||
printf '\n\n'
|
||||
|
||||
# Dump raw output as hex
|
||||
echo "[Hex output]"
|
||||
for ((i = 0; i < ${#raw_output}; i++)); do
|
||||
if (( i % 16 == 0 )); then
|
||||
printf '%04x: ' "$i"
|
||||
fi
|
||||
|
||||
char="${raw_output:i:1}"
|
||||
printf '%02x ' "'$char"
|
||||
|
||||
if (( (i + 1) % 16 == 0 )); then
|
||||
printf '\n'
|
||||
fi
|
||||
done
|
||||
|
||||
printf '\n'
|
||||
|
||||
- name: "Basic stdin usage"
|
||||
stdin: |
|
||||
echo hi
|
||||
|
||||
- name: "Basic sequence"
|
||||
stdin: |
|
||||
echo 'hi'; echo 'there'
|
||||
|
||||
- name: "Basic -s usage with -c"
|
||||
args: ["-s", "-c", "echo hi1 hi2"]
|
||||
stdin: |
|
||||
[[ $(type -P $0) == $(type -P $BASH) ]] && echo '$0 matches $BASH'
|
||||
echo "\$1: $1"
|
||||
echo "\$2: $2"
|
||||
echo "\$3: $3"
|
||||
|
||||
- name: "Basic -s usage with positional args"
|
||||
args: ["-s", "arg1", "arg2", "arg3"]
|
||||
stdin: |
|
||||
[[ $(type -P $0) == $(type -P $BASH) ]] && echo '$0 matches $BASH'
|
||||
echo "\$1: $1"
|
||||
echo "\$2: $2"
|
||||
echo "\$3: $3"
|
||||
|
||||
- name: "Basic -i usage with commands via stdin"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo hi
|
||||
echo there
|
||||
|
||||
- name: "Basic script execution"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "ARGS: $@"
|
||||
exit 22
|
||||
args: ["./script.sh", 1, 2, 3]
|
||||
|
||||
- name: "Passing option-like arg to script"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "ARGS: $@"
|
||||
exit 22
|
||||
args: ["./script.sh", "-v"]
|
||||
|
||||
- name: "Ensure ~ is resolvable"
|
||||
stdin: "test ~"
|
||||
|
||||
- name: "Rc file loading (interactive)"
|
||||
ignore_stderr: true
|
||||
removed_default_args: ["--norc"] # Need to cancel this out so we can override
|
||||
args: ["--rcfile", "testrc", "-i"]
|
||||
env:
|
||||
PS1: ""
|
||||
test_files:
|
||||
- path: "testrc"
|
||||
contents: |
|
||||
echo "Executing in rc file"
|
||||
stdin: |
|
||||
echo "Executing in main script body"
|
||||
|
||||
- name: "Rc file loading (non-interactive)"
|
||||
ignore_stderr: true
|
||||
removed_default_args: ["--norc"] # Need to cancel this out so we can override
|
||||
args: ["--rcfile", "testrc"]
|
||||
env:
|
||||
PS1: ""
|
||||
test_files:
|
||||
- path: "testrc"
|
||||
contents: |
|
||||
echo "Executing in rc file"
|
||||
stdin: |
|
||||
echo "Executing in main script body"
|
||||
|
||||
- name: "Missing rc file"
|
||||
ignore_stderr: true
|
||||
removed_default_args: ["--norc"] # Need to cancel this out so we can override
|
||||
args: ["--rcfile", "non-existent-file", "-i"]
|
||||
env:
|
||||
PS1: ""
|
||||
stdin: |
|
||||
echo "Executing in main script body"
|
||||
|
||||
- name: "Unknown option"
|
||||
ignore_stderr: true
|
||||
args: ["--unknown-option", "-c", "echo hi"]
|
||||
|
||||
- name: "-f on the command line"
|
||||
args: ["-f"]
|
||||
stdin: |
|
||||
touch abc
|
||||
touch abd
|
||||
echo a*
|
||||
@@ -0,0 +1,33 @@
|
||||
name: "Builtins: alias"
|
||||
cases:
|
||||
- name: "Basic alias usage"
|
||||
stdin: |
|
||||
shopt -s expand_aliases
|
||||
alias myalias=echo
|
||||
alias
|
||||
myalias 'hello'
|
||||
|
||||
- name: "Alias with trailing space"
|
||||
known_failure: true # Issue #57
|
||||
stdin: |
|
||||
shopt -s expand_aliases
|
||||
alias cmd='echo '
|
||||
alias other='replaced '
|
||||
alias otherother='also-replaced'
|
||||
|
||||
cmd other otherother
|
||||
|
||||
- name: "Alias referencing to alias"
|
||||
known_failure: true # Issue #57
|
||||
stdin: |
|
||||
shopt -s expand_aliases
|
||||
alias myalias=echo
|
||||
alias outeralias=myalias
|
||||
outeralias 'hello'
|
||||
|
||||
- name: "Alias to keywords"
|
||||
known_failure: true # Issue #286
|
||||
stdin: |
|
||||
shopt -s expand_aliases
|
||||
alias myalias=if
|
||||
myalias true; then echo "true"; fi
|
||||
@@ -0,0 +1,160 @@
|
||||
name: "Builtins: bind"
|
||||
cases:
|
||||
- name: "bind succeeds in non-interactive mode"
|
||||
stdin: |
|
||||
bind > /dev/null 2>&1
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "bind -l"
|
||||
args: ["run.sh"]
|
||||
removed_default_args: ["--input-backend=basic"]
|
||||
additional_test_args: ["--input-backend=reedline"]
|
||||
ignore_stderr: true
|
||||
min_oracle_version: "5.3" # Added functions in 5.3
|
||||
test_files:
|
||||
- path: "run.sh"
|
||||
contents: |
|
||||
# List bindable actions but ignore brush custom entries
|
||||
echo "[bind -l]"
|
||||
bind -l | grep -v brush- | sort
|
||||
echo "bind -l result: ${PIPESTATUS[@]}"
|
||||
|
||||
- name: "bind -q/-P/-p"
|
||||
args: ["run.sh"]
|
||||
removed_default_args: ["--input-backend=basic"]
|
||||
additional_test_args: ["--input-backend=reedline"]
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "run.sh"
|
||||
contents: |
|
||||
# Basic-check some known bindings
|
||||
well_known_func="clear-screen"
|
||||
|
||||
echo "[bind -q]"
|
||||
bind -q ${well_known_func}
|
||||
echo "bind -q result: ${PIPESTATUS[@]}"
|
||||
|
||||
echo "[bind -P]"
|
||||
bind -P | grep ${well_known_func}
|
||||
echo "bind -P result: ${PIPESTATUS[@]}"
|
||||
|
||||
echo "[bind -p]"
|
||||
bind -p | grep ${well_known_func}
|
||||
echo "bind -p result: ${PIPESTATUS[@]}"
|
||||
|
||||
- name: "bind -x/-X/-r/-u"
|
||||
args: ["run.sh"]
|
||||
removed_default_args: ["--input-backend=basic"]
|
||||
additional_test_args: ["--input-backend=reedline"]
|
||||
min_oracle_version: "5.3" # -X output changed in 5.3
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "run.sh"
|
||||
contents: |
|
||||
# Bind a key and check it
|
||||
echo "[bind -x]"
|
||||
bind -x '"\C-t":echo hi'
|
||||
echo "bind -x result: $?"
|
||||
|
||||
echo "[bind -X]"
|
||||
bind -X
|
||||
echo "bind -X result: $?"
|
||||
|
||||
# Unbind it via -r
|
||||
echo "[bind -r]"
|
||||
bind -r '\C-t'
|
||||
echo "bind -r result: $?"
|
||||
bind -X
|
||||
echo "bind -X result: $?"
|
||||
echo "[bind -r (again)]"
|
||||
bind -r '\C-t'
|
||||
echo "bind -r (again) result: $?"
|
||||
|
||||
# Unbind via -u
|
||||
well_known_func="clear-screen"
|
||||
echo "[bind -u]"
|
||||
bind -u ${well_known_func}
|
||||
echo "bind -u result: $?"
|
||||
echo "[bind -q (after -u)]"
|
||||
bind -q ${well_known_func}
|
||||
echo "bind -q (after -u) result: $?"
|
||||
|
||||
- name: "bind keyseq"
|
||||
args: ["run.sh"]
|
||||
removed_default_args: ["--input-backend=basic"]
|
||||
additional_test_args: ["--input-backend=reedline"]
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "run.sh"
|
||||
contents: |
|
||||
well_known_func="clear-screen"
|
||||
bind -u ${well_known_func} # Ensure it's unbound first
|
||||
|
||||
# Bind a keyseq
|
||||
echo "[bind keyseq]"
|
||||
bind '"\C-t":clear-screen'
|
||||
echo "bind keyseq result: $?"
|
||||
|
||||
# List current bindings
|
||||
bind -q ${well_known_func}
|
||||
echo "bind -q result: $?"
|
||||
|
||||
- name: "bind macro 1"
|
||||
args: ["run.sh"]
|
||||
removed_default_args: ["--input-backend=basic"]
|
||||
additional_test_args: ["--input-backend=reedline"]
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "run.sh"
|
||||
contents: |
|
||||
key_to_bind='\C-r'
|
||||
|
||||
echo "[bind -S (pre)]"
|
||||
bind -S | grep "${key_to_bind}"
|
||||
echo "bind -S result: ${PIPESTATUS[@]}"
|
||||
|
||||
echo "[bind -s (pre)]"
|
||||
bind -s | grep "${key_to_bind}"
|
||||
echo "bind -s result: ${PIPESTATUS[@]}"
|
||||
|
||||
echo "[bind macro]"
|
||||
bind '"\C-r": "\e[0;1A\e[0;0A"'
|
||||
echo "bind macro result: $?"
|
||||
|
||||
echo "[bind -S]"
|
||||
bind -S | grep "${key_to_bind}"
|
||||
echo "bind -S result: ${PIPESTATUS[@]}"
|
||||
|
||||
echo "[bind -s]"
|
||||
bind -s | grep "${key_to_bind}"
|
||||
echo "bind -s result: ${PIPESTATUS[@]}"
|
||||
|
||||
echo "[bind -r]"
|
||||
bind -r "${key_to_bind}"
|
||||
echo "bind -r result: ${PIPESTATUS[@]}"
|
||||
|
||||
echo "[bind -s (after -r)]"
|
||||
bind -s | grep "${key_to_bind}"
|
||||
echo "bind -s (after -r) result: ${PIPESTATUS[@]}"
|
||||
|
||||
echo "[bind -S (after -r)]"
|
||||
bind -S | grep "${key_to_bind}"
|
||||
echo "bind -S (after -r) result: ${PIPESTATUS[@]}"
|
||||
|
||||
- name: "bind macro 2"
|
||||
args: ["run.sh"]
|
||||
removed_default_args: ["--input-backend=basic"]
|
||||
additional_test_args: ["--input-backend=reedline"]
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "run.sh"
|
||||
contents: |
|
||||
key_to_bind='\ec'
|
||||
|
||||
echo "[bind macro]"
|
||||
bind '"\ec": " \C-b\C-k`something`\e\C-e"'
|
||||
echo "bind macro result: $?"
|
||||
|
||||
echo "[bind -S]"
|
||||
bind -S | grep "${key_to_bind}"
|
||||
echo "bind -S result: ${PIPESTATUS[@]}"
|
||||
@@ -0,0 +1,44 @@
|
||||
name: "Builtins: builtin"
|
||||
cases:
|
||||
- name: "builtin with no builtin"
|
||||
stdin: builtin
|
||||
|
||||
- name: "builtin with unknown builtin"
|
||||
ignore_stderr: true
|
||||
stdin: builtin not-a-builtin args
|
||||
|
||||
- name: "valid builtin"
|
||||
stdin: builtin echo "Hello world"
|
||||
|
||||
- name: "valid builtin with hyphen args"
|
||||
stdin: builtin echo -e "Hello\nWorld"
|
||||
|
||||
- name: "builtin passing through results"
|
||||
stdin: |
|
||||
builtin false; echo "builtin false => $?"
|
||||
builtin true; echo "builtin true => $?"
|
||||
|
||||
- name: "builtin with non-decl builtin"
|
||||
stdin: |
|
||||
builtin echo variable=value
|
||||
|
||||
- name: "builtin with decl builtin"
|
||||
stdin: |
|
||||
builtin declare variable=value
|
||||
declare -p variable
|
||||
|
||||
- name: "builtin with disabled builtin"
|
||||
ignore_stderr: true
|
||||
incompatible_os:
|
||||
- opensuse-tumbleweed
|
||||
stdin: |
|
||||
cd .
|
||||
echo $?
|
||||
|
||||
enable -n cd
|
||||
builtin cd .
|
||||
echo $?
|
||||
|
||||
enable cd
|
||||
builtin cd .
|
||||
echo $?
|
||||
@@ -0,0 +1,67 @@
|
||||
name: "caller builtin"
|
||||
cases:
|
||||
- name: "caller in function"
|
||||
test_files:
|
||||
- path: script.sh
|
||||
contents: |
|
||||
function outer() {
|
||||
echo "In outer"
|
||||
inner
|
||||
}
|
||||
|
||||
function inner() {
|
||||
echo "In inner"
|
||||
|
||||
echo "[Calling caller 0]"
|
||||
caller 0
|
||||
|
||||
echo "[Calling caller 1]"
|
||||
caller 1
|
||||
}
|
||||
stdin: |
|
||||
source script.sh
|
||||
outer
|
||||
|
||||
- name: "caller no args"
|
||||
known_failure: true # TODO(caller): needs investigation
|
||||
test_files:
|
||||
- path: script.sh
|
||||
contents: |
|
||||
function myfunc() {
|
||||
echo "In myfunc"
|
||||
caller
|
||||
}
|
||||
stdin: |
|
||||
source script.sh
|
||||
myfunc
|
||||
|
||||
- name: "caller no args (nested)"
|
||||
test_files:
|
||||
- path: script.sh
|
||||
contents: |
|
||||
function myfunc() {
|
||||
echo "In myfunc"
|
||||
caller
|
||||
}
|
||||
|
||||
function myouterfunc() {
|
||||
echo "In myouterfunc"
|
||||
myfunc
|
||||
}
|
||||
stdin: |
|
||||
source script.sh
|
||||
myouterfunc
|
||||
|
||||
- name: "caller invalid expr"
|
||||
ignore_stderr: true # Different error messages
|
||||
stdin: |
|
||||
f() { caller x; }
|
||||
f
|
||||
|
||||
- name: "caller out of range"
|
||||
stdin: |
|
||||
caller 100
|
||||
|
||||
- name: "caller not in function"
|
||||
stdin: |
|
||||
caller
|
||||
@@ -0,0 +1,171 @@
|
||||
name: "Builtins: cd"
|
||||
cases:
|
||||
- name: "Basic cd usage"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "cd /"
|
||||
cd /
|
||||
echo $?
|
||||
echo "pwd: $PWD"
|
||||
|
||||
echo "cd usr"
|
||||
cd usr
|
||||
echo $?
|
||||
echo "pwd: $PWD"
|
||||
|
||||
echo "cd"
|
||||
cd
|
||||
echo $?
|
||||
echo "pwd: $PWD"
|
||||
|
||||
- name: "cd to file"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "file"
|
||||
contents: "file contents"
|
||||
stdin: |
|
||||
ls -1
|
||||
cd file
|
||||
echo $?
|
||||
ls -1
|
||||
|
||||
- name: "cd -"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
cd /
|
||||
echo "pwd: $PWD"
|
||||
cd usr
|
||||
echo "pwd: $PWD"
|
||||
echo "oldpwd: $OLDPWD"
|
||||
cd -
|
||||
echo $?
|
||||
echo "pwd: $PWD"
|
||||
|
||||
- name: "cd ~"
|
||||
stdin: |
|
||||
mkdir ./my_home
|
||||
export HOME="$(realpath ./my_home)"
|
||||
echo "Set HOME: $(basename $HOME)"
|
||||
(
|
||||
echo "Subshell 1: HOME=$(basename $HOME)"
|
||||
cd ~
|
||||
echo "pwd: $(basename $PWD)"
|
||||
)
|
||||
(
|
||||
echo "Subshell 2: HOME=$(basename $HOME)"
|
||||
cd -L ~
|
||||
echo "pwd: $(basename $PWD)"
|
||||
)
|
||||
(
|
||||
echo "Subshell 3: HOME=$(basename $HOME)"
|
||||
cd -P ~
|
||||
echo "pwd: $(basename $PWD)"
|
||||
)
|
||||
|
||||
- name: "cd with symlink"
|
||||
stdin: |
|
||||
mkdir -p ./level1/level2/level3
|
||||
cd level1
|
||||
ln -s ./level2/level3 ./symlink
|
||||
|
||||
# -L by default
|
||||
(
|
||||
cd ./symlink
|
||||
echo "$(basename $PWD)"
|
||||
cd ..
|
||||
echo "$(basename $PWD)"
|
||||
)
|
||||
|
||||
- name: "cd -L"
|
||||
stdin: |
|
||||
mkdir -p ./level1/level2/level3
|
||||
cd level1
|
||||
ln -s ./level2/level3 ./symlink
|
||||
|
||||
echo "[Case 1]"
|
||||
(
|
||||
cd ./symlink
|
||||
echo "$(basename $PWD)"
|
||||
cd -L ..
|
||||
echo "$(basename $PWD)"
|
||||
)
|
||||
|
||||
echo "[Case 2]"
|
||||
(
|
||||
cd -L ./symlink
|
||||
echo "$(basename $PWD)"
|
||||
cd ..
|
||||
echo "$(basename $PWD)"
|
||||
)
|
||||
|
||||
# without pwd
|
||||
echo "[Case 3]"
|
||||
(
|
||||
cd -L ./symlink
|
||||
echo "$(basename $PWD)"
|
||||
export PWD=
|
||||
cd -L ..
|
||||
echo "$(basename $PWD)"
|
||||
)
|
||||
|
||||
echo "[Case 4]"
|
||||
(
|
||||
cd -L ./symlink
|
||||
export PWD=
|
||||
# start a shell without $PWD
|
||||
(
|
||||
cd .
|
||||
echo "$(basename $PWD)"
|
||||
)
|
||||
)
|
||||
|
||||
- name: "cd -P"
|
||||
stdin: |
|
||||
mkdir -p ./level1/level2/level3
|
||||
cd level1
|
||||
ln -s ./level2/level3 ./symlink
|
||||
|
||||
echo "[Case 1]"
|
||||
(
|
||||
cd ./symlink
|
||||
echo "cd ./symlink => $(basename $PWD)"
|
||||
cd -P ..
|
||||
echo "cd -P .. => $(basename $PWD)"
|
||||
)
|
||||
|
||||
echo "[Case 2]"
|
||||
(
|
||||
cd -P ./symlink
|
||||
echo "cd -P ./symlink => $(basename $PWD)"
|
||||
cd ..
|
||||
echo "cd .. => $(basename $PWD)"
|
||||
)
|
||||
|
||||
- name: "OLDPWD"
|
||||
env:
|
||||
OLDPWD: "/nonexistent/path"
|
||||
stdin: |
|
||||
mkdir subdir
|
||||
|
||||
echo "[0]"
|
||||
echo ${OLDPWD+set}
|
||||
|
||||
echo "[1]"
|
||||
echo ${OLDPWD-NOTSET}
|
||||
|
||||
echo "[2]"
|
||||
declare -p OLDPWD 2>&1 | grep -o "declare -[a-z]* OLDPWD"
|
||||
|
||||
echo "[3]"
|
||||
env | grep "^OLDPWD=" || echo "NOT_IN_ENV"
|
||||
|
||||
echo
|
||||
echo "[Unsetting + cd'ing...]"
|
||||
unset OLDPWD
|
||||
cd $PWD/subdir
|
||||
|
||||
echo "[4]"
|
||||
declare -p OLDPWD 2>&1 | grep -o "declare -[a-z]* OLDPWD"
|
||||
|
||||
echo "[5]"
|
||||
env | grep -o "^OLDPWD="
|
||||
@@ -0,0 +1,13 @@
|
||||
name: "Builtins: colon"
|
||||
cases:
|
||||
- name: "Basic colon usage"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
:
|
||||
echo "Result 1: $?"
|
||||
: something
|
||||
echo "Result 2: $?"
|
||||
: --anything=here or here
|
||||
echo "Result 3: $?"
|
||||
: --help
|
||||
echo "Result 4: $?"
|
||||
@@ -0,0 +1,88 @@
|
||||
name: "Builtins: command"
|
||||
cases:
|
||||
- name: "Basic command usage"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "Executing echo built-in"
|
||||
command echo "Hello"
|
||||
|
||||
echo "Executing ls using name"
|
||||
command ls -d /
|
||||
|
||||
echo "Executing ls using absolute path"
|
||||
command $(which ls) -d /
|
||||
|
||||
echo "Executing non-existent command by name"
|
||||
command non-existent
|
||||
|
||||
echo "Executing non-existent command by path"
|
||||
command /usr/bin/non-existent
|
||||
|
||||
- name: "Non-executable command"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
mkdir subdir
|
||||
|
||||
echo "Before executing"
|
||||
./subdir
|
||||
echo "After executing: result=$?"
|
||||
|
||||
echo "Before and-or list execution"
|
||||
./subdir || true
|
||||
echo "After and-or list execution: result=$?"
|
||||
|
||||
- name: "command -v"
|
||||
stdin: |
|
||||
echo "PATH: $PATH"
|
||||
|
||||
echo "[echo]"
|
||||
command -v echo
|
||||
|
||||
echo "[non-existent]"
|
||||
command -v non-existent || echo "1. Not found"
|
||||
|
||||
echo "[/usr/bin/non-existent]"
|
||||
command -v /usr/bin/non-existent || echo "2. Not found"
|
||||
|
||||
- name: "command -v -p"
|
||||
stdin: |
|
||||
unset PATH
|
||||
|
||||
echo "[no -p]"
|
||||
command -v cat
|
||||
|
||||
echo "[-p]"
|
||||
command -v -p cat
|
||||
|
||||
- name: "command -v with full paths"
|
||||
stdin: |
|
||||
echo "[cat]"
|
||||
command -v cat
|
||||
|
||||
echo "[\$(command -v cat)]"
|
||||
command -v $(command -v cat)
|
||||
|
||||
- name: "command -V"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
command -V echo
|
||||
command -V ls
|
||||
command -V $(which ls)
|
||||
|
||||
command -V non-existent || echo "1. Not found"
|
||||
command -V /usr/bin/non-existent || echo "2. Not found"
|
||||
|
||||
- name: "command with --"
|
||||
stdin: |
|
||||
command -- ls
|
||||
|
||||
- name: "command with --help"
|
||||
stdin: |
|
||||
command ls --help
|
||||
|
||||
- name: "command with -p"
|
||||
ignore_stderr: true # In case it fails on the oracle as well, ignore the specific error message.
|
||||
stdin: |
|
||||
unset PATH
|
||||
|
||||
command -p -- ls
|
||||
@@ -0,0 +1,20 @@
|
||||
name: "Builtin Common Tests"
|
||||
cases:
|
||||
- name: "Piping builtin output"
|
||||
stdin: |
|
||||
shopt | wc -l | wc -l
|
||||
|
||||
- name: "Redirecting builtin output"
|
||||
stdin: |
|
||||
declare my_variable=10
|
||||
declare -p my_variable >out.txt
|
||||
|
||||
echo "Dumping file contents..."
|
||||
cat out.txt
|
||||
|
||||
- name: "Overrides"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
declare -p myvar
|
||||
myvar=10 declare -p myvar
|
||||
declare -p myvar
|
||||
@@ -0,0 +1,309 @@
|
||||
name: "Builtins: compgen"
|
||||
cases:
|
||||
- name: "compgen -A alias"
|
||||
stdin: |
|
||||
alias myalias="ls"
|
||||
alias myalias2="echo hi"
|
||||
|
||||
compgen -A alias myalias | sort
|
||||
|
||||
- name: "compgen -A binding"
|
||||
stdin: |
|
||||
compgen -A binding tab | sort
|
||||
|
||||
- name: "compgen -A builtin"
|
||||
stdin: |
|
||||
compgen -A builtin cd | sort
|
||||
|
||||
- name: "compgen -A directory"
|
||||
stdin: |
|
||||
touch somefile
|
||||
mkdir somedir
|
||||
mkdir somedir2
|
||||
|
||||
compgen -A directory some | sort
|
||||
|
||||
- name: "compgen -A file"
|
||||
stdin: |
|
||||
touch somefile
|
||||
mkdir somedir
|
||||
mkdir somedir2
|
||||
|
||||
compgen -A file some | sort
|
||||
|
||||
- name: "compgen -A file with dot files"
|
||||
stdin: |
|
||||
touch .file
|
||||
mkdir .dir
|
||||
|
||||
echo "[without dotglob]"
|
||||
shopt -u dotglob
|
||||
compgen -A file | sort
|
||||
|
||||
echo "[with dotglob]"
|
||||
shopt -s dotglob
|
||||
compgen -A file | sort
|
||||
|
||||
- name: "compgen -A function"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
echo hi
|
||||
}
|
||||
|
||||
myfunc2() {
|
||||
echo hello
|
||||
}
|
||||
|
||||
compgen -A function myfunc | sort
|
||||
|
||||
- name: "compgen -A keyword"
|
||||
stdin: |
|
||||
compgen -A keyword esa | sort
|
||||
|
||||
- name: "compgen -A variable"
|
||||
stdin: |
|
||||
declare myvar=10
|
||||
declare myvar2=11
|
||||
|
||||
compgen -A variable myvar | sort
|
||||
|
||||
- name: "compgen -W"
|
||||
stdin: |
|
||||
echo "1. Basic compgen -W"
|
||||
compgen -W "one two three" -- t | sort
|
||||
echo "2. compgen -W with expansion"
|
||||
myvar=value compgen -W '${myvar}'
|
||||
|
||||
- name: "compgen -W does not pathname-expand"
|
||||
stdin: |
|
||||
touch foo.txt bar.txt baz.log
|
||||
|
||||
echo "[glob pattern should be literal]"
|
||||
compgen -W '*.txt' | sort
|
||||
|
||||
echo "[question mark glob should be literal]"
|
||||
compgen -W '?.log' | sort
|
||||
|
||||
- name: "compgen -W with unsorted values"
|
||||
stdin: |
|
||||
compgen -W 'c b d a'
|
||||
|
||||
- name: "compgen -W with options"
|
||||
stdin: |
|
||||
compgen -W '--abc --def' -- '--ab'
|
||||
|
||||
- name: "compgen with no matches"
|
||||
stdin: |
|
||||
compgen -W yes no && echo "1. Result"
|
||||
|
||||
- name: "compgen -f with tilde"
|
||||
home_dir: testhome # relative to default working dir
|
||||
stdin: |
|
||||
mkdir testhome && cd testhome
|
||||
|
||||
echo "HOME base: $(basename $HOME)"
|
||||
echo "~ base:" $(basename ~)
|
||||
|
||||
touch item1
|
||||
|
||||
echo "[0]"
|
||||
for p in $(compgen -f ~); do
|
||||
echo ${p//$HOME/HOME}
|
||||
done
|
||||
|
||||
echo "[1]"
|
||||
for p in $(compgen -f ~/); do
|
||||
echo ${p//$HOME/HOME}
|
||||
done
|
||||
|
||||
- name: "compgen -f with quoted tilde"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
touch item1
|
||||
HOME=$(pwd)
|
||||
|
||||
echo "[0]"
|
||||
for p in $(compgen -f '~/'); do
|
||||
echo ${p//$HOME/HOME}
|
||||
done
|
||||
|
||||
- name: "compgen -f with quoted var"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
touch item1
|
||||
HOME=$(pwd)
|
||||
|
||||
echo "[0]"
|
||||
for p in $(compgen -f '$HOME/'); do
|
||||
echo ${p//$HOME/HOME}
|
||||
done
|
||||
|
||||
- name: "compgen -f with quoted + escaped var"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
touch item1
|
||||
HOME=$(pwd)
|
||||
|
||||
echo "[0]"
|
||||
for p in $(compgen -f '\$HOME/'); do
|
||||
echo ${p//$HOME/HOME}
|
||||
done
|
||||
|
||||
- name: "compgen with interesting hyphens"
|
||||
stdin: |
|
||||
compgen -P-before -S-after -W "one two three" -- t | sort
|
||||
|
||||
- name: "compgen -X"
|
||||
stdin: |
|
||||
echo "[Take 1]"
|
||||
compgen -W 'foo bar' -X 'foo' ''
|
||||
|
||||
echo "[Take 2]"
|
||||
compgen -W 'foo bar' -X 'f*' ''
|
||||
|
||||
echo "[Take 3]"
|
||||
compgen -W '&1 foo' -X '\&*' ''
|
||||
|
||||
- name: "compgen -X with replacement"
|
||||
stdin: |
|
||||
echo "[Take 1]"
|
||||
compgen -W 'somebody something' -X '&b*' some
|
||||
|
||||
- name: "compgen -X with extglob"
|
||||
stdin: |
|
||||
touch README
|
||||
shopt -s extglob
|
||||
|
||||
echo "[Take 1]"
|
||||
compgen -f READ
|
||||
|
||||
echo "[Take 2]"
|
||||
compgen -f -X "READ" READ
|
||||
|
||||
echo "[Take 3]"
|
||||
compgen -f -X "README" READ
|
||||
|
||||
echo "[Take 4]"
|
||||
compgen -f -X "!(READ)" READ
|
||||
|
||||
echo "[Take 5]"
|
||||
compgen -f -X "!(README)" READ
|
||||
|
||||
echo "[Take 6]"
|
||||
compgen -f -X "!!(READ)" READ
|
||||
|
||||
echo "[Take 7]"
|
||||
compgen -f -X "!!(README)" READ
|
||||
|
||||
- name: "compgen -o dirnames"
|
||||
stdin: |
|
||||
echo "[Take 1]"
|
||||
compgen -W 'completion' -o dirnames -- c | sort
|
||||
|
||||
echo "[Take 2]"
|
||||
mkdir subdir
|
||||
touch subfile
|
||||
compgen -W 'completion' -o dirnames -- s | sort
|
||||
|
||||
- name: "compgen -o default"
|
||||
stdin: |
|
||||
echo "[Take 1]"
|
||||
compgen -W 'completion' -o default -- c | sort
|
||||
|
||||
echo "[Take 2]"
|
||||
mkdir subdir
|
||||
touch subfile
|
||||
compgen -W 'completion' -o default -- s | sort
|
||||
|
||||
- name: "compgen -o bashdefault"
|
||||
stdin: |
|
||||
echo "[Take 1]"
|
||||
compgen -W 'completion' -o bashdefault -- c | sort
|
||||
|
||||
echo "[Take 2]"
|
||||
mkdir subdir
|
||||
touch subfile
|
||||
compgen -W 'completion' -o bashdefault -- s | sort
|
||||
|
||||
- name: "compgen -o plusdirs"
|
||||
stdin: |
|
||||
echo "[Take 1]"
|
||||
touch cfile
|
||||
mkdir cdir
|
||||
compgen -W 'completion' -o plusdirs -o default -S suffix -- c | sort
|
||||
|
||||
- name: "compgen -A command"
|
||||
# Temporarily marked unconstrained Path case as `skip: true` to keep CI stable.
|
||||
# Environment-dependent due to host PATH contents(Ubuntu includes X11, while Arch and others do not).
|
||||
skip: true
|
||||
stdin: |
|
||||
compgen -A command | sort | grep -v "^brush"
|
||||
|
||||
- name: "compgen -A command in a constrained PATH"
|
||||
stdin: |
|
||||
SORT=$(command -v sort)
|
||||
GREP=$(command -v grep)
|
||||
PATH=$(pwd)
|
||||
compgen -A command | $SORT | $GREP -v "^brush"
|
||||
|
||||
- name: "compgen -A command builtins in a constrained PATH"
|
||||
stdin: |
|
||||
SORT=$(command -v sort)
|
||||
PATH=$(pwd)
|
||||
compgen -A command cd | $SORT
|
||||
|
||||
- name: "compgen -A command keywords in a constrained PATH"
|
||||
stdin: |
|
||||
SORT=$(command -v sort)
|
||||
PATH=$(pwd)
|
||||
compgen -A command [ | $SORT
|
||||
|
||||
- name: "compgen -A command function in a constrained PATH"
|
||||
stdin: |
|
||||
SORT=$(command -v sort)
|
||||
PATH=$(pwd)
|
||||
myfunc() {
|
||||
echo hi
|
||||
}
|
||||
myfunc2() {
|
||||
echo hello
|
||||
}
|
||||
compgen -A command myfunc | $SORT
|
||||
|
||||
- name: "compgen -A command with executable symlink"
|
||||
stdin: |
|
||||
# pwd
|
||||
# drwxr-xr-x ├── test-bin
|
||||
# .rwxr-xr-x │ ├── dummy_exe
|
||||
# .rw-r--r-- │ └── dummy_non_exe
|
||||
# drwxr-xr-x └── test-links
|
||||
# lrwxrwxrwx ├── link_valid ⇒ $(pwd)/test-bin/dummy_exe
|
||||
# lrwxrwxrwx └── link_valid_exe_rec ⇒ $(pwd)/test-links/link_valid
|
||||
# lrwxrwxrwx ├── link_non_exec ⇒ $(pwd)/test-bin/dummy_non_exe
|
||||
# lrwxrwxrwx ├── link_broken ⇒ $(pwd)/test-bin/dummy_non_exist
|
||||
#
|
||||
mkdir test-bin
|
||||
touch test-bin/dummy_exe
|
||||
touch test-bin/dummy_non_exe
|
||||
chmod +x test-bin/dummy_exe
|
||||
|
||||
mkdir test-links
|
||||
ln -s $(pwd)/test-bin/dummy_exe $(pwd)/test-links/link_valid
|
||||
ln -s $(pwd)/test-links/link_vaild $(pwd)/test-links/link_valid_rec
|
||||
ln -s $(pwd)/test-bin/dummy_non_exe $(pwd)/test-links/link_non_exec
|
||||
ln -s $(pwd)/test-bin/dummy_non_exist $(pwd)/test-links/link_broken
|
||||
|
||||
PATH="$(pwd)/test-links:$PATH"
|
||||
|
||||
echo "[Valid]"
|
||||
compgen -A command -- link_valid | sort
|
||||
|
||||
echo "[Invalid]"
|
||||
compgen -A command -- link_non_exec
|
||||
compgen -A command -- link_broken
|
||||
|
||||
- name: "compgen -F sets COMP_KEY and COMP_TYPE to 0"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
_comp() { echo "KEY=${COMP_KEY:-unset} TYPE=${COMP_TYPE:-unset}"; COMPREPLY=(); }
|
||||
compgen -F _comp mycmd
|
||||
@@ -0,0 +1,114 @@
|
||||
name: "Builtins: complete"
|
||||
cases:
|
||||
- name: "complete with no options"
|
||||
stdin: |
|
||||
complete -W foo mycmd
|
||||
complete
|
||||
|
||||
- name: "Roundtrip: complete -W"
|
||||
stdin: |
|
||||
complete -W foo mycmd
|
||||
complete -p mycmd
|
||||
|
||||
complete -W 'foo bar' mycmd
|
||||
complete -p mycmd
|
||||
|
||||
- name: "Roundtrip: complete -P"
|
||||
stdin: |
|
||||
complete -P myprefix mycmd
|
||||
complete -p mycmd
|
||||
|
||||
complete -P 'my prefix' mycmd
|
||||
complete -p mycmd
|
||||
|
||||
- name: "Roundtrip: complete -S"
|
||||
stdin: |
|
||||
complete -S mysuffix mycmd
|
||||
complete -p mycmd
|
||||
|
||||
complete -S 'my suffix' mycmd
|
||||
complete -p mycmd
|
||||
|
||||
- name: "Roundtrip: complete -F"
|
||||
stdin: |
|
||||
complete -Fmyfunc mycmd
|
||||
complete -p mycmd
|
||||
|
||||
- name: "Roundtrip: complete -F"
|
||||
stdin: |
|
||||
complete -G pattern mycmd
|
||||
complete -p mycmd
|
||||
|
||||
complete -G 'pat tern' mycmd
|
||||
complete -p mycmd
|
||||
|
||||
- name: "Roundtrip: complete -X"
|
||||
stdin: |
|
||||
complete -X pattern mycmd
|
||||
complete -p mycmd
|
||||
|
||||
complete -X 'pat tern' mycmd
|
||||
complete -p mycmd
|
||||
|
||||
- name: "Roundtrip: complete -C"
|
||||
stdin: |
|
||||
complete -C cmd mycmd
|
||||
complete -p mycmd
|
||||
|
||||
complete -C 'c md' mycmd
|
||||
complete -p mycmd
|
||||
|
||||
- name: "Roundtrip: complete -A"
|
||||
stdin: |
|
||||
for action in alias arrayvar binding builtin command directory disabled enabled export file 'function' group helptopic hostname job keyword running service setopt shopt signal stopped user variable; do
|
||||
complete -A ${action} mycmd
|
||||
complete -p mycmd
|
||||
done
|
||||
|
||||
- name: "Roundtrip: complete -o options"
|
||||
stdin: |
|
||||
for opt in bashdefault default dirnames filenames noquote nosort nospace plusdirs; do
|
||||
echo "--- Testing option: ${opt} ------------------"
|
||||
complete -o ${opt} mycmd_${opt}
|
||||
complete -p mycmd_${opt}
|
||||
done
|
||||
|
||||
- name: "complete -r"
|
||||
stdin: |
|
||||
echo "[Removing non-existent]"
|
||||
complete -r nonexistent
|
||||
echo $?
|
||||
|
||||
echo "[Removing existing]"
|
||||
complete -W token mycmd
|
||||
complete -r mycmd
|
||||
echo $?
|
||||
complete -p mycmd 2>/dev/null
|
||||
|
||||
- name: "complete -r with no args"
|
||||
stdin: |
|
||||
complete -W token mycmd1
|
||||
complete -W token mycmd2
|
||||
|
||||
complete -r
|
||||
echo $?
|
||||
|
||||
complete -p
|
||||
|
||||
- name: "complete -r with special options"
|
||||
stdin: |
|
||||
complete -W token mycmd
|
||||
complete -W other -E
|
||||
|
||||
complete -r -E
|
||||
echo $?
|
||||
|
||||
complete -p
|
||||
|
||||
- name: "compgen -W preserves duplicates"
|
||||
stdin: |
|
||||
compgen -W "a b a c b"
|
||||
|
||||
- name: "compgen -W preserves order"
|
||||
stdin: |
|
||||
compgen -W "z y x a"
|
||||
@@ -0,0 +1,451 @@
|
||||
name: "Builtins: declare"
|
||||
common_test_files:
|
||||
- path: "helpers.sh"
|
||||
contents: |
|
||||
stable_print_assoc_array() {
|
||||
# TODO(nameref): enable use of nameref when implemented; for now
|
||||
# we assume the name of the array is assoc_array
|
||||
# local -n assoc_array=$1
|
||||
local key
|
||||
|
||||
for key in $(printf "%s\n" "${!assoc_array[@]}" | sort -n); do
|
||||
echo "\"${key}\" => ${assoc_array[${key}]}"
|
||||
done
|
||||
}
|
||||
|
||||
cases:
|
||||
- name: "Display vars"
|
||||
stdin: |
|
||||
declare myvar=something
|
||||
declare -p myvar
|
||||
|
||||
myarr=(a b c)
|
||||
declare -p myarr
|
||||
|
||||
- name: "Display vars with interesting chars"
|
||||
stdin: |
|
||||
(testvar="\"abc\"" && declare -p testvar && declare | grep testvar=)
|
||||
echo "-------------------------"
|
||||
(testvar="a b c" && declare -p testvar && declare | grep testvar=)
|
||||
echo "-------------------------"
|
||||
(testvar="'" && declare -p testvar && declare | grep testvar=)
|
||||
|
||||
- name: "Display vars with interesting chars 2"
|
||||
min_oracle_version: 5.2 # some sequences render differently in older shell versions
|
||||
stdin: |
|
||||
(testvar=$'a\nb' && declare -p testvar && declare | grep testvar=)
|
||||
echo "-------------------------"
|
||||
(testvar=$'\x03' && declare -p testvar && declare | grep testvar=)
|
||||
echo "-------------------------"
|
||||
(testvar=$'\x08' && declare -p testvar && declare | grep testvar=)
|
||||
echo "-------------------------"
|
||||
(testvar=$(printf '\033[34mabc\033[0m') && declare -p testvar && declare | grep testvar=)
|
||||
|
||||
- name: "Declare integer"
|
||||
stdin: |
|
||||
declare -i num=10
|
||||
declare -p num
|
||||
|
||||
echo $num
|
||||
num+=10
|
||||
echo $num
|
||||
|
||||
declare +i num
|
||||
declare -p num
|
||||
|
||||
echo $num
|
||||
num+=10
|
||||
echo $num
|
||||
|
||||
- name: "Declare integer with non-integer string"
|
||||
stdin: |
|
||||
declare -i var=value
|
||||
declare -p var
|
||||
|
||||
- name: "Update integer with non-integer string"
|
||||
stdin: |
|
||||
declare -i var=10
|
||||
declare -p var
|
||||
|
||||
var=value
|
||||
declare -p var
|
||||
|
||||
- name: "Update integer array with non-integer string"
|
||||
stdin: |
|
||||
declare -ai arr=()
|
||||
declare -p arr
|
||||
|
||||
arr[0]="value"
|
||||
declare -p arr
|
||||
|
||||
- name: "Update integer array with non-integer string"
|
||||
stdin: |
|
||||
declare -Ai arr=()
|
||||
declare -p arr
|
||||
|
||||
arr['key']="value"
|
||||
declare -p arr
|
||||
|
||||
- name: "Declare readonly variable"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
declare -r var="readonly"
|
||||
declare -p var
|
||||
|
||||
echo $var
|
||||
|
||||
var="change"
|
||||
echo "change result: $?"
|
||||
echo "var: ${var}"
|
||||
|
||||
declare +r var
|
||||
echo "+r result: $?"
|
||||
declare -p var
|
||||
|
||||
- name: "Declare array"
|
||||
stdin: |
|
||||
declare -a arr=("element1" "element2" "element3")
|
||||
declare -p arr
|
||||
echo "[0]: ${arr[0]}"
|
||||
echo "[1]: ${arr[0]}"
|
||||
echo "[2]: ${arr[0]}"
|
||||
echo "[3]: ${arr[0]}"
|
||||
echo "STAR: ${arr[*]}"
|
||||
echo "AT: ${arr[*]}"
|
||||
|
||||
- name: "Declare associative array"
|
||||
stdin: |
|
||||
declare -A arr=(["x1"]=1 ["x2"]=2)
|
||||
declare -p arr
|
||||
echo "[x]: ${arr[x1]}"
|
||||
echo "[y]: ${arr[x2]}"
|
||||
echo "[z]: ${arr[x3]}"
|
||||
echo "STAR: ${arr[*]}"
|
||||
echo "AT: ${arr[*]}"
|
||||
|
||||
- name: "Declare and export variable"
|
||||
stdin: |
|
||||
declare -x myexportedvar="exported variable"
|
||||
env | grep myexportedvar
|
||||
|
||||
- name: "Declare and export unset variable"
|
||||
stdin: |
|
||||
declare -x myunsetexportedvar
|
||||
env | grep myunsetexportedvar
|
||||
set | grep myunsetexportedvar=
|
||||
|
||||
- name: "Re-declaring variable"
|
||||
stdin: |
|
||||
var="value"
|
||||
declare var
|
||||
echo "var: ${var}"
|
||||
|
||||
- name: "Declaring without value"
|
||||
stdin: |
|
||||
[[ -v var ]] && echo "1: Variable is set"
|
||||
declare var
|
||||
declare -p var
|
||||
[[ -v var ]] && echo "2: Variable is set"
|
||||
declare var2=""
|
||||
declare -p var2
|
||||
[[ -v var2 ]] && echo "3: Variable is set"
|
||||
|
||||
- name: "Displaying local vars"
|
||||
stdin: |
|
||||
function test {
|
||||
echo "Dumping local variables (should be empty)"
|
||||
local -p
|
||||
|
||||
local -i int_var=10
|
||||
local -A assoc_array=(["x1"]=1 ["x2"]=2)
|
||||
local -a array=(a b c)
|
||||
local -r ro_var="readonly"
|
||||
local -t traced="value"
|
||||
|
||||
echo "Dump all variables"
|
||||
local -p
|
||||
}
|
||||
|
||||
test
|
||||
|
||||
- name: "Using local to detect function presence"
|
||||
stdin: |
|
||||
function test {
|
||||
local something 2>/dev/null && echo "In function"
|
||||
}
|
||||
|
||||
local something 2>/dev/null || echo "Not in function"
|
||||
|
||||
test
|
||||
|
||||
- name: "Displaying function names"
|
||||
stdin: |
|
||||
echo "Dumping function names"
|
||||
declare -F
|
||||
declare -p -F
|
||||
|
||||
function test {
|
||||
:
|
||||
}
|
||||
|
||||
echo "Dumping function names again"
|
||||
declare -F
|
||||
declare -p -F
|
||||
|
||||
echo "Dumping test"
|
||||
declare -F test
|
||||
declare -p -F test
|
||||
|
||||
- name: "Displaying functions"
|
||||
stdin: |
|
||||
echo "Dumping functions"
|
||||
declare -f
|
||||
declare -p -f
|
||||
|
||||
function test {
|
||||
:
|
||||
}
|
||||
|
||||
echo "Dumping functions again"
|
||||
declare -f
|
||||
declare -p -f
|
||||
|
||||
echo "Dumping test"
|
||||
declare -f test
|
||||
declare -p -f test
|
||||
|
||||
- name: "Displaying non-existent functions"
|
||||
stdin: |
|
||||
declare -f not_a_function
|
||||
echo "Result (-f): $?"
|
||||
|
||||
declare -F not_a_function
|
||||
echo "Result (-F): $?"
|
||||
|
||||
- name: "Valid conversions"
|
||||
stdin: |
|
||||
declare -a arr1=(a b c)
|
||||
declare -a arr1
|
||||
echo "Conversion result: $?"
|
||||
declare -p arr1
|
||||
|
||||
declare -A arr2=(["x1"]=1 ["x2"]=2)
|
||||
declare -A arr2
|
||||
echo "Conversion result: $?"
|
||||
declare -p arr2
|
||||
|
||||
declare scalar1="value"
|
||||
declare -a scalar1
|
||||
echo "Conversion result: $?"
|
||||
declare -p scalar1
|
||||
|
||||
declare scalar2="value"
|
||||
declare -A scalar2
|
||||
echo "Conversion result: $?"
|
||||
declare -p scalar2
|
||||
|
||||
- name: "Bad conversions"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
declare -a arr1=(a b c)
|
||||
declare -A arr1
|
||||
echo "Conversion result: $?"
|
||||
declare -p arr1
|
||||
|
||||
declare -A arr2=(["x1"]=1 ["x2"]=2)
|
||||
declare -a arr2
|
||||
echo "Conversion result: $?"
|
||||
declare -p arr2
|
||||
|
||||
- name: "Declare -p using invalid forms"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
declare arr=(a b c)
|
||||
declare -p arr[0]
|
||||
echo "Result: $?"
|
||||
declare -p arr[0]=1
|
||||
echo "Result: $?"
|
||||
|
||||
declare scalar=x
|
||||
echo "Result: $?"
|
||||
declare -p scalar=y
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Updating value"
|
||||
stdin: |
|
||||
declare var="value"
|
||||
declare -p var
|
||||
declare var="changed"
|
||||
declare -p var
|
||||
|
||||
- name: "Updating value attributes"
|
||||
stdin: |
|
||||
declare -ix var=10
|
||||
declare -p var
|
||||
declare +ix var
|
||||
declare -p var
|
||||
|
||||
- name: "Updating array"
|
||||
stdin: |
|
||||
declare arr=(a b c)
|
||||
declare -p arr
|
||||
declare arr=(d e)
|
||||
declare -p arr
|
||||
declare arr=10
|
||||
declare -p arr
|
||||
|
||||
declare arr2=a
|
||||
declare -p arr2
|
||||
declare -A arr2=(["key"]="value")
|
||||
declare -p arr2
|
||||
|
||||
- name: "Updating causing conversion"
|
||||
stdin: |
|
||||
source helpers.sh
|
||||
|
||||
declare assoc_array="scalar-value"
|
||||
declare -p assoc_array
|
||||
|
||||
declare -A assoc_array["key"]="key-value"
|
||||
stable_print_assoc_array assoc_array
|
||||
|
||||
- name: "Uppercase attribute"
|
||||
stdin: |
|
||||
declare var=value
|
||||
|
||||
declare -u var
|
||||
declare -p var
|
||||
|
||||
var="abcd"
|
||||
declare -p var
|
||||
|
||||
declare another="abcd"
|
||||
declare -u another
|
||||
another+=efg
|
||||
declare -p another
|
||||
|
||||
- name: "Lowercase attribute"
|
||||
stdin: |
|
||||
declare var=value
|
||||
|
||||
declare -l var
|
||||
declare -p var
|
||||
|
||||
var="AbCd"
|
||||
declare -p var
|
||||
|
||||
declare another="ABCD"
|
||||
declare -l another
|
||||
another+=EFG
|
||||
declare -p another
|
||||
|
||||
- name: "Capitalize attribute"
|
||||
stdin: |
|
||||
declare var=value
|
||||
|
||||
declare -c var
|
||||
declare -p var
|
||||
|
||||
var="aBcD eFg"
|
||||
declare -p var
|
||||
|
||||
declare another="aBcD"
|
||||
declare -c another
|
||||
another+=eFg
|
||||
declare -p another
|
||||
|
||||
- name: "Declare invalid identifiers"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
declare 1=x && echo "Set 1"
|
||||
declare @=10 && echo "Set @"
|
||||
|
||||
- name: "Nameref declare -p shows nameref attribute"
|
||||
stdin: |
|
||||
target="hello"
|
||||
declare -n ref=target
|
||||
declare -p ref
|
||||
declare -p target
|
||||
|
||||
- name: "Nameref declare -n with initial value"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
target="original"
|
||||
declare -n ref=target
|
||||
echo "ref: $ref"
|
||||
echo "target: $target"
|
||||
|
||||
- name: "Nameref remove attribute with +n"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
target="value"
|
||||
declare -n ref=target
|
||||
echo "as nameref: $ref"
|
||||
declare +n ref
|
||||
echo "after +n: ref=$ref"
|
||||
declare -p ref
|
||||
|
||||
- name: "Nameref to array via declare"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
arr=(one two three)
|
||||
declare -n ref=arr
|
||||
echo "ref[0]: ${ref[0]}"
|
||||
echo "ref[1]: ${ref[1]}"
|
||||
echo "ref[@]: ${ref[@]}"
|
||||
echo "ref[*]: ${ref[*]}"
|
||||
|
||||
- name: "Nameref array modification via declare"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
arr=(a b c)
|
||||
declare -n ref=arr
|
||||
ref[1]="modified"
|
||||
echo "arr[1]: ${arr[1]}"
|
||||
echo "ref[1]: ${ref[1]}"
|
||||
|
||||
- name: "Nameref to associative array via declare"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
declare -A assoc=(["key1"]="val1" ["key2"]="val2")
|
||||
declare -n ref=assoc
|
||||
echo "ref[key1]: ${ref[key1]}"
|
||||
echo "ref[key2]: ${ref[key2]}"
|
||||
|
||||
- name: "Nameref array iteration in function via local -n"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
print_array() {
|
||||
local -n arr=$1
|
||||
echo "count: ${#arr[@]}"
|
||||
for elem in "${arr[@]}"; do
|
||||
echo "elem: $elem"
|
||||
done
|
||||
}
|
||||
|
||||
myarr=(first second third)
|
||||
print_array myarr
|
||||
|
||||
- name: "Nameref get keys via declare"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
declare -A assoc=(["a"]=1 ["b"]=2 ["c"]=3)
|
||||
declare -n ref=assoc
|
||||
echo "keys (sorted): $(printf '%s\n' ${!ref[@]} | sort | tr '\n' ' ')"
|
||||
echo "count: ${#ref[@]}"
|
||||
|
||||
- name: "Nameref array length via declare"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
arr=(one two three four five)
|
||||
declare -n ref=arr
|
||||
echo "length: ${#ref[@]}"
|
||||
echo "first element length: ${#ref[0]}"
|
||||
|
||||
- name: "Nameref declare -n filter"
|
||||
stdin: |
|
||||
regular="plain"
|
||||
declare -n ref1=regular
|
||||
declare -n ref2=regular
|
||||
not_a_ref="also plain"
|
||||
declare -p -n 2>/dev/null | sort
|
||||
@@ -0,0 +1,135 @@
|
||||
name: "Builtins: dot"
|
||||
cases:
|
||||
- name: "Basic dot usage"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "In sourced script"
|
||||
var="updated"
|
||||
stdin: |
|
||||
var="orig"
|
||||
. script.sh
|
||||
echo "var: ${var}"
|
||||
|
||||
- name: "Basic source usage"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "In sourced script"
|
||||
for f in ${FUNCNAME[@]}; do
|
||||
echo "FUNCNAME: '${f}'"
|
||||
done
|
||||
for s in ${BASH_SOURCE[@]}; do
|
||||
echo "BASH_SOURCE: '${s}'"
|
||||
done
|
||||
var="updated"
|
||||
|
||||
function script_func() {
|
||||
echo "In script func"
|
||||
for f in ${FUNCNAME[@]}; do
|
||||
echo "FUNCNAME: '${f}'"
|
||||
done
|
||||
for s in ${BASH_SOURCE[@]}; do
|
||||
echo "BASH_SOURCE: '${s}'"
|
||||
done
|
||||
}
|
||||
stdin: |
|
||||
var="orig"
|
||||
source ./script.sh
|
||||
echo "var: ${var}"
|
||||
script_func
|
||||
|
||||
- name: "Source non-existent file path"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
source script.sh
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Source dir"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
source .
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Source script without args"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "In sourced script"
|
||||
echo "Args: $@"
|
||||
stdin: |
|
||||
set -- outer1 outer2 outer3
|
||||
source script.sh
|
||||
|
||||
- name: "Source script with args"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "In sourced script"
|
||||
echo "Args: $@"
|
||||
stdin: |
|
||||
set -- outer1 outer2 outer3
|
||||
source script.sh inner1 inner2
|
||||
|
||||
- name: "Source with redirection"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "In sourced script"
|
||||
stdin: |
|
||||
source script.sh arg1 arg2 > out.txt
|
||||
echo "Sourced script; dumping..."
|
||||
cat out.txt
|
||||
|
||||
- name: "Source with return"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "Before return"
|
||||
return 42
|
||||
echo "After return"
|
||||
stdin: |
|
||||
echo "Before source"
|
||||
source script.sh
|
||||
echo "After source, exit code: $?"
|
||||
|
||||
- name: "Source with exit"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "Before exit"
|
||||
exit 42
|
||||
echo "After exit"
|
||||
stdin: |
|
||||
echo "Before source"
|
||||
source script.sh
|
||||
echo "After source"
|
||||
|
||||
- name: "Source with break in caller's loop"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "In script, breaking"
|
||||
break
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Iteration $i"
|
||||
source script.sh
|
||||
echo "After source"
|
||||
done
|
||||
echo "After loop"
|
||||
|
||||
- name: "Source with continue in caller's loop"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "In script, continuing"
|
||||
continue
|
||||
echo "After continue"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Iteration $i"
|
||||
source script.sh
|
||||
echo "After source"
|
||||
done
|
||||
echo "After loop"
|
||||
@@ -0,0 +1,12 @@
|
||||
name: "Builtins: echo"
|
||||
cases:
|
||||
- name: "echo with only --"
|
||||
stdin: echo --
|
||||
|
||||
- name: "echo with -- and args"
|
||||
stdin: echo -- -1 --"aaa" ?^1as-
|
||||
|
||||
- name: "echo -e"
|
||||
stdin: |
|
||||
echo -e "1. \x65" | hexdump -C
|
||||
echo -e "2. \x{65}" | hexdump -C
|
||||
@@ -0,0 +1,32 @@
|
||||
name: "Builtins: enable"
|
||||
cases:
|
||||
- name: "List special builtins"
|
||||
stdin: enable -s
|
||||
|
||||
- name: "List default-disabled builtins"
|
||||
stdin: enable -n
|
||||
|
||||
- name: "List all builtins"
|
||||
stdin: |
|
||||
# List builtins but ignore any brush specific ones.
|
||||
enable | grep -v brush
|
||||
|
||||
- name: "Disable builtins"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
type printf
|
||||
|
||||
# Disable the builtin
|
||||
PATH=
|
||||
enable -n printf
|
||||
|
||||
# Check
|
||||
type printf
|
||||
print "Gone\n"
|
||||
|
||||
# Re-enable
|
||||
enable printf
|
||||
|
||||
# Re-check
|
||||
type printf
|
||||
printf "Back\n"
|
||||
@@ -0,0 +1,47 @@
|
||||
name: "Builtins: eval"
|
||||
cases:
|
||||
- name: "Basic eval usage"
|
||||
stdin: |
|
||||
eval 'echo 1 + 1 == $((1 + 1))'
|
||||
|
||||
- name: "eval with return in function"
|
||||
stdin: |
|
||||
f() {
|
||||
eval 'return 42'
|
||||
echo "Should not reach here"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "eval with exit"
|
||||
stdin: |
|
||||
eval 'exit 42'
|
||||
echo "After eval'd exit"
|
||||
|
||||
- name: "eval with break in loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
eval 'break'
|
||||
echo "After eval'd break"
|
||||
done
|
||||
echo "After loop"
|
||||
|
||||
- name: "eval with continue in loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Iteration $i"
|
||||
eval 'continue'
|
||||
echo "After eval'd continue"
|
||||
done
|
||||
echo "After loop"
|
||||
|
||||
- name: "eval with nested break levels"
|
||||
stdin: |
|
||||
for i in 1 2; do
|
||||
for j in a b c; do
|
||||
eval 'break 2'
|
||||
echo "After eval'd break (inner loop)"
|
||||
done
|
||||
echo "After eval'd break (outer loop)"
|
||||
done
|
||||
echo "After outer loop"
|
||||
@@ -0,0 +1,51 @@
|
||||
name: "Builtins: exec"
|
||||
cases:
|
||||
- name: "Exec an invalid path"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
exec /some/nonexistent/path
|
||||
|
||||
- name: "Exec with no arguments"
|
||||
stdin: |
|
||||
pid=$$
|
||||
exec
|
||||
[[ "${pid}" == "$$" ]] || echo "PID changed"
|
||||
|
||||
- name: "Exec with no arguments and redirection for new fd"
|
||||
stdin: |
|
||||
exec 3>some-file.txt
|
||||
echo "Hello, fd 3!" >&3
|
||||
|
||||
- name: "Exec with no arguments and redirection of stderr"
|
||||
stdin: |
|
||||
exec 2>/dev/null
|
||||
|
||||
# Do something that should display an error to stderr
|
||||
source /non-existent
|
||||
|
||||
- name: "Exec with no arguments and redirection in a script"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
exec 2>output-file.txt
|
||||
echo hi >&2
|
||||
args: ["./script.sh"]
|
||||
|
||||
- name: "Exec a command"
|
||||
stdin: |
|
||||
exec $0 -c 'echo "In child shell"'
|
||||
echo "This is never reached"
|
||||
|
||||
- name: "exec without -c"
|
||||
stdin: |
|
||||
export myvar=value
|
||||
exec $0 -c 'echo "myvar: ${myvar}"'
|
||||
|
||||
- name: "exec -c"
|
||||
stdin: |
|
||||
export myvar=value
|
||||
exec -c $0 -c 'echo "myvar: ${myvar}"'
|
||||
|
||||
- name: "exec -a"
|
||||
stdin: |
|
||||
exec -a shellname $0 -c 'echo "0: $0"'
|
||||
@@ -0,0 +1,85 @@
|
||||
name: "Builtins: exit"
|
||||
cases:
|
||||
- name: "Exit without code"
|
||||
stdin: |
|
||||
exit
|
||||
|
||||
- name: "Exit with code"
|
||||
stdin: |
|
||||
exit 10
|
||||
|
||||
- name: "Exit with i64 max"
|
||||
stdin: |
|
||||
exit 9223372036854775807
|
||||
|
||||
- name: "Exit with i64 min"
|
||||
stdin: |
|
||||
exit -9223372036854775808
|
||||
|
||||
- name: "Exit in for loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
exit 42
|
||||
echo "Got past exit in loop"
|
||||
done
|
||||
echo "Got past loop"
|
||||
|
||||
- name: "Exit in arithmetic for loop body"
|
||||
stdin: |
|
||||
for ((i = 0; i < 10; i++)); do
|
||||
exit 42
|
||||
echo "Got past exit in loop"
|
||||
done
|
||||
echo "Got past loop"
|
||||
|
||||
- name: "Exit in while loop condition"
|
||||
stdin: |
|
||||
while exit 42; do
|
||||
echo "In loop"
|
||||
done
|
||||
echo "Got past loop"
|
||||
|
||||
- name: "Exit in while loop body"
|
||||
stdin: |
|
||||
while true; do
|
||||
exit 42
|
||||
echo "Got past exit in body"
|
||||
break
|
||||
done
|
||||
echo "Got past loop"
|
||||
|
||||
- name: "Exit in sequence"
|
||||
stdin: |
|
||||
exit 42; echo "Should not be printed"
|
||||
|
||||
- name: "Exit in function"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
exit 42
|
||||
echo "Got past exit in function"
|
||||
}
|
||||
|
||||
myfunc
|
||||
echo "Got past function call"
|
||||
|
||||
- name: "Exit in subshell"
|
||||
stdin: |
|
||||
(exit 42)
|
||||
echo "Got past subshell"
|
||||
|
||||
- name: "Exit in command substitution"
|
||||
stdin: |
|
||||
output=$(echo hi; exit 42; echo there)
|
||||
echo "Got past command substitution"
|
||||
|
||||
- name: "Exit in and/or"
|
||||
stdin: |
|
||||
exit 42 || echo "Got past exit"
|
||||
|
||||
- name: "Exit in brace group"
|
||||
stdin: |
|
||||
{
|
||||
exit 42
|
||||
echo "Got past exit"
|
||||
}
|
||||
echo "Got past brace group"
|
||||
@@ -0,0 +1,36 @@
|
||||
name: "Builtins: export"
|
||||
cases:
|
||||
- name: "Basic export usage"
|
||||
stdin: |
|
||||
MY_TEST_VAR="value"
|
||||
echo "Looking for unexported variable..."
|
||||
env | grep MY_TEST_VAR
|
||||
|
||||
echo "Exporting and looking for it again..."
|
||||
export MY_TEST_VAR
|
||||
env | grep MY_TEST_VAR
|
||||
|
||||
echo "Exporting with new value..."
|
||||
export MY_TEST_VAR="changed value"
|
||||
env | grep MY_TEST_VAR
|
||||
|
||||
- name: "Exporting array"
|
||||
stdin: |
|
||||
export arr=(a 1 2)
|
||||
declare -p arr
|
||||
|
||||
- name: "Unexporting variable"
|
||||
stdin: |
|
||||
MY_TEST_VAR="value"
|
||||
export MY_TEST_VAR="value"
|
||||
export -n MY_TEST_VAR
|
||||
|
||||
env | grep MY_TEST_VAR
|
||||
|
||||
- name: "Export with assignment through nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
declare -n ref=MY_EXPORT_VAR
|
||||
export ref="exported_value"
|
||||
echo "MY_EXPORT_VAR: $MY_EXPORT_VAR"
|
||||
env | grep MY_EXPORT_VAR
|
||||
@@ -0,0 +1,275 @@
|
||||
name: "Builtins: fc"
|
||||
cases:
|
||||
- name: "fc -l lists recent commands"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
fc -l
|
||||
|
||||
- name: "fc -l with range (positive indices)"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
: cmd4
|
||||
: cmd5
|
||||
fc -l 2 4
|
||||
|
||||
- name: "fc -l with single argument"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
fc -l 2
|
||||
|
||||
- name: "fc -l with negative indices"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
: cmd4
|
||||
: cmd5
|
||||
fc -l -3 -1
|
||||
|
||||
- name: "fc -l with string search"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
: cmd1
|
||||
: other
|
||||
: cmd2
|
||||
: something
|
||||
fc -l cmd
|
||||
|
||||
- name: "fc -l with -n (no line numbers)"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
fc -ln
|
||||
|
||||
- name: "fc -l with -r (reverse order)"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
fc -lr
|
||||
|
||||
- name: "fc -l with -n and -r combined"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
fc -lnr
|
||||
|
||||
- name: "fc -l with range and -r"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
: cmd4
|
||||
: cmd5
|
||||
fc -lr 2 4
|
||||
|
||||
- name: "fc -l 0 means -1"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
fc -l 0
|
||||
|
||||
- name: "fc -l defaults to last 16 commands"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
: cmd4
|
||||
: cmd5
|
||||
: cmd6
|
||||
: cmd7
|
||||
: cmd8
|
||||
: cmd9
|
||||
: cmd10
|
||||
: cmd11
|
||||
: cmd12
|
||||
: cmd13
|
||||
: cmd14
|
||||
: cmd15
|
||||
: cmd16
|
||||
: cmd17
|
||||
: cmd18
|
||||
fc -l
|
||||
|
||||
- name: "fc -s re-executes last command"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo hello
|
||||
fc -s
|
||||
|
||||
- name: "fc -s with command argument"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo hello
|
||||
echo world
|
||||
fc -s echo
|
||||
|
||||
- name: "fc -s with pattern replacement"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo hello
|
||||
fc -s hello=goodbye
|
||||
|
||||
- name: "fc -s with command number"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo first
|
||||
echo second
|
||||
echo third
|
||||
fc -s 1
|
||||
|
||||
- name: "fc -s with negative offset"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo first
|
||||
echo second
|
||||
echo third
|
||||
fc -s -2
|
||||
|
||||
- name: "fc -s with string prefix match"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo aaa
|
||||
echo bbb
|
||||
echo aac
|
||||
fc -s aa
|
||||
|
||||
- name: "fc -s with pattern replacement and command number"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo first
|
||||
echo second
|
||||
echo hello
|
||||
fc -s hello=goodbye 3
|
||||
|
||||
- name: "fc -s with pattern replacement and string prefix"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo aaa
|
||||
: somecommand
|
||||
echo hello world
|
||||
fc -s hello=goodbye echo
|
||||
|
||||
- name: "fc -s with pattern replacement and negative index"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo one
|
||||
echo two
|
||||
echo hello world
|
||||
fc -s hello=goodbye -1
|
||||
|
||||
- name: "fc -s pattern replacement when pattern doesn't match"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo hello
|
||||
fc -s xyz=abc
|
||||
|
||||
- name: "fc -s pattern replacement replaces all occurrences"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo test test test
|
||||
fc -s test=foo
|
||||
|
||||
- name: "fc -s with empty replacement"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo hello world
|
||||
fc -s hello=
|
||||
|
||||
- name: "fc with invalid range"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
fc -l 100 200
|
||||
expect_status: 1
|
||||
|
||||
- name: "fc -s with nonexistent command"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo hello
|
||||
fc -s xyz
|
||||
expect_status: 1
|
||||
|
||||
- name: "fc -l with both string and number"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd3
|
||||
: other
|
||||
fc -l cmd 3
|
||||
@@ -0,0 +1,476 @@
|
||||
name: "Builtins: getopts"
|
||||
cases:
|
||||
- name: "Basic getopts"
|
||||
stdin: |
|
||||
func() {
|
||||
echo "Beginning of args"
|
||||
while getopts "ab:" option "$@"; do
|
||||
case ${option} in
|
||||
a) echo "Option a; OPTARG=${OPTARG}";;
|
||||
b) echo "Option b; OPTARG=${OPTARG}";;
|
||||
*) echo "Unknown option: ${option}";;
|
||||
esac
|
||||
echo "OPTIND is now ${OPTIND}"
|
||||
echo "OPTERR is now ${OPTERR}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Result: $?"
|
||||
echo "option: ${option}"
|
||||
echo "End of args"
|
||||
}
|
||||
|
||||
func -a -b my_b_arg
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: no args"
|
||||
stdin: |
|
||||
getopts "a:b:" myvar
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
- name: "getopts: toplevel positional parameters"
|
||||
stdin: |
|
||||
set -- -a value
|
||||
getopts "a:b:" myvar
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
- name: "getopts: function positional parameters"
|
||||
stdin: |
|
||||
my_function() {
|
||||
getopts "a:b:" myvar
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
}
|
||||
|
||||
my_function -b value
|
||||
|
||||
- name: "getopts: function script parameters"
|
||||
test_files:
|
||||
- path: script.sh
|
||||
contents: |
|
||||
getopts "a:b:" myvar
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
stdin: |
|
||||
source ./script.sh -a value
|
||||
|
||||
- name: "getopts: no options given"
|
||||
stdin: |
|
||||
while getopts "a:b:" myvar notoption; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Done; result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
- name: "getopts: options and non-options"
|
||||
stdin: |
|
||||
while getopts "ab:" myvar -a -b value notoption; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Done; result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
- name: "getopts: --"
|
||||
stdin: |
|
||||
while getopts "ab:" myvar -a -b value -- -c notoption; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Done; result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
- name: "getopts: -- as first arg"
|
||||
stdin: |
|
||||
while getopts "ab:" myvar -- -a; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Done; result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
- name: "getopts: invalid option when optstr starts with colon"
|
||||
stdin: |
|
||||
getopts ":a:" myvar -b value
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
- name: "getopts: invalid option when optstr does not start with colon"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
getopts "a:" myvar -b value
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
- name: "getopts: multiple options in one token"
|
||||
stdin: |
|
||||
while getopts "a:bcdefg" myvar -fedcba something -g; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Done; result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
- name: "getopts: short option and value in single arg"
|
||||
stdin: |
|
||||
getopts "p:" myvar -p-
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
|
||||
while getopts "abep:" myvar -abpsomething-else; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "OPTERR: ${OPTERR}"
|
||||
echo "-----------------"
|
||||
done
|
||||
|
||||
- name: "getopts: OPTERR=0 suppresses error for unknown option"
|
||||
stdin: |
|
||||
OPTERR=0
|
||||
getopts "a:" myvar -x 2>/dev/null
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: OPTERR=1 shows error for unknown option"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
OPTERR=1
|
||||
getopts "a:" myvar -x
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: OPTERR nonzero shows error"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
OPTERR=42
|
||||
getopts "a:" myvar -x
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
|
||||
- name: "getopts: missing required argument without leading colon"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
getopts "a:" myvar -a
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: missing required argument with leading colon"
|
||||
stdin: |
|
||||
getopts ":a:" myvar -a
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: missing required argument with OPTERR=0"
|
||||
stdin: |
|
||||
OPTERR=0
|
||||
getopts "a:" myvar -a 2>/dev/null
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: combined unknown and missing arg errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
func() {
|
||||
while getopts "a:b:" opt "$@"; do
|
||||
case "$opt" in
|
||||
a) echo "a=$OPTARG" ;;
|
||||
b) echo "b=$OPTARG" ;;
|
||||
\?) echo "unknown" ;;
|
||||
:) echo "missing arg for $OPTARG" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
func -a foo -x -b
|
||||
echo "Done: $?"
|
||||
|
||||
- name: "getopts: bare dash as argument"
|
||||
stdin: |
|
||||
getopts "ab:" myvar -
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: leading colon with dash-colon option"
|
||||
stdin: |
|
||||
getopts ":a:" myvar -:
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: unknown option in middle of combined flags"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
while getopts "ac" myvar -axc; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Done; result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
|
||||
- name: "getopts: missing arg in combined flags"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
while getopts "a:b" myvar -ba; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Final: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: combined flags with arg-taking option consumes next arg"
|
||||
stdin: |
|
||||
while getopts "a:b" myvar -ba nextarg; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Done; result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: OPTIND reset between calls"
|
||||
stdin: |
|
||||
while getopts "a:b" myvar -b -a foo; do
|
||||
echo "myvar: ${myvar}; OPTARG: ${OPTARG-UNSET}; OPTIND: ${OPTIND}"
|
||||
done
|
||||
echo "--- reset ---"
|
||||
OPTIND=1
|
||||
while getopts "a:b" myvar -b -a bar; do
|
||||
echo "myvar: ${myvar}; OPTARG: ${OPTARG-UNSET}; OPTIND: ${OPTIND}"
|
||||
done
|
||||
|
||||
- name: "getopts: empty optstring"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
getopts "" myvar -a
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: OPTIND=0"
|
||||
stdin: |
|
||||
OPTIND=0
|
||||
getopts "a:" myvar -a value
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: negative OPTIND treated as reset"
|
||||
stdin: |
|
||||
OPTIND=-1
|
||||
getopts "a:" myvar -a value
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: bare dash among other args"
|
||||
stdin: |
|
||||
while getopts "ab" myvar -a - -b; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Done; result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: OPTIND past end of args"
|
||||
stdin: |
|
||||
OPTIND=100
|
||||
getopts "a:b:" myvar -a value
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: missing arg with leading colon in combined flags"
|
||||
stdin: |
|
||||
while getopts ":a:b" myvar -ba; do
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
echo "----------------"
|
||||
done
|
||||
echo "Done; result: $?"
|
||||
|
||||
- name: "getopts: non-numeric OPTIND treated as reset"
|
||||
stdin: |
|
||||
OPTIND=abc
|
||||
getopts "a:" myvar -a value
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: option value is empty string"
|
||||
stdin: |
|
||||
getopts "a:" myvar -a ""
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: '${OPTARG}'"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: option value looks like another option"
|
||||
stdin: |
|
||||
getopts "a:b" myvar -a -b
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: OPTIND=0 resets mid-parse of combined options"
|
||||
stdin: |
|
||||
getopts "abc" myvar -abc
|
||||
echo "first: ${myvar}"
|
||||
OPTIND=0
|
||||
getopts "abc" myvar -abc
|
||||
echo "after reset: ${myvar}"
|
||||
|
||||
- name: "getopts: OPTERR non-numeric treated as enabled"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
OPTERR=abc
|
||||
getopts "a:" myvar -x
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: invalid variable name rejected"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
getopts "a" "123abc" -a
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "getopts: duplicate option char in optstring uses first definition"
|
||||
stdin: |
|
||||
getopts "aa:" myvar -a val
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: optstring is only a colon"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
getopts ":" myvar -a
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTARG: ${OPTARG-UNSET}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts: empty string argument is not an option"
|
||||
stdin: |
|
||||
getopts "a" myvar ""
|
||||
echo "Result: $?"
|
||||
echo "myvar: ${myvar}"
|
||||
echo "OPTIND: ${OPTIND}"
|
||||
|
||||
- name: "getopts writes through nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
declare -n ref=result
|
||||
getopts "a:b" ref -a value
|
||||
echo "Result: $?"
|
||||
echo "result: ${result}"
|
||||
echo "ref: ${ref}"
|
||||
echo "OPTARG: ${OPTARG}"
|
||||
|
||||
- name: "getopts writes through subscripted nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
arr=(x x x)
|
||||
declare -n ref='arr[1]'
|
||||
getopts "a:b" ref -a value
|
||||
echo "Result: $?"
|
||||
echo "arr[1]: ${arr[1]}"
|
||||
echo "ref: ${ref}"
|
||||
@@ -0,0 +1,69 @@
|
||||
name: "Builtins: hash"
|
||||
cases:
|
||||
- name: "Non-existent program"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
hash non-existent-program
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Re-hash non-existent program"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
hash -p /some/path non-existent-program
|
||||
hash -t non-existent-program && echo "1. Result: $?"
|
||||
hash non-existent-program && echo "2. Result: $?"
|
||||
hash -t non-existent-program && echo "3. Result: $?"
|
||||
|
||||
- name: "Existent program"
|
||||
stdin: |
|
||||
hash ls
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Display not-yet-hashed program"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
hash -t ls
|
||||
echo "1. Result: $?"
|
||||
ls >/dev/null 2>&1
|
||||
hash -t ls
|
||||
echo "2. Result: $?"
|
||||
|
||||
- name: "Display hashed program"
|
||||
stdin: |
|
||||
hash -p /some/path somecmd && echo "1. Result: $?"
|
||||
hash -t somecmd && echo "2. Result: $?"
|
||||
|
||||
- name: "Display multiple hashed programs"
|
||||
stdin: |
|
||||
hash -p /some/path somecmd1 && echo "1. Result: $?"
|
||||
hash -p /some/path somecmd2 && echo "2. Result: $?"
|
||||
hash -t somecmd1 somecmd2 && echo "3. Result: $?"
|
||||
|
||||
- name: "Display -l path"
|
||||
stdin: |
|
||||
hash -p "/some/spaces path" somecmd && echo "1. Result: $?"
|
||||
hash -t somecmd && echo "2. Result: $?"
|
||||
hash -t -l somecmd && echo "3. Result: $?"
|
||||
|
||||
- name: "Remove"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
hash -p /some/path somecmd && echo "1. Result: $?"
|
||||
hash -t somecmd && echo "2. Result: $?"
|
||||
hash -d somecmd && echo "3. Result: $?"
|
||||
hash -t somecmd && echo "4. Result: $?"
|
||||
|
||||
- name: "Remove all"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
hash -p /some/path somecmd1 && echo "1. Result: $?"
|
||||
hash -p /some/path somecmd2 && echo "2. Result: $?"
|
||||
hash -r && echo "3. Result: $?"
|
||||
hash -t somecmd1 && echo "4. Result: $?"
|
||||
hash -t somecmd2 && echo "5. Result: $?"
|
||||
|
||||
- name: "Hash file path"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
hash /dev/null && echo "1. Result: $?"
|
||||
hash /dev/nulll && echo "2. Result: $?"
|
||||
@@ -0,0 +1,13 @@
|
||||
name: "Builtins: help"
|
||||
cases:
|
||||
- name: "Help"
|
||||
ignore_stdout: true
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
help
|
||||
|
||||
- name: "Topic-specific help"
|
||||
ignore_stdout: true
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
help echo
|
||||
@@ -0,0 +1,193 @@
|
||||
name: "Builtins: history"
|
||||
cases:
|
||||
- name: "basic history saving"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo something
|
||||
echo else
|
||||
|
||||
- name: "existing history file"
|
||||
pty: true
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
test_files:
|
||||
- path: "history-file.txt"
|
||||
contents: |
|
||||
a b c
|
||||
1 2 3
|
||||
ignore_stdout: true # Prompts and other don't quite line up
|
||||
stdin: |
|
||||
#expect-prompt
|
||||
history >history-output.txt
|
||||
#send:Enter
|
||||
#expect-prompt
|
||||
#send:Ctrl+D
|
||||
|
||||
- name: "existing history file with timestamps"
|
||||
pty: true
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
test_files:
|
||||
- path: "history-file.txt"
|
||||
contents: |
|
||||
#1750000000
|
||||
a b c
|
||||
#1750000001
|
||||
1 2 3
|
||||
ignore_stdout: true # Prompts and other don't quite line up
|
||||
stdin: |
|
||||
#expect-prompt
|
||||
history >history-output.txt
|
||||
#send:Enter
|
||||
#expect-prompt
|
||||
#send:Ctrl+D
|
||||
|
||||
- name: "existing history file with timestamps and HISTTIMEFORMAT"
|
||||
pty: true
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
HISTTIMEFORMAT: "%C "
|
||||
test_files:
|
||||
- path: "history-file.txt"
|
||||
contents: |
|
||||
#1750000000
|
||||
a b c
|
||||
#1750000001
|
||||
1 2 3
|
||||
ignore_stdout: true # Prompts and other don't quite line up
|
||||
stdin: |
|
||||
#expect-prompt
|
||||
#send:Ctrl+D
|
||||
|
||||
- name: "history N"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
history 2
|
||||
|
||||
- name: "history with duplicates"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd1
|
||||
: cmd1
|
||||
: cmd2
|
||||
: cmd1
|
||||
history
|
||||
|
||||
- name: "history -c"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
history -c
|
||||
history
|
||||
|
||||
- name: "history -d"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
history -d 1
|
||||
history
|
||||
|
||||
- name: "history -d: negative offset"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
history -d -2
|
||||
history
|
||||
|
||||
- name: "history -a"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo "[histfile]"
|
||||
cat $HISTFILE
|
||||
|
||||
history -a
|
||||
|
||||
echo "[histfile after -a]"
|
||||
cat $HISTFILE
|
||||
|
||||
history -a
|
||||
|
||||
echo "[histfile after second -a]"
|
||||
cat $HISTFILE
|
||||
|
||||
- name: "history -w"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
echo "[histfile]"
|
||||
cat $HISTFILE
|
||||
|
||||
history -w
|
||||
|
||||
echo "[histfile after -w]"
|
||||
cat $HISTFILE
|
||||
|
||||
history -w
|
||||
|
||||
echo "[histfile after second -w]"
|
||||
cat $HISTFILE
|
||||
|
||||
- name: "history -w with explicit file"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
history -w some-file.txt
|
||||
|
||||
- name: "history -s"
|
||||
known_failure: true # NOTE: bash excludes the history command itself from history
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
history -s a b c
|
||||
history
|
||||
|
||||
- name: "HISTCMD"
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
stdin: |
|
||||
: cmd1
|
||||
echo $HISTCMD
|
||||
: cmd2
|
||||
echo $HISTCMD
|
||||
: cmd3
|
||||
echo $HISTCMD
|
||||
|
||||
- name: "HISTTIMEFORMAT"
|
||||
known_failure: true # TODO(history): needs triage
|
||||
args: ["-o", "history"]
|
||||
env:
|
||||
HISTFILE: "history-file.txt"
|
||||
HISTTIMEFORMAT: "century=%C "
|
||||
stdin: |
|
||||
: cmd1
|
||||
: cmd2
|
||||
history
|
||||
|
||||
- name: "history file with unreadable lines"
|
||||
ignore_stderr: true # Ignore brush's warnings about unreadable lines
|
||||
stdin: |
|
||||
bash -c 'printf "\xff\xfe\xfd" >history-file.txt'
|
||||
HISTFILE=history-file.txt $0 -o history -c 'echo hi'
|
||||
@@ -0,0 +1,6 @@
|
||||
name: "Builtins: job-related builtins"
|
||||
cases:
|
||||
- name: "Basic async job"
|
||||
stdin: |
|
||||
echo hi &
|
||||
wait
|
||||
@@ -0,0 +1,27 @@
|
||||
name: "Builtins: kill"
|
||||
cases:
|
||||
- name: "kill -l"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
for i in $(seq 1 31); do kill -l $i; done
|
||||
# limit the number of signals to 31. Realtime signals are not implemented yet.
|
||||
for i in $(kill -l | sed -e "s/[[:digit:]]*)//g"); do echo $i; done | head -31
|
||||
# invalid option
|
||||
kill -l 9999
|
||||
kill -l HUP
|
||||
kill -l iNt
|
||||
kill -l int
|
||||
kill -l SIGHUP
|
||||
kill -l EXIT
|
||||
|
||||
- name: "kill -s"
|
||||
stdin: |
|
||||
kill -s USR1 $$
|
||||
|
||||
- name: "kill -n"
|
||||
stdin: |
|
||||
kill -n 9 $$
|
||||
|
||||
- name: "kill -sigspec"
|
||||
stdin: |
|
||||
kill -USR1 $$
|
||||
@@ -0,0 +1,28 @@
|
||||
name: "Builtins: let"
|
||||
cases:
|
||||
- name: "Basic let usage"
|
||||
stdin: |
|
||||
let 0; echo "0 => $?"
|
||||
let 1; echo "1 => $?"
|
||||
|
||||
let 0==0; echo "0==0 => $?"
|
||||
let 0!=0; echo "0!=0 => $?"
|
||||
|
||||
let 1 0; echo "1 0 => $?"
|
||||
let 0 1; echo "0 1 => $?"
|
||||
|
||||
- name: "let with assignment"
|
||||
stdin: |
|
||||
let x=10; echo "x=10 => $?; x==${x}"
|
||||
|
||||
- name: "let assignment through nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
target=0
|
||||
declare -n ref=target
|
||||
let ref=42
|
||||
echo "target: $target"
|
||||
let ref+=8
|
||||
echo "target: $target"
|
||||
let ref++
|
||||
echo "target: $target"
|
||||
@@ -0,0 +1,21 @@
|
||||
name: "Builtins: local"
|
||||
cases:
|
||||
- name: "Basic local usage"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
local x=10
|
||||
echo "in myfunc: x==$x"
|
||||
}
|
||||
x=5
|
||||
echo "before call: x==$x"
|
||||
myfunc
|
||||
echo "after call: x==$x"
|
||||
|
||||
- name: "Local with empty array"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
local x=()
|
||||
declare -p x
|
||||
echo "x[0]: ${x[0]}"
|
||||
}
|
||||
myfunc
|
||||
@@ -0,0 +1,202 @@
|
||||
name: "Builtins: mapfile"
|
||||
cases:
|
||||
#
|
||||
# Basic functionality
|
||||
#
|
||||
- name: "mapfile basic"
|
||||
stdin: |
|
||||
mapfile arr < <(echo -e "a\nb\nc")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile -t"
|
||||
stdin: |
|
||||
mapfile -t arr < <(echo -e "a\nb\nc")
|
||||
declare -p arr
|
||||
|
||||
- name: "readarray -t"
|
||||
stdin: |
|
||||
readarray -t arr < <(echo -e "a\nb\nc")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile default MAPFILE"
|
||||
stdin: |
|
||||
mapfile -t < <(echo -e "a\nb")
|
||||
declare -p MAPFILE
|
||||
|
||||
#
|
||||
# -O flag
|
||||
#
|
||||
- name: "mapfile -O starts at offset"
|
||||
stdin: |
|
||||
mapfile -t -O 3 arr < <(echo -e "a\nb\nc")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile -O 0 preserves existing"
|
||||
stdin: |
|
||||
arr=(x y z w)
|
||||
mapfile -t -O 0 arr < <(echo -e "a\nb")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile without -O clears existing"
|
||||
stdin: |
|
||||
arr=(x y z w)
|
||||
mapfile -t arr < <(echo -e "a\nb")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile -O preserves sparse array"
|
||||
stdin: |
|
||||
declare -a arr
|
||||
arr[0]=x
|
||||
arr[5]=y
|
||||
arr[10]=z
|
||||
mapfile -t -O 2 arr < <(echo -e "a\nb")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile -O large offset"
|
||||
stdin: |
|
||||
mapfile -t -O 100 arr < <(echo -e "a\nb")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile -O negative"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
mapfile -O -1 arr < /dev/null
|
||||
echo "exit: $?"
|
||||
|
||||
#
|
||||
# Variable state edge cases
|
||||
#
|
||||
- name: "mapfile into nonexistent variable"
|
||||
stdin: |
|
||||
mapfile -t newvar < <(echo -e "a\nb")
|
||||
declare -p newvar
|
||||
|
||||
- name: "mapfile into string variable"
|
||||
stdin: |
|
||||
myvar="hello"
|
||||
mapfile -t myvar < <(echo -e "a\nb")
|
||||
declare -p myvar
|
||||
|
||||
- name: "mapfile -O into string variable"
|
||||
stdin: |
|
||||
myvar="hello"
|
||||
mapfile -t -O 2 myvar < <(echo -e "a\nb")
|
||||
declare -p myvar
|
||||
|
||||
- name: "mapfile into associative array"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
declare -A myassoc
|
||||
mapfile -t myassoc < <(echo -e "a\nb")
|
||||
echo "exit: $?"
|
||||
|
||||
#
|
||||
# -d delimiter
|
||||
#
|
||||
- name: "mapfile -d custom delimiter"
|
||||
stdin: |
|
||||
mapfile -t -d 'Z' arr < <(echo -ne "helloZthereZgeneralZkenobi")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile -d empty string (NUL)"
|
||||
stdin: |
|
||||
mapfile -t -d '' arr < <(printf 'a\0b\0c')
|
||||
declare -p arr
|
||||
|
||||
#
|
||||
# -n count
|
||||
#
|
||||
- name: "mapfile -n limits count"
|
||||
stdin: |
|
||||
mapfile -t -n 2 arr < <(echo -e "a\nb\nc\nd")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile -n 0 no limit"
|
||||
stdin: |
|
||||
mapfile -t -n 0 arr < <(echo -e "a\nb\nc")
|
||||
declare -p arr
|
||||
|
||||
#
|
||||
# -s skip
|
||||
#
|
||||
- name: "mapfile -s"
|
||||
stdin: |
|
||||
mapfile -t -s 2 arr < <(echo -e "a\nb\nc\nd")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile -s with -n"
|
||||
stdin: |
|
||||
mapfile -t -s 1 -n 2 arr < <(echo -e "a\nb\nc\nd\ne")
|
||||
declare -p arr
|
||||
|
||||
#
|
||||
# -u file descriptor
|
||||
#
|
||||
- name: "mapfile -u reads from fd"
|
||||
stdin: |
|
||||
exec 3< <(echo -e "a\nb\nc")
|
||||
mapfile -t -u 3 arr
|
||||
exec 3<&-
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile -u invalid fd"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
mapfile -u 99 arr
|
||||
echo "exit: $?"
|
||||
|
||||
#
|
||||
# Combinations
|
||||
#
|
||||
- name: "mapfile -O with -s and -n"
|
||||
stdin: |
|
||||
mapfile -t -O 10 -s 1 -n 2 arr < <(echo -e "a\nb\nc\nd\ne")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile multiple invocations without -O"
|
||||
stdin: |
|
||||
mapfile -t arr < <(echo -e "a\nb")
|
||||
mapfile -t arr < <(echo -e "x\ny\nz")
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile multiple invocations with -O"
|
||||
stdin: |
|
||||
mapfile -t arr < <(echo -e "a\nb")
|
||||
mapfile -t -O 2 arr < <(echo -e "x\ny\nz")
|
||||
declare -p arr
|
||||
|
||||
#
|
||||
# Empty input
|
||||
#
|
||||
- name: "mapfile empty input"
|
||||
stdin: |
|
||||
mapfile -t arr < /dev/null
|
||||
declare -p arr 2>/dev/null || echo "arr not set"
|
||||
|
||||
- name: "mapfile empty input clears existing"
|
||||
stdin: |
|
||||
arr=(x y z)
|
||||
mapfile -t arr < /dev/null
|
||||
declare -p arr 2>/dev/null || echo "arr not set"
|
||||
|
||||
- name: "mapfile -O empty input preserves"
|
||||
stdin: |
|
||||
arr=(x y z)
|
||||
mapfile -t -O 0 arr < /dev/null
|
||||
declare -p arr
|
||||
|
||||
- name: "mapfile into nameref variable"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
declare -a target
|
||||
declare -n ref=target
|
||||
mapfile -t ref < <(echo -e "a\nb\nc")
|
||||
declare -p target
|
||||
|
||||
- name: "mapfile -O into nameref variable"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
target=(x y)
|
||||
declare -n ref=target
|
||||
mapfile -t -O 2 ref < <(echo -e "a\nb")
|
||||
declare -p target
|
||||
@@ -0,0 +1,347 @@
|
||||
name: "Builtins: printf"
|
||||
|
||||
cases:
|
||||
# Basic functionality tests
|
||||
- name: "One-variable printf"
|
||||
stdin: |
|
||||
printf "%s" "0"
|
||||
|
||||
- name: "Basic printf"
|
||||
stdin: |
|
||||
printf "%s, %s" "Hello" "world"
|
||||
|
||||
- name: "printf -v"
|
||||
stdin: |
|
||||
printf -v myvar "%s, %s" "Hello" "world"
|
||||
echo "myvar: '${myvar}'"
|
||||
|
||||
- name: "printf -v with array index"
|
||||
stdin: |
|
||||
declare -a myarray=()
|
||||
printf -v 'myarray[5]' "%s, %s" "Hello" "world"
|
||||
declare | grep myarray
|
||||
|
||||
- name: "printf with -v as a format arg"
|
||||
stdin: |
|
||||
printf "%s\n" "-v"
|
||||
|
||||
# Bash-specific %q format (shell-safe quoting)
|
||||
- name: "printf %q"
|
||||
stdin: |
|
||||
echo "[1]"
|
||||
printf "%q" 'TEXT'; echo
|
||||
|
||||
echo "[2]"
|
||||
printf "%q" '$VAR'; echo
|
||||
|
||||
echo "[3]"
|
||||
printf "%q" '"'; echo
|
||||
|
||||
- name: "printf ~%q"
|
||||
stdin: |
|
||||
echo "[1]"
|
||||
printf "~%q" 'TEXT'; echo
|
||||
|
||||
echo "[2]"
|
||||
printf "~%q" '$VAR'; echo
|
||||
|
||||
echo "[3]"
|
||||
printf "~%q" '"'; echo
|
||||
|
||||
# =================================================================
|
||||
# STANDARD C PRINTF FORMAT SPECIFIERS
|
||||
# These should work with external printf and bash builtin printf
|
||||
# =================================================================
|
||||
|
||||
# Integer formatting tests
|
||||
- name: "printf %d with integers"
|
||||
stdin: |
|
||||
printf "%d\n" 42
|
||||
printf "%d\n" -17
|
||||
printf "%d\n" 0
|
||||
|
||||
- name: "printf %o (octal)"
|
||||
stdin: |
|
||||
printf "%o\n" 42
|
||||
printf "%o\n" 8
|
||||
printf "%o\n" 255
|
||||
|
||||
- name: "printf %x and %X (hex)"
|
||||
stdin: |
|
||||
printf "%x\n" 255
|
||||
printf "%X\n" 255
|
||||
printf "%x\n" 42
|
||||
|
||||
# Floating point tests
|
||||
- name: "printf %f (float)"
|
||||
stdin: |
|
||||
printf "%f\n" 3.14159
|
||||
printf "%.2f\n" 3.14159
|
||||
printf "%6.2f\n" 3.14159
|
||||
|
||||
- name: "printf %e and %E (scientific)"
|
||||
stdin: |
|
||||
printf "%e\n" 1234.5
|
||||
printf "%E\n" 1234.5
|
||||
|
||||
- name: "printf %g and %G (general)"
|
||||
stdin: |
|
||||
printf "%g\n" 1234.5
|
||||
printf "%G\n" 0.00012345
|
||||
|
||||
# Character formatting
|
||||
- name: "printf %c (character)"
|
||||
stdin: |
|
||||
printf "%c\n" 65
|
||||
printf "%c\n" 97
|
||||
printf "%c%c%c\n" 72 101 121
|
||||
|
||||
# Width and precision tests
|
||||
- name: "printf width formatting"
|
||||
stdin: |
|
||||
printf "%5s|\n" "hi"
|
||||
printf "%-5s|\n" "hi"
|
||||
printf "%05d|\n" 42
|
||||
|
||||
- name: "printf precision with strings"
|
||||
stdin: |
|
||||
printf "%.3s\n" "hello"
|
||||
printf "%10.3s\n" "hello"
|
||||
|
||||
# Multiple arguments and reuse
|
||||
- name: "printf argument reuse"
|
||||
stdin: |
|
||||
printf "%s %s %s\n" "one" "two"
|
||||
printf "%d %d %d\n" 1 2
|
||||
|
||||
# Special characters and escapes
|
||||
- name: "printf with escape sequences"
|
||||
stdin: |
|
||||
printf "hello\nworld\n"
|
||||
printf "tab\there\n"
|
||||
printf "quote: \"\n"
|
||||
|
||||
- name: "printf with backslash escapes"
|
||||
stdin: |
|
||||
printf "\\n\\t\\r\\\\\n"
|
||||
|
||||
# Missing arguments (bash handles gracefully)
|
||||
- name: "printf missing arguments"
|
||||
stdin: |
|
||||
printf "%s %s\n" "only_one"
|
||||
|
||||
# Empty and edge cases
|
||||
- name: "printf with empty format"
|
||||
stdin: |
|
||||
printf ""
|
||||
|
||||
- name: "printf with just format string"
|
||||
stdin: |
|
||||
printf "just text\n"
|
||||
|
||||
# Type conversion tests (safe cases only)
|
||||
- name: "printf valid string to number conversion"
|
||||
stdin: |
|
||||
printf "%d\n" "42"
|
||||
printf "%d\n" "0"
|
||||
|
||||
- name: "printf with percent literal"
|
||||
stdin: |
|
||||
printf "100%% complete\n"
|
||||
|
||||
- name: "printf %b (backslash escape expansion)"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
printf "%b\n" "hello\\nworld"
|
||||
printf "%b\n" "tab\\there"
|
||||
printf "%b\n" "quote\\\"here"
|
||||
|
||||
- name: "printf %b with field width"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
printf "%10b|\n" "hello\\nworld"
|
||||
printf "%-10b|\n" "tab\\there"
|
||||
|
||||
- name: "printf %T (date/time formatting)"
|
||||
known_failure: true # Not supported by uucore
|
||||
stdin: |
|
||||
printf "%(%Y-%m-%d)T\n" 1234567890
|
||||
|
||||
# Test argument reuse with bash-specific formats
|
||||
- name: "printf %q argument reuse"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
printf "%q %q %q\n" "one with spaces" "two"
|
||||
|
||||
- name: "printf %b argument reuse"
|
||||
stdin: |
|
||||
printf "%b %b %b\n" "one\\ntwo" "three"
|
||||
|
||||
# Test C-style constant interpretation
|
||||
- name: "printf with quoted character arguments"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
printf "%d\n" "'A"
|
||||
printf "%d\n" '"'
|
||||
|
||||
- name: "printf with plus/minus signs in numeric arguments"
|
||||
stdin: |
|
||||
printf "%d\n" "+42"
|
||||
printf "%d\n" "-17"
|
||||
|
||||
# =================================================================
|
||||
# ADDITIONAL STANDARD FORMAT SPECIFIERS
|
||||
# More comprehensive coverage of printf(3) man page features
|
||||
# =================================================================
|
||||
|
||||
# Additional format specifiers from man page
|
||||
- name: "printf %i (signed integer)"
|
||||
stdin: |
|
||||
printf "%i\n" 42
|
||||
printf "%i\n" -17
|
||||
|
||||
- name: "printf %u (unsigned integer)"
|
||||
stdin: |
|
||||
printf "%u\n" 42
|
||||
printf "%u\n" 4294967295
|
||||
|
||||
# Advanced width and precision
|
||||
- name: "printf with asterisk width and precision"
|
||||
stdin: |
|
||||
printf "%*s|\n" 10 "hello"
|
||||
printf "%.*s|\n" 3 "hello world"
|
||||
printf "%*.*s|\n" 10 3 "hello world"
|
||||
|
||||
- name: "printf with zero precision and zero arg"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
printf "%.0d\n" 0
|
||||
|
||||
- name: "printf with zero precision"
|
||||
stdin: |
|
||||
printf "%.0d\n" 42
|
||||
|
||||
# Flag characters
|
||||
- name: "printf with flag characters"
|
||||
stdin: |
|
||||
printf "%+d\n" 42
|
||||
printf "% d\n" 42
|
||||
printf "%#x\n" 255
|
||||
printf "%#o\n" 64
|
||||
|
||||
# Left justification
|
||||
- name: "printf left justification"
|
||||
stdin: |
|
||||
printf "%-10s|\n" "left"
|
||||
printf "%-10d|\n" 42
|
||||
|
||||
# Zero padding
|
||||
- name: "printf zero padding"
|
||||
stdin: |
|
||||
printf "%010d\n" 42
|
||||
printf "%010.2f\n" 3.14
|
||||
|
||||
# Combining flags
|
||||
- name: "printf combining flags"
|
||||
stdin: |
|
||||
printf "%+010d\n" 42
|
||||
printf "%-+10d|\n" 42
|
||||
|
||||
# More precision tests
|
||||
- name: "printf precision with integers"
|
||||
stdin: |
|
||||
printf "%.5d\n" 42
|
||||
printf "%.5o\n" 42
|
||||
printf "%.5x\n" 42
|
||||
|
||||
# Large numbers and edge cases
|
||||
- name: "printf with large numbers"
|
||||
stdin: |
|
||||
printf "%d\n" 2147483647
|
||||
printf "%u\n" 4294967295
|
||||
printf "%x\n" 4294967295
|
||||
|
||||
# More character and escape testing
|
||||
- name: "printf with special characters"
|
||||
stdin: |
|
||||
printf "%c\n" 0
|
||||
printf "%c\n" 32
|
||||
printf "%c\n" 127
|
||||
|
||||
# Negative numbers with different formats
|
||||
- name: "printf negative numbers"
|
||||
stdin: |
|
||||
printf "%d\n" -1
|
||||
printf "%o\n" -1
|
||||
printf "%x\n" -1
|
||||
printf "%u\n" -1
|
||||
|
||||
# Empty string handling
|
||||
- name: "printf with empty strings"
|
||||
stdin: |
|
||||
printf "%s|\n" ""
|
||||
printf "%5s|\n" ""
|
||||
printf "%.5s|\n" ""
|
||||
|
||||
# More empty string handling
|
||||
- name: "printf with empty strings for numbers"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
printf "%d" ""
|
||||
printf "%x" ""
|
||||
|
||||
# Scientific notation edge cases
|
||||
- name: "printf scientific notation edge cases"
|
||||
stdin: |
|
||||
printf "%e\n" 0.0
|
||||
printf "%E\n" 0.0
|
||||
printf "%g\n" 0.0000001
|
||||
printf "%G\n" 1000000.0
|
||||
|
||||
# Mixed format strings
|
||||
- name: "printf mixed formats in one string"
|
||||
stdin: |
|
||||
printf "Dec: %d, Hex: %x, Oct: %o, Char: %c\n" 65 65 65 65
|
||||
|
||||
# Field width larger than content
|
||||
- name: "printf wide fields"
|
||||
stdin: |
|
||||
printf "%20s|\n" "short"
|
||||
printf "%20d|\n" 42
|
||||
|
||||
# Return value testing (should always be 0 for successful printf)
|
||||
- name: "printf return value"
|
||||
stdin: |
|
||||
printf "test"
|
||||
echo " exit: $?"
|
||||
|
||||
# Error handling tests - marked as known failures due to external printf differences
|
||||
- name: "printf with invalid format"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
printf "%z\n" "test"
|
||||
|
||||
- name: "printf with no arguments"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
printf
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "printf string to number conversion with invalid input"
|
||||
known_failure: true # Need to investigate
|
||||
stdin: |
|
||||
printf "%d\n" "abc"
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "printf %q with empty string"
|
||||
stdin: |
|
||||
printf '%q\n' ''
|
||||
printf '~%q\n' ''
|
||||
|
||||
- name: "printf -v through nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
declare -n ref=target
|
||||
printf -v ref "formatted %d" 42
|
||||
echo "target: $target"
|
||||
echo "ref: $ref"
|
||||
@@ -0,0 +1,8 @@
|
||||
name: "Builtins: PS4"
|
||||
cases:
|
||||
- name: "PS4 expansion"
|
||||
stdin: |
|
||||
PS4='+$FUNCNAME '
|
||||
bar() { true; }
|
||||
foo() { set -x; bar; set +x; }
|
||||
foo
|
||||
@@ -0,0 +1,65 @@
|
||||
name: "Builtins: pushd/popd/dirs"
|
||||
incompatible_configs: ["sh"]
|
||||
cases:
|
||||
- name: "Basic pushd usage"
|
||||
stdin: |
|
||||
cd /
|
||||
pushd /
|
||||
echo $?
|
||||
echo $PWD
|
||||
pushd /usr
|
||||
dirs
|
||||
echo $?
|
||||
echo $PWD
|
||||
popd
|
||||
echo $?
|
||||
echo $PWD
|
||||
popd
|
||||
echo $?
|
||||
|
||||
- name: "pushd without dir change"
|
||||
stdin: |
|
||||
cd /
|
||||
pushd -n /usr
|
||||
dirs
|
||||
|
||||
- name: "popd without dir change"
|
||||
stdin: |
|
||||
cd /
|
||||
pushd /
|
||||
pushd /usr
|
||||
popd -n
|
||||
dirs
|
||||
|
||||
- name: "popd with empty stack"
|
||||
ignore_stderr: true
|
||||
stdin: popd
|
||||
|
||||
- name: "pushd to non-existent dir"
|
||||
ignore_stderr: true
|
||||
stdin: pushd /non-existent-dir
|
||||
|
||||
- name: "basic dirs usage"
|
||||
stdin: |
|
||||
cd /
|
||||
dirs
|
||||
|
||||
- name: "dirs with tilde replacement"
|
||||
env:
|
||||
HOME: /usr
|
||||
stdin: |
|
||||
echo "HOME: $HOME"
|
||||
|
||||
cd ~
|
||||
echo "New PWD: $PWD"
|
||||
|
||||
dirs
|
||||
dirs -l
|
||||
|
||||
- name: "dirs to clear"
|
||||
stdin: |
|
||||
cd /usr
|
||||
pushd /usr
|
||||
pushd /
|
||||
dirs -c
|
||||
dirs
|
||||
@@ -0,0 +1,70 @@
|
||||
name: "Builtins: pwd"
|
||||
cases:
|
||||
- name: "Basic pwd usage"
|
||||
stdin: |
|
||||
cd /
|
||||
pwd
|
||||
echo "Result: $?"
|
||||
#
|
||||
cd usr
|
||||
pwd
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "pwd -LP"
|
||||
stdin: |
|
||||
mkdir -p ./level1/level2/level3
|
||||
cd level1
|
||||
ln -s ./level2/level3 ./symlink
|
||||
|
||||
(
|
||||
cd ./symlink
|
||||
basename $(pwd)
|
||||
basename $(pwd -L)
|
||||
basename $(pwd -P)
|
||||
)
|
||||
(
|
||||
cd ./symlink
|
||||
export PWD=
|
||||
basename $(pwd)
|
||||
basename $(pwd -L)
|
||||
basename $(pwd -P)
|
||||
)
|
||||
(
|
||||
cd ./symlink
|
||||
export PWD=
|
||||
# start a shell without $PWD
|
||||
(
|
||||
basename $(pwd)
|
||||
basename $(pwd -L)
|
||||
basename $(pwd -P)
|
||||
)
|
||||
)
|
||||
|
||||
cd ~
|
||||
pwd
|
||||
pwd -L
|
||||
pwd -P
|
||||
|
||||
- name: "pwd with moved dir"
|
||||
known_failure: true # Needs investigation
|
||||
stdin: |
|
||||
root=$(pwd)
|
||||
|
||||
mkdir -p ${root}/subdir
|
||||
cd ${root}/subdir
|
||||
mv ${root}/subdir ${root}/renamed
|
||||
|
||||
echo "pwd -L: $(basename $(pwd -L))"
|
||||
echo "pwd -P: $(basename $(pwd -P))"
|
||||
|
||||
- name: "pwd with removed dir"
|
||||
known_failure: true # Needs investigation
|
||||
stdin: |
|
||||
root=$(pwd)
|
||||
|
||||
mkdir -p ${root}/subdir
|
||||
cd ${root}/subdir
|
||||
rmdir ${root}/subdir
|
||||
|
||||
echo "pwd -L: $(basename $(pwd -L))"
|
||||
echo "pwd -P: $(basename $(pwd -P))"
|
||||
@@ -0,0 +1,392 @@
|
||||
name: "Builtins: read"
|
||||
cases:
|
||||
- name: "Basic read usage from file"
|
||||
test_files:
|
||||
- path: "data.txt"
|
||||
contents: |
|
||||
a 1
|
||||
b 2
|
||||
stdin: |
|
||||
while read name num; do echo "Hello, $name => $num"; done < data.txt
|
||||
echo "Exited loop; name='$name', num='$num'"
|
||||
|
||||
- name: "Basic read usage from file and no var name"
|
||||
test_files:
|
||||
- path: "data.txt"
|
||||
contents: |
|
||||
a 1
|
||||
b 2
|
||||
stdin: |
|
||||
while read; do echo "reply: '$REPLY'"; done < data.txt
|
||||
echo "Exited loop; reply='$REPLY'"
|
||||
|
||||
- name: "Basic read usage with empty file"
|
||||
stdin: |
|
||||
while read name num; do echo "Hello, $name => $num"; done < /dev/null
|
||||
declare -p name num
|
||||
|
||||
while read; do echo "reply: '$REPLY'"; done < /dev/null
|
||||
declare -p REPLY
|
||||
|
||||
while read -a arr; do declare -p arr; done < /dev/null
|
||||
declare -p arr
|
||||
|
||||
- name: "Basic read usage from pipe"
|
||||
stdin: |
|
||||
echo "1."
|
||||
(echo a; echo b) | while read name; do echo "Hello, $name"; done
|
||||
|
||||
echo "2."
|
||||
(echo "a b") | while read name; do echo "Hello, 1:$name REPLY:$REPLY"; done
|
||||
|
||||
echo "3."
|
||||
(echo "a b") | while read -a arr; do declare -p arr; done
|
||||
|
||||
- name: "read with closed stdin"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo 1 | read <&-
|
||||
echo "result: $?"
|
||||
|
||||
- name: "read from here string"
|
||||
stdin: |
|
||||
read myvar <<< "hello"
|
||||
echo "myvar: ${myvar}"
|
||||
|
||||
- name: "read from process substitution"
|
||||
stdin: |
|
||||
read myvar < <(echo hello)
|
||||
echo "myvar: ${myvar}"
|
||||
|
||||
- name: "read with custom IFS"
|
||||
stdin: |
|
||||
content=" a b c "
|
||||
while IFS= read line; do
|
||||
echo "LINE: '$line'"
|
||||
done <<<"${content}"
|
||||
|
||||
- name: "read text with tabs and custom IFS"
|
||||
stdin: |
|
||||
while IFS="" read myvar; do
|
||||
echo "myvar1: |${myvar}|"
|
||||
done < <(printf "a\tb\nc d\te\n")
|
||||
|
||||
- name: "read with empty entries"
|
||||
stdin: |
|
||||
(echo "x"; echo ""; echo "y"; echo ""; echo "") | while read line; do echo "LINE: '$line'"; done
|
||||
echo 'x,,y,z' | (IFS=',' read READ1 READ2 READ3 READ4; declare -p READ1; declare -p READ2; declare -p READ3; declare -p READ4)
|
||||
|
||||
- name: "read -a with empty lines"
|
||||
stdin: |
|
||||
(echo "hi"; echo ""; echo "there"; echo ""; echo "you") | (read -a READ; declare -p READ)
|
||||
(echo -e "hi\t\tthere\t\tyou") | (read -a READ -d $'\t'; declare -p READ)
|
||||
|
||||
- name: "read -a with empty interior field"
|
||||
stdin: |
|
||||
echo -e -n "hi||there||you" | (IFS='|' read -a READ; declare -p READ)
|
||||
echo -e -n "hi there you " | (IFS=' ' read -a READ; declare -p READ)
|
||||
echo -e -n "hi there you " | (read -a READ; declare -p READ)
|
||||
|
||||
- name: "read -a with empty leading field"
|
||||
stdin: |
|
||||
echo -e -n "|hi|there|you" | (IFS='|' read -a READ; declare -p READ)
|
||||
|
||||
- name: "read -a with empty trailing field"
|
||||
stdin: |
|
||||
echo -e -n "hi|there|you|" | (IFS='|' read -a READ; declare -p READ)
|
||||
|
||||
- name: "read -a with empty entries + empty delimiter"
|
||||
stdin: |
|
||||
(echo "hi"; echo ""; echo "there"; echo ""; echo "you") | (read -a READ -d ''; declare -p READ)
|
||||
echo -e -n "hi\n\nthere\n\nyou\n" | (read -a READ -d ''; declare -p READ)
|
||||
|
||||
- name: "read -a with empty entries + empty delimiter + custom IFS"
|
||||
stdin: |
|
||||
(echo 'x,,y,z'; echo 'w,v'; echo ''; echo ''; echo 'm') | (IFS=',' read -d "" -a READ; declare -p READ)
|
||||
|
||||
- name: "read with empty delimiter"
|
||||
min_oracle_version: 5.2 # \n renders differently in older shell versions
|
||||
stdin: |
|
||||
echo x | (read -d ""; declare -p REPLY)
|
||||
|
||||
# Timeout tests (read -t)
|
||||
- name: "read -t 0 with data available in pipe"
|
||||
# TODO(read):
|
||||
# This test is racy - poll(0) may check the pipe before echo's data arrives.
|
||||
# Bash consistently wins this race; our implementation sometimes doesn't.
|
||||
# Worth investigating.
|
||||
skip: true
|
||||
stdin: |
|
||||
echo "test" | { read -t 0; echo "exit: $?"; }
|
||||
|
||||
- name: "read -t 0 with no data (empty pipe)"
|
||||
skip: true # TODO(read): see above
|
||||
stdin: |
|
||||
# Closed pipe with no data
|
||||
: | { read -t 0; echo "exit: $?"; }
|
||||
|
||||
- name: "read -t with data immediately available"
|
||||
stdin: |
|
||||
echo "hello world" | { read -t 1 line; echo "exit: $?, line: '$line'"; }
|
||||
|
||||
- name: "read -t with fractional timeout and data available"
|
||||
stdin: |
|
||||
echo "test data" | { read -t 0.5 var; echo "exit: $?, var: '$var'"; }
|
||||
|
||||
- name: "read -t with regular file (always ready)"
|
||||
test_files:
|
||||
- path: "data.txt"
|
||||
contents: |
|
||||
line1
|
||||
line2
|
||||
stdin: |
|
||||
read -t 0 < data.txt; echo "exit: $?"
|
||||
read -t 1 line < data.txt; echo "exit: $?, line: '$line'"
|
||||
|
||||
- name: "read -t negative timeout is invalid"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
read -t -1
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "read -t with -n option"
|
||||
stdin: |
|
||||
echo "abcdef" | { read -t 1 -n 3 var; echo "exit: $?, var: '$var'"; }
|
||||
|
||||
- name: "read -t 0 with here string"
|
||||
stdin: |
|
||||
read -t 0 <<< "hello"; echo "exit: $?"
|
||||
read -t 1 var <<< "hello"; echo "exit: $?, var: '$var'"
|
||||
|
||||
# Raw mode tests (read -r)
|
||||
- name: "read -r preserves backslashes"
|
||||
stdin: |
|
||||
echo 'hello\nworld' | { read -r line; echo "line: '$line'"; }
|
||||
echo 'path\\to\\file' | { read -r line; echo "line: '$line'"; }
|
||||
|
||||
- name: "read without -r processes escape sequences"
|
||||
stdin: |
|
||||
echo 'hello\nworld' | { read line; echo "line: '$line'"; }
|
||||
echo 'a\\b' | { read line; echo "line: '$line'"; }
|
||||
|
||||
- name: "read -r with trailing backslash"
|
||||
stdin: |
|
||||
printf 'line with trailing\\' | { read -r line; echo "line: '$line'"; }
|
||||
|
||||
- name: "read without -r line continuation"
|
||||
stdin: |
|
||||
# Backslash-newline continues to next line
|
||||
printf 'first\\\nsecond' | { read line; echo "line: '$line'"; }
|
||||
|
||||
- name: "read -r does not do line continuation"
|
||||
stdin: |
|
||||
printf 'first\\\nsecond' | { read -r line; echo "line: '$line'"; }
|
||||
|
||||
# -N vs -n option tests
|
||||
- name: "read -n respects delimiter"
|
||||
stdin: |
|
||||
# -n stops at delimiter even before char count
|
||||
echo "ab:cd" | { read -n 5 var; echo "var: '$var'"; }
|
||||
|
||||
- name: "read -N ignores delimiter"
|
||||
skip: true # TODO(read): test sometimes results in printf error
|
||||
stdin: |
|
||||
# -N reads exactly N chars, delimiter treated as regular char
|
||||
printf "ab:cd" | { read -N 4 var; echo "var: '$var'"; }
|
||||
|
||||
- name: "read -N does not split by IFS"
|
||||
stdin: |
|
||||
# -N should not split result by IFS
|
||||
printf "a b c" | { read -N 5 var; echo "var: '$var'"; }
|
||||
|
||||
- name: "read -N with array"
|
||||
stdin: |
|
||||
# With -a and -N, entire input goes to single array element
|
||||
printf "a b c" | { read -N 5 -a arr; declare -p arr; }
|
||||
|
||||
# Exit code tests
|
||||
- name: "read exit code on success"
|
||||
stdin: |
|
||||
echo "data" | { read var; echo "exit: $?"; }
|
||||
|
||||
- name: "read exit code on EOF"
|
||||
stdin: |
|
||||
# EOF returns non-zero
|
||||
read var < /dev/null; echo "exit: $?"
|
||||
|
||||
- name: "read exit code on EOF with partial data"
|
||||
stdin: |
|
||||
# EOF with partial data still returns non-zero but assigns
|
||||
printf "partial" | { read var; echo "exit: $?, var: '$var'"; }
|
||||
|
||||
# IFS edge cases
|
||||
- name: "read with IFS containing both whitespace and non-whitespace"
|
||||
stdin: |
|
||||
echo "a:b c:d" | (IFS=': ' read v1 v2 v3 v4; echo "v1='$v1' v2='$v2' v3='$v3' v4='$v4'")
|
||||
|
||||
- name: "read with consecutive non-whitespace delimiters"
|
||||
stdin: |
|
||||
echo "a::b" | (IFS=':' read v1 v2 v3; echo "v1='$v1' v2='$v2' v3='$v3'")
|
||||
|
||||
- name: "read assignment to fewer variables than fields"
|
||||
stdin: |
|
||||
echo "a b c d e" | { read v1 v2; echo "v1='$v1' v2='$v2'"; }
|
||||
|
||||
- name: "read assignment to more variables than fields"
|
||||
stdin: |
|
||||
echo "a b" | { read v1 v2 v3 v4; echo "v1='$v1' v2='$v2' v3='$v3' v4='$v4'"; }
|
||||
|
||||
- name: "read last variable preserves original delimiters"
|
||||
stdin: |
|
||||
# Last variable should get remainder with original delimiters, not re-joined with space
|
||||
echo "x:y:z:w" | (IFS=':' read a b; echo "a='$a' b='$b'")
|
||||
echo "x:y::z:w" | (IFS=':' read a b; echo "a='$a' b='$b'")
|
||||
echo "a:b c:d e" | (IFS=': ' read v1 v2; echo "v1='$v1' v2='$v2'")
|
||||
|
||||
- name: "read with consecutive delimiters at field boundary"
|
||||
stdin: |
|
||||
# When field limit causes boundary at consecutive delimiters,
|
||||
# extra delimiters should be preserved in remainder.
|
||||
echo "x::y" | (IFS=':' read a b; echo "a='$a' b='$b'")
|
||||
echo "x:::y" | (IFS=':' read a b; echo "a='$a' b='$b'")
|
||||
# Also test with more than 2 variables
|
||||
echo "a::b:c" | (IFS=':' read v1 v2 v3; echo "v1='$v1' v2='$v2' v3='$v3'")
|
||||
|
||||
- name: "read with empty IFS preserves entire line"
|
||||
stdin: |
|
||||
# Empty IFS means no splitting - entire line goes to first variable
|
||||
echo "a b c" | (IFS='' read v1 v2 v3; echo "v1='$v1' v2='$v2' v3='$v3'")
|
||||
echo "a:b:c" | (IFS='' read v1 v2; echo "v1='$v1' v2='$v2'")
|
||||
# With array
|
||||
echo "a b c" | (IFS='' read -a arr; declare -p arr)
|
||||
|
||||
- name: "read REPLY preserves leading and trailing whitespace"
|
||||
stdin: |
|
||||
# When no variable names given, REPLY should preserve whitespace
|
||||
read <<< ' hello world '
|
||||
echo "REPLY='$REPLY'"
|
||||
read <<< ' tab separated '
|
||||
echo "REPLY='$REPLY'"
|
||||
|
||||
- name: "read -a clears pre-existing array elements"
|
||||
stdin: |
|
||||
# Array should be cleared before assignment, not merged
|
||||
arr=(old1 old2 old3 old4 old5)
|
||||
read -a arr <<< 'new1 new2'
|
||||
declare -p arr
|
||||
|
||||
- name: "read -N still processes backslashes without -r"
|
||||
stdin: |
|
||||
# -N ignores delimiter but still does backslash processing
|
||||
printf 'a\\nb' | { read -N 4 var; echo "var='$var'"; }
|
||||
printf 'a\\\\b' | { read -N 4 var; echo "var='$var'"; }
|
||||
|
||||
- name: "read -r -N for truly raw fixed-length reads"
|
||||
stdin: |
|
||||
# -r -N should preserve backslashes exactly
|
||||
printf 'a\\nb' | { read -r -N 4 var; echo "var='$var'"; }
|
||||
printf 'a\\\\b' | { read -r -N 4 var; echo "var='$var'"; }
|
||||
|
||||
- name: "read -u with invalid file descriptor"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
read -u 99 var
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "read into readonly variable"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
readonly myvar="original"
|
||||
echo "test" | read myvar
|
||||
echo "exit: $?, myvar='$myvar'"
|
||||
|
||||
- name: "read with mixed IFS into fewer variables"
|
||||
stdin: |
|
||||
# Complex IFS with fewer variables - verify remainder handling
|
||||
echo 'x: y : z : w' | (IFS=': ' read a b; echo "a='$a' b='$b'")
|
||||
|
||||
# Backslash escape counting tests (bash counts OUTPUT chars, not INPUT)
|
||||
- name: "read -n counts output chars after escape processing"
|
||||
stdin: |
|
||||
# Bash counts OUTPUT characters toward -n limit (after escape processing).
|
||||
# Input: a, \, b, c = 4 input bytes. With -n 3:
|
||||
# 'a' (output 1), '\b' → 'b' (output 2), 'c' (output 3) = "abc"
|
||||
printf 'a\\bc' | { read -n 3 var; echo "var='$var'"; }
|
||||
# Input: \, \, a, b = 4 input bytes. With -n 3:
|
||||
# '\\' → '\' (output 1), 'a' (output 2), 'b' (output 3) = "\ab"
|
||||
printf '\\\\ab' | { read -n 3 var; echo "var='$var'"; }
|
||||
|
||||
- name: "read -n with backslash-newline continuation"
|
||||
stdin: |
|
||||
# Backslash-newline is line continuation (consumes both, outputs nothing).
|
||||
# Input: a, b, \, \n, c = 5 input bytes. With -n 3:
|
||||
# 'a' (output 1), 'b' (output 2), '\n' → continuation, 'c' (output 3) = "abc"
|
||||
printf 'ab\\\nc' | { read -n 3 var; echo "var='$var'"; }
|
||||
|
||||
- name: "read trailing backslash discarded on EOF"
|
||||
stdin: |
|
||||
# Bash discards a pending backslash when EOF is reached
|
||||
printf 'ab\\' | { read var; echo "var='$var'"; }
|
||||
printf 'ab\\' | { read -n 5 var; echo "var='$var'"; }
|
||||
|
||||
# Prompt tests (read -p)
|
||||
- name: "read -p reads input correctly"
|
||||
stdin: |
|
||||
# Test that -p option reads input correctly (prompt display behavior
|
||||
# differs between bash and brush when stdin is not a terminal)
|
||||
read -p "Enter: " var <<< "hello"
|
||||
echo "var='$var'"
|
||||
|
||||
# File descriptor tests (read -u)
|
||||
- name: "read -u with valid file descriptor"
|
||||
stdin: |
|
||||
# Read from a specific file descriptor
|
||||
exec 3<<< "from fd 3"
|
||||
read -u 3 var
|
||||
echo "var='$var'"
|
||||
exec 3<&-
|
||||
|
||||
- name: "read -u combined with other options"
|
||||
stdin: |
|
||||
exec 4<<< "hello world test"
|
||||
read -u 4 -a arr
|
||||
declare -p arr
|
||||
exec 4<&-
|
||||
|
||||
# Silent mode tests (read -s)
|
||||
- name: "read -s with pipe input"
|
||||
stdin: |
|
||||
# Silent mode should still read input correctly (just not echo it)
|
||||
echo "secret" | { read -s var; echo "var='$var'"; }
|
||||
|
||||
- name: "read -s with multiple variables"
|
||||
stdin: |
|
||||
echo "a b c" | { read -s v1 v2 v3; echo "v1='$v1' v2='$v2' v3='$v3'"; }
|
||||
|
||||
- name: "read into nameref variable"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
declare -n ref=result
|
||||
read ref <<< "input_value"
|
||||
echo "result: $result"
|
||||
echo "ref: $ref"
|
||||
|
||||
- name: "read -a into nameref array"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
declare -n ref=myarr
|
||||
read -a ref <<< "one two three"
|
||||
echo "myarr[0]: ${myarr[0]}"
|
||||
echo "myarr[1]: ${myarr[1]}"
|
||||
echo "myarr[2]: ${myarr[2]}"
|
||||
echo "ref[1]: ${ref[1]}"
|
||||
|
||||
- name: "read into subscripted nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
arr=(a b c)
|
||||
declare -n ref='arr[1]'
|
||||
read ref <<< "replaced"
|
||||
echo "arr[0]: ${arr[0]}"
|
||||
echo "arr[1]: ${arr[1]}"
|
||||
echo "arr[2]: ${arr[2]}"
|
||||
@@ -0,0 +1,53 @@
|
||||
name: "Builtins: readonly"
|
||||
cases:
|
||||
- name: "making var readonly"
|
||||
stdin: |
|
||||
my_var="value"
|
||||
readonly my_var
|
||||
|
||||
echo "Invoking declare -p..."
|
||||
declare -p my_var
|
||||
|
||||
- name: "using readonly with value"
|
||||
stdin: |
|
||||
readonly my_var="my_value"
|
||||
|
||||
echo "Invoking declare -p..."
|
||||
declare -p my_var
|
||||
|
||||
- name: "readonly array after append"
|
||||
stdin: |
|
||||
ARR=()
|
||||
f() {
|
||||
ARR+=("a" "b")
|
||||
readonly ARR
|
||||
echo "${#ARR[@]}"
|
||||
}
|
||||
f
|
||||
|
||||
- name: "readonly on nameref makes nameref readonly"
|
||||
known_failure: true
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
target="value"
|
||||
declare -n ref=target
|
||||
readonly ref
|
||||
declare -p ref
|
||||
declare -p target
|
||||
# Changing what ref points to should fail
|
||||
declare -n ref=other 2>/dev/null
|
||||
echo "exit: $?"
|
||||
# But target should still be writable
|
||||
target="new_value"
|
||||
echo "target: $target"
|
||||
|
||||
- name: "readonly target through nameref assignment"
|
||||
known_failure: true
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
target="value"
|
||||
readonly target
|
||||
declare -n ref=target
|
||||
ref="new_value" 2>/dev/null
|
||||
echo "exit: $?"
|
||||
echo "target: $target"
|
||||
@@ -0,0 +1,158 @@
|
||||
name: "Builtins: return"
|
||||
cases:
|
||||
- name: "Return outside function or script"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
return 42
|
||||
|
||||
- name: "Return outside function with stderr to /dev/full"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
# /dev/full may not be available on all platforms.
|
||||
[[ -e /dev/full ]] || exit 0
|
||||
return 2>/dev/full
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "Return in directly invoked script"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
return 42
|
||||
echo "Got past return"
|
||||
args: ["./script.sh"]
|
||||
|
||||
- name: "Return from sourced script"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
return 42
|
||||
echo "Got past return"
|
||||
stdin: |
|
||||
source script.sh
|
||||
|
||||
- name: "Return from subshell"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
(return)
|
||||
echo "Got past subshell"
|
||||
exit 42
|
||||
|
||||
- name: "Return from nested sourced script"
|
||||
test_files:
|
||||
- path: "inner.sh"
|
||||
contents: |
|
||||
return 42
|
||||
echo "Got past inner return"
|
||||
- path: "outer.sh"
|
||||
contents: |
|
||||
source inner.sh
|
||||
echo "Got to end of outer script"
|
||||
stdin: |
|
||||
source outer.sh
|
||||
|
||||
- name: "Return in function"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
myfunc() {
|
||||
echo "In myfunc()"
|
||||
return 5
|
||||
echo "Should not get here"
|
||||
}
|
||||
echo "Calling myfunc()..."
|
||||
myfunc
|
||||
echo "Returned: $?"
|
||||
args: ["./script.sh"]
|
||||
|
||||
- name: "Return in for loop in function"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
for i in 1 2 3; do
|
||||
echo "In myfunc: $i"
|
||||
return 5
|
||||
done
|
||||
}
|
||||
|
||||
myfunc
|
||||
echo "Returned: $?"
|
||||
|
||||
- name: "Return in arithmetic for loop in function"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
for ((i=0; i < 5; i++)); do
|
||||
echo "In myfunc: $i"
|
||||
return 5
|
||||
done
|
||||
}
|
||||
|
||||
myfunc
|
||||
echo "Returned: $?"
|
||||
|
||||
- name: "Return in while loop in function"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
i=0
|
||||
while [[ $i -lt 5 ]]; do
|
||||
echo "In myfunc: $i"
|
||||
return 33
|
||||
i=$((i+1))
|
||||
done
|
||||
}
|
||||
|
||||
myfunc
|
||||
echo "Returned: $?"
|
||||
|
||||
- name: "Return in case"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
case 1 in
|
||||
1)
|
||||
echo "In case"
|
||||
return 5
|
||||
echo "Should not get here"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
myfunc
|
||||
echo "Returned: $?"
|
||||
|
||||
- name: "Return in brace group"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
{
|
||||
echo "In brace group"
|
||||
return 5
|
||||
echo "Should not get here"
|
||||
}
|
||||
}
|
||||
|
||||
myfunc
|
||||
echo "Returned: $?"
|
||||
|
||||
- name: "Return in and/or"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
echo "In and/or" && return 5 && echo "Should not get here"
|
||||
}
|
||||
|
||||
myfunc
|
||||
echo "Returned: $?"
|
||||
|
||||
- name: "Return from nested clauses"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
*)
|
||||
shift 2 || {
|
||||
echo "Returning"
|
||||
return 5
|
||||
}
|
||||
echo "Shifted and fell through"
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
myfunc a b c
|
||||
@@ -0,0 +1,103 @@
|
||||
name: "Builtins: set"
|
||||
cases:
|
||||
- name: "set with no args"
|
||||
stdin: |
|
||||
MYVARIABLE=VALUE
|
||||
set > set-output.txt
|
||||
grep MYVARIABLE set-output.txt
|
||||
|
||||
# Remove set-output.txt to avoid it being byte-for-byte compared.
|
||||
rm set-output.txt
|
||||
|
||||
- name: "Basic set usage"
|
||||
stdin: |
|
||||
set a b c d
|
||||
echo ${*}
|
||||
|
||||
- name: "set with options"
|
||||
stdin: |
|
||||
function dumpopts {
|
||||
# Dump the options
|
||||
echo "[Options: $1]"
|
||||
echo "set options: " $-
|
||||
shopt -p -o pipefail
|
||||
}
|
||||
|
||||
set -e -u -o pipefail
|
||||
dumpopts enabled
|
||||
echo '*: ' $*
|
||||
|
||||
set +e +u +o pipefail
|
||||
dumpopts disabled
|
||||
echo '*: ' $*
|
||||
|
||||
- name: "set with multiple combined options"
|
||||
stdin: |
|
||||
function dumpopts {
|
||||
# Dump the options
|
||||
echo "[Options: $1]"
|
||||
echo "set options: " $-
|
||||
shopt -p -o pipefail
|
||||
}
|
||||
|
||||
set -euo pipefail
|
||||
dumpopts enabled
|
||||
echo '$*: ' $*
|
||||
|
||||
set +euo pipefail
|
||||
dumpopts disabled
|
||||
echo '$*: ' $*
|
||||
|
||||
- name: "set clearing args"
|
||||
stdin: |
|
||||
set a b c
|
||||
echo ${*}
|
||||
set a
|
||||
echo ${*}
|
||||
|
||||
- name: "set with -"
|
||||
stdin: |
|
||||
set - a b c
|
||||
echo "args: " ${*}
|
||||
set -
|
||||
echo "args: " ${*}
|
||||
|
||||
- name: "set with --"
|
||||
stdin: |
|
||||
set -- a b c
|
||||
echo "args: " ${*}
|
||||
set --
|
||||
echo "args: " ${*}
|
||||
|
||||
- name: "set with option-looking args"
|
||||
stdin: |
|
||||
set -- a -v
|
||||
echo ${*}
|
||||
|
||||
set - a -v
|
||||
echo ${*}
|
||||
|
||||
set a -v
|
||||
echo ${*}
|
||||
|
||||
set -- a +x
|
||||
echo ${*}
|
||||
|
||||
set - a +x
|
||||
echo ${*}
|
||||
|
||||
set a +x
|
||||
echo ${*}
|
||||
|
||||
- name: "function-local set"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
function f() {
|
||||
local -
|
||||
set -x
|
||||
echo "In func"
|
||||
}
|
||||
|
||||
echo "Before func"
|
||||
f
|
||||
echo "After func"
|
||||
@@ -0,0 +1,77 @@
|
||||
name: "Builtins: shopt"
|
||||
cases:
|
||||
- name: "shopt defaults"
|
||||
ignore_whitespace: true # Spacing changes across versions
|
||||
min_oracle_version: 5.3 # Options changed in bash 5.3
|
||||
incompatible_os: ["fedora"] # Fedora enables "syslog-history" option
|
||||
stdin: |
|
||||
shopt | sort | grep -v extglob
|
||||
|
||||
- name: "shopt interactive defaults"
|
||||
ignore_whitespace: true # Spacing changes across versions
|
||||
min_oracle_version: 5.3 # Options changed in bash 5.3
|
||||
incompatible_os: ["fedora"] # Fedora enables "syslog-history" option
|
||||
pty: true
|
||||
args: ["-i", "-c", "shopt | sort | grep -v extglob"]
|
||||
|
||||
- name: "shopt -o defaults"
|
||||
ignore_whitespace: true # Spacing changes across versions
|
||||
min_oracle_version: 5.3 # Options changed in bash 5.3
|
||||
stdin: |
|
||||
shopt -o | sort
|
||||
|
||||
- name: "shopt -o interactive defaults"
|
||||
ignore_whitespace: true # Spacing changes across versions
|
||||
min_oracle_version: 5.3 # Options changed in bash 5.3
|
||||
pty: true
|
||||
args: ["-i", "-c", "shopt -o | sort | grep -v monitor"]
|
||||
|
||||
- name: "extglob defaults"
|
||||
known_failure: true # TODO(patterns): we force this setting on in our shell
|
||||
stdin: |
|
||||
shopt extglob
|
||||
|
||||
- name: "extglob interactive defaults"
|
||||
pty: true
|
||||
args: ["-i", "-c", "shopt extglob"]
|
||||
known_failure: true
|
||||
|
||||
- name: "shopt -o interactive monitor default"
|
||||
ignore_whitespace: true # Spacing changes across versions
|
||||
min_oracle_version: 5.3 # Options changed in bash 5.3
|
||||
pty: true
|
||||
args: ["-i", "-c", "shopt -o monitor"]
|
||||
|
||||
- name: "shopt toggle"
|
||||
ignore_whitespace: true # Spacing changes across versions
|
||||
stdin: |
|
||||
echo "Setting checkwinsize"
|
||||
shopt -s checkwinsize
|
||||
|
||||
echo "Displaying checkwinsize"
|
||||
shopt checkwinsize
|
||||
shopt -p checkwinsize
|
||||
|
||||
echo "Unsetting checkwinsize"
|
||||
shopt -u checkwinsize
|
||||
|
||||
echo "Displaying checkwinsize"
|
||||
shopt checkwinsize
|
||||
shopt -p checkwinsize
|
||||
|
||||
- name: "shopt -o usage"
|
||||
ignore_whitespace: true # Spacing changes across versions
|
||||
stdin: |
|
||||
echo "Setting emacs"
|
||||
shopt -o -s emacs
|
||||
|
||||
echo "Displaying emacs"
|
||||
shopt -o emacs
|
||||
shopt -o -p emacs
|
||||
|
||||
echo "Unsetting emacs"
|
||||
shopt -o -u emacs
|
||||
|
||||
echo "Displaying emacs"
|
||||
shopt -o emacs
|
||||
shopt -o -p emacs
|
||||
@@ -0,0 +1,357 @@
|
||||
name: "Builtins: test"
|
||||
cases:
|
||||
- name: "test: = operator"
|
||||
stdin: |
|
||||
shopt -u nocasematch
|
||||
test "ab" = "ab" && echo "ab = ab"
|
||||
test "ab" = "AB" && echo "ab = AB"
|
||||
test "ab" = "cd" && echo "ab = cd"
|
||||
test "ab" = "a?" && echo "ab = a?"
|
||||
|
||||
shopt -s nocasematch
|
||||
test "ab" = "ab" && echo "ab = ab"
|
||||
test "ab" = "AB" && echo "ab = AB"
|
||||
test "ab" = "cd" && echo "ab = cd"
|
||||
test "ab" = "a?" && echo "ab = a?"
|
||||
|
||||
- name: "test: == operator"
|
||||
stdin: |
|
||||
shopt -u nocasematch
|
||||
test "ab" == "ab" && echo "ab == ab"
|
||||
test "ab" == "AB" && echo "ab == AB"
|
||||
test "ab" == "cd" && echo "ab == cd"
|
||||
test "ab" == "a?" && echo "ab == a?"
|
||||
|
||||
shopt -s nocasematch
|
||||
test "ab" == "ab" && echo "ab == ab"
|
||||
test "ab" == "AB" && echo "ab == AB"
|
||||
test "ab" == "cd" && echo "ab == cd"
|
||||
test "ab" == "a?" && echo "ab == a?"
|
||||
|
||||
- name: "test: files refer to same device and inode"
|
||||
stdin: |
|
||||
[ /bin/sh -ef /bin/sh ] && echo "-ef correctly identified device and inode numbers"
|
||||
|
||||
[ ! /etc/os-release -ef /bin/sh ] && echo "-ef correctly identified device and inode numbers that do not match"
|
||||
|
||||
- name: "test: file is newer"
|
||||
stdin: |
|
||||
touch -d "2 hours ago" bar
|
||||
touch foo
|
||||
|
||||
[ foo -nt bar ] && echo "-nt correctly identified newer file"
|
||||
[ foo -nt foo ] && echo "-nt incorrectly identified file as newer than itself"
|
||||
[ foo -nt file_no_exists ] && echo "-nt correctly identified when file2 does not exist"
|
||||
|
||||
- name: "test: file is older"
|
||||
stdin: |
|
||||
touch -d "2 hours ago" foo
|
||||
touch bar
|
||||
|
||||
[ foo -ot bar ] && echo "-ot correctly identified older file"
|
||||
[ foo -ot foo ] && echo "-ot incorrectly identified file as older than itself"
|
||||
[ file_no_exists -ot foo ] && echo "-ot correctly identified when file1 does not exist"
|
||||
|
||||
- name: "test: parens"
|
||||
stdin: |
|
||||
test \( "a" = "a" \) && echo "Parens work"
|
||||
|
||||
- name: "test: -- string"
|
||||
stdin: |
|
||||
test --
|
||||
echo "Result(test): $?"
|
||||
|
||||
[ -- ]
|
||||
echo "Result([): $?"
|
||||
|
||||
- name: "test: invalid operators"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
test - -
|
||||
echo "Result: $?"
|
||||
|
||||
# String operators
|
||||
- name: "test: != operator"
|
||||
stdin: |
|
||||
test "ab" != "cd" && echo "ab != cd"
|
||||
test "ab" != "ab" && echo "ab != ab"
|
||||
test "" != "x" && echo "empty != x"
|
||||
|
||||
- name: "test: -z operator (zero length)"
|
||||
stdin: |
|
||||
test -z "" && echo "-z empty"
|
||||
test -z "x" && echo "-z x"
|
||||
unset VAR
|
||||
test -z "$VAR" && echo "-z unset"
|
||||
|
||||
- name: "test: -n operator (non-zero length)"
|
||||
stdin: |
|
||||
test -n "x" && echo "-n x"
|
||||
test -n "" && echo "-n empty"
|
||||
VAR="hello"
|
||||
test -n "$VAR" && echo "-n set"
|
||||
|
||||
- name: "test: < operator (string sort before)"
|
||||
stdin: |
|
||||
test "a" \< "b" && echo "a < b"
|
||||
test "b" \< "a" && echo "b < a"
|
||||
test "abc" \< "abd" && echo "abc < abd"
|
||||
test "a" \< "a" && echo "a < a"
|
||||
|
||||
- name: "test: > operator (string sort after)"
|
||||
stdin: |
|
||||
test "b" \> "a" && echo "b > a"
|
||||
test "a" \> "b" && echo "a > b"
|
||||
test "abd" \> "abc" && echo "abd > abc"
|
||||
test "a" \> "a" && echo "a > a"
|
||||
|
||||
# Arithmetic comparison operators
|
||||
- name: "test: -eq operator"
|
||||
stdin: |
|
||||
test 5 -eq 5 && echo "5 -eq 5"
|
||||
test 5 -eq 6 && echo "5 -eq 6"
|
||||
test -1 -eq -1 && echo "-1 -eq -1"
|
||||
test 0 -eq 0 && echo "0 -eq 0"
|
||||
|
||||
- name: "test: -ne operator"
|
||||
stdin: |
|
||||
test 5 -ne 6 && echo "5 -ne 6"
|
||||
test 5 -ne 5 && echo "5 -ne 5"
|
||||
test -1 -ne 1 && echo "-1 -ne 1"
|
||||
|
||||
- name: "test: -lt operator"
|
||||
stdin: |
|
||||
test 3 -lt 5 && echo "3 -lt 5"
|
||||
test 5 -lt 3 && echo "5 -lt 3"
|
||||
test 5 -lt 5 && echo "5 -lt 5"
|
||||
test -2 -lt -1 && echo "-2 -lt -1"
|
||||
|
||||
- name: "test: -le operator"
|
||||
stdin: |
|
||||
test 3 -le 5 && echo "3 -le 5"
|
||||
test 5 -le 5 && echo "5 -le 5"
|
||||
test 6 -le 5 && echo "6 -le 5"
|
||||
test -2 -le -2 && echo "-2 -le -2"
|
||||
|
||||
- name: "test: -gt operator"
|
||||
stdin: |
|
||||
test 5 -gt 3 && echo "5 -gt 3"
|
||||
test 3 -gt 5 && echo "3 -gt 5"
|
||||
test 5 -gt 5 && echo "5 -gt 5"
|
||||
test -1 -gt -2 && echo "-1 -gt -2"
|
||||
|
||||
- name: "test: -ge operator"
|
||||
stdin: |
|
||||
test 5 -ge 3 && echo "5 -ge 3"
|
||||
test 5 -ge 5 && echo "5 -ge 5"
|
||||
test 3 -ge 5 && echo "3 -ge 5"
|
||||
test -1 -ge -1 && echo "-1 -ge -1"
|
||||
|
||||
# File existence and type operators
|
||||
- name: "test: -e operator (file exists)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -e testfile && echo "-e existing file"
|
||||
test -e nonexistent && echo "-e nonexistent"
|
||||
mkdir testdir
|
||||
test -e testdir && echo "-e existing dir"
|
||||
|
||||
- name: "test: -a operator (file exists, deprecated)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -a testfile && echo "-a existing file"
|
||||
test -a nonexistent && echo "-a nonexistent"
|
||||
|
||||
- name: "test: -f operator (regular file)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -f testfile && echo "-f regular file"
|
||||
mkdir testdir
|
||||
test -f testdir && echo "-f directory"
|
||||
test -f nonexistent && echo "-f nonexistent"
|
||||
|
||||
- name: "test: -d operator (directory)"
|
||||
stdin: |
|
||||
mkdir testdir
|
||||
test -d testdir && echo "-d directory"
|
||||
touch testfile
|
||||
test -d testfile && echo "-d regular file"
|
||||
test -d nonexistent && echo "-d nonexistent"
|
||||
|
||||
- name: "test: -h and -L operators (symbolic link)"
|
||||
stdin: |
|
||||
touch target
|
||||
ln -s target symlink
|
||||
test -h symlink && echo "-h symlink"
|
||||
test -L symlink && echo "-L symlink"
|
||||
test -h target && echo "-h regular file"
|
||||
test -L target && echo "-L regular file"
|
||||
test -h nonexistent && echo "-h nonexistent"
|
||||
|
||||
- name: "test: -p operator (named pipe/FIFO)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -p testfile && echo "-p regular file"
|
||||
test -p nonexistent && echo "-p nonexistent"
|
||||
|
||||
- name: "test: -S operator (socket)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -S testfile && echo "-S regular file"
|
||||
test -S nonexistent && echo "-S nonexistent"
|
||||
|
||||
- name: "test: -b operator (block device)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -b testfile && echo "-b regular file"
|
||||
test -b nonexistent && echo "-b nonexistent"
|
||||
# Block devices exist in /dev but may vary by system
|
||||
|
||||
- name: "test: -c operator (character device)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -c testfile && echo "-c regular file"
|
||||
test -c nonexistent && echo "-c nonexistent"
|
||||
|
||||
# File permission operators
|
||||
- name: "test: -r operator (readable)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -r testfile && echo "-r readable"
|
||||
test -r nonexistent && echo "-r nonexistent"
|
||||
|
||||
- name: "test: -w operator (writable)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -w testfile && echo "-w writable"
|
||||
test -w nonexistent && echo "-w nonexistent"
|
||||
|
||||
- name: "test: -x operator (executable)"
|
||||
stdin: |
|
||||
touch testfile
|
||||
test -x testfile && echo "-x executable"
|
||||
|
||||
- name: "test: -s operator (size greater than zero)"
|
||||
stdin: |
|
||||
echo "content" > nonempty
|
||||
test -s nonempty && echo "-s nonempty"
|
||||
touch empty
|
||||
test -s empty && echo "-s empty"
|
||||
test -s nonexistent && echo "-s nonexistent"
|
||||
|
||||
# Variable and option operators
|
||||
- name: "test: -v operator (variable is set)"
|
||||
stdin: |
|
||||
VAR="hello"
|
||||
test -v VAR && echo "-v set variable"
|
||||
unset VAR
|
||||
test -v VAR && echo "-v unset variable"
|
||||
VAR=""
|
||||
test -v VAR && echo "-v empty variable"
|
||||
|
||||
- name: "test: -R operator (nameref variable)"
|
||||
stdin: |
|
||||
VAR="value"
|
||||
declare -n REF=VAR
|
||||
test -R REF && echo "-R nameref"
|
||||
test -R VAR && echo "-R regular var"
|
||||
unset REF VAR
|
||||
|
||||
- name: "test: -o operator (shell option)"
|
||||
stdin: |
|
||||
set -e
|
||||
test -o errexit && echo "-o errexit enabled"
|
||||
set +e
|
||||
test -o errexit && echo "-o errexit disabled"
|
||||
test -o nonexistentoption && echo "-o nonexistent"
|
||||
|
||||
# Logical operators
|
||||
- name: "test: ! operator (negation)"
|
||||
stdin: |
|
||||
test ! "a" = "b" && echo "! a=b"
|
||||
test ! "a" = "a" && echo "! a=a"
|
||||
test ! -e nonexistent && echo "! -e nonexistent"
|
||||
test ! -e / && echo "! -e /"
|
||||
|
||||
- name: "test: -a operator (logical AND)"
|
||||
stdin: |
|
||||
test "a" = "a" -a "b" = "b" && echo "true -a true"
|
||||
test "a" = "a" -a "b" = "c" && echo "true -a false"
|
||||
test "a" = "b" -a "c" = "c" && echo "false -a true"
|
||||
test "a" = "b" -a "c" = "d" && echo "false -a false"
|
||||
|
||||
- name: "test: -o operator (logical OR)"
|
||||
stdin: |
|
||||
test "a" = "a" -o "b" = "b" && echo "true -o true"
|
||||
test "a" = "a" -o "b" = "c" && echo "true -o false"
|
||||
test "a" = "b" -o "c" = "c" && echo "false -o true"
|
||||
test "a" = "b" -o "c" = "d" && echo "false -o false"
|
||||
|
||||
- name: "test: complex logical expressions"
|
||||
stdin: |
|
||||
test \( "a" = "a" -o "b" = "c" \) -a "d" = "d" && echo "complex 1"
|
||||
test ! \( "a" = "b" \) && echo "complex 2"
|
||||
test "a" = "a" -a \( "b" = "b" -o "c" = "d" \) && echo "complex 3"
|
||||
|
||||
# Edge cases
|
||||
- name: "test: single argument (non-empty string is true)"
|
||||
stdin: |
|
||||
test "hello" && echo "non-empty is true"
|
||||
test "" && echo "empty is true"
|
||||
test "0" && echo "zero string is true"
|
||||
|
||||
- name: "test: no arguments"
|
||||
stdin: |
|
||||
test
|
||||
echo "no args: $?"
|
||||
|
||||
- name: "test: special characters in strings"
|
||||
stdin: |
|
||||
test "hello world" = "hello world" && echo "spaces match"
|
||||
test "a b" = "a b" && echo "tabs match"
|
||||
test 'a"b' = 'a"b' && echo "quotes match"
|
||||
test "a'b" = "a'b" && echo "single quotes match"
|
||||
|
||||
- name: "test: arithmetic comparison with spaces/tabs in operand"
|
||||
stdin: |
|
||||
# Leading/trailing spaces
|
||||
test 0 -eq ' 0'
|
||||
echo "leading space: $?"
|
||||
|
||||
test 0 -eq '0 '
|
||||
echo "trailing space: $?"
|
||||
|
||||
test 0 -eq ' 0 '
|
||||
echo "both spaces: $?"
|
||||
|
||||
# Tabs
|
||||
test 0 -eq ' 0'
|
||||
echo "leading tab: $?"
|
||||
|
||||
test 0 -eq '0 '
|
||||
echo "trailing tab: $?"
|
||||
|
||||
- name: "test: arithmetic comparison with newline in operand"
|
||||
min_oracle_version: "5.3"
|
||||
stdin: |
|
||||
# Newline trimming seems to have changed in or around bash 5.3
|
||||
test 0 -eq ' 0
|
||||
'
|
||||
echo "newline trailing: $?"
|
||||
|
||||
- name: "test: -t operator with whitespace in fd"
|
||||
stdin: |
|
||||
# -t operator should trim whitespace before parsing fd number
|
||||
# These should all behave identically (false since not a terminal)
|
||||
test -t 0
|
||||
r1=$?
|
||||
test -t ' 0'
|
||||
r2=$?
|
||||
test -t '0 '
|
||||
r3=$?
|
||||
test -t ' 0 '
|
||||
r4=$?
|
||||
|
||||
# All should have the same result
|
||||
[ $r1 -eq $r2 ] && [ $r2 -eq $r3 ] && [ $r3 -eq $r4 ] && echo "All -t results match"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
name: "Builtins: times"
|
||||
cases:
|
||||
- name: "Basic usage"
|
||||
ignore_stdout: true
|
||||
stdin: |
|
||||
times
|
||||
@@ -0,0 +1,724 @@
|
||||
name: "Builtins: trap"
|
||||
cases:
|
||||
#
|
||||
# trap -l signal listing
|
||||
#
|
||||
- name: "trap -l - lists all signal names"
|
||||
stdin: |
|
||||
# Just verify it outputs something reasonable - check common signals exist
|
||||
trap -l | grep -qE 'HUP|SIGHUP' && echo "has HUP"
|
||||
trap -l | grep -qE 'INT|SIGINT' && echo "has INT"
|
||||
trap -l | grep -qE 'TERM|SIGTERM' && echo "has TERM"
|
||||
|
||||
#
|
||||
# Basic trap registration
|
||||
#
|
||||
- name: "trap registration"
|
||||
stdin: |
|
||||
trap "echo 1" SIGINT
|
||||
trap "echo 2" SIGINT
|
||||
trap -p INT
|
||||
|
||||
trap "echo 3" int
|
||||
trap -p INT
|
||||
|
||||
trap "echo 4" 2
|
||||
trap -p INT
|
||||
|
||||
- name: "trap unregistering"
|
||||
stdin: |
|
||||
echo "[Case 1]"
|
||||
trap "echo 1" SIGINT
|
||||
trap SIGINT
|
||||
trap -p INT
|
||||
|
||||
echo "[Case 2]"
|
||||
trap "echo 2" SIGINT
|
||||
trap - SIGINT
|
||||
trap -p INT
|
||||
|
||||
- name: "trap EXIT"
|
||||
stdin: |
|
||||
trap 'echo "[exit]"' EXIT
|
||||
trap -p EXIT
|
||||
|
||||
- name: "trap EXIT: status code handling"
|
||||
stdin: |
|
||||
trap 'echo "[exit]: \$?: $?"' EXIT
|
||||
trap -p EXIT
|
||||
false
|
||||
|
||||
- name: "trap DEBUG"
|
||||
stdin: |
|
||||
trap 'echo [command: ${BASH_COMMAND}]' DEBUG
|
||||
trap -p DEBUG
|
||||
|
||||
echo First
|
||||
echo Second
|
||||
false
|
||||
|
||||
- name: "trap ERR - registration only"
|
||||
stdin: |
|
||||
trap "echo [err]" ERR
|
||||
trap -p ERR
|
||||
|
||||
- name: "trap ERR - basic invocation"
|
||||
stdin: |
|
||||
trap 'echo "ERR: $?"' ERR
|
||||
false
|
||||
echo "continues"
|
||||
false
|
||||
echo "continues again"
|
||||
|
||||
- name: "trap ERR - not triggered in conditional"
|
||||
stdin: |
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
if false; then
|
||||
echo "then"
|
||||
else
|
||||
echo "else"
|
||||
fi
|
||||
echo "after"
|
||||
|
||||
- name: "trap ERR - not triggered with && short-circuit"
|
||||
stdin: |
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
false && echo "not printed"
|
||||
echo "after"
|
||||
|
||||
- name: "trap ERR - not triggered with ||"
|
||||
stdin: |
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
false || echo "fallback"
|
||||
echo "after"
|
||||
|
||||
- name: "trap ERR - not triggered with negation"
|
||||
stdin: |
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
! false
|
||||
echo "after"
|
||||
|
||||
- name: "trap ERR - not triggered in while condition"
|
||||
stdin: |
|
||||
trap 'echo "ERR"' ERR
|
||||
while false; do
|
||||
echo "loop body"
|
||||
done
|
||||
echo "after while"
|
||||
|
||||
- name: "trap ERR - not triggered in until condition"
|
||||
stdin: |
|
||||
trap 'echo "ERR"' ERR
|
||||
count=0
|
||||
until false; do
|
||||
((count++))
|
||||
if ((count > 2)); then break; fi
|
||||
done
|
||||
echo "after until"
|
||||
|
||||
- name: "trap ERR - not triggered as part of || list"
|
||||
stdin: |
|
||||
trap 'echo "ERR"' ERR
|
||||
false || true || false
|
||||
echo "after"
|
||||
|
||||
- name: "trap ERR - not triggered for negated command"
|
||||
stdin: |
|
||||
trap 'echo "ERR"' ERR
|
||||
if ! true; then
|
||||
echo "then"
|
||||
fi
|
||||
! false
|
||||
echo "after"
|
||||
|
||||
- name: "trap ERR - triggered after && chain completes then fails"
|
||||
stdin: |
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
true && true
|
||||
false
|
||||
echo "after"
|
||||
|
||||
- name: "trap ERR - execution doesn't affect control flow"
|
||||
stdin: |
|
||||
trap 'echo "ERR"' ERR
|
||||
result=0
|
||||
false || result=$?
|
||||
echo "result: $result"
|
||||
|
||||
- name: "trap ERR - multiple failures trigger multiple traps"
|
||||
stdin: |
|
||||
count=0
|
||||
trap '((count++)); echo "ERR $count"' ERR
|
||||
false
|
||||
false
|
||||
false
|
||||
echo "total: $count"
|
||||
|
||||
- name: "trap ERR - has access to exit status"
|
||||
stdin: |
|
||||
trap 'echo "ERR with status: $?"' ERR
|
||||
(exit 42)
|
||||
echo "after"
|
||||
|
||||
- name: "trap ERR - has access to BASH_COMMAND"
|
||||
known_failure: true # TODO(traps): ERR trap basic execution not implemented
|
||||
stdin: |
|
||||
trap 'echo "ERR for: $BASH_COMMAND"' ERR
|
||||
nonexistent_command_xyz 2>/dev/null
|
||||
echo "after"
|
||||
|
||||
- name: "trap ERR - has access to LINENO"
|
||||
known_failure: true # TODO(traps): ERR trap basic execution not implemented
|
||||
stdin: |
|
||||
trap 'echo "ERR at line: $LINENO"' ERR
|
||||
false
|
||||
echo "after"
|
||||
|
||||
- name: "return in EXIT trap within function"
|
||||
known_failure: true # TODO(traps): return in trap handler behavior differs
|
||||
stdin: |
|
||||
myfunc() {
|
||||
trap 'return 0' EXIT
|
||||
false
|
||||
echo "should not print in func"
|
||||
}
|
||||
myfunc
|
||||
echo "after func"
|
||||
|
||||
- name: "subshell inherits signal traps (for trap -p display)"
|
||||
stdin: |
|
||||
trap 'echo "[parent int]"' INT
|
||||
trap 'echo "[parent term]"' TERM
|
||||
trap -p INT
|
||||
echo "---"
|
||||
(trap -p INT; trap -p TERM)
|
||||
|
||||
- name: "subshell can override inherited trap"
|
||||
stdin: |
|
||||
trap 'echo "[parent int]"' INT
|
||||
trap -p INT
|
||||
echo "---"
|
||||
(trap 'echo "[subshell int]"' INT; trap -p INT)
|
||||
|
||||
- name: "subshell inherits EXIT trap"
|
||||
stdin: |
|
||||
trap 'echo "[exit]"' EXIT
|
||||
trap -p EXIT
|
||||
echo "---"
|
||||
(trap -p EXIT)
|
||||
|
||||
- name: "subshell inherits DEBUG trap"
|
||||
stdin: |
|
||||
trap 'echo "[debug]"' DEBUG
|
||||
trap -p DEBUG
|
||||
echo "---"
|
||||
(trap -p DEBUG)
|
||||
|
||||
- name: "subshell does not execute inherited DEBUG trap"
|
||||
stdin: |
|
||||
trap 'echo "[debug]"' DEBUG
|
||||
echo "parent"
|
||||
echo "---"
|
||||
(echo "subshell")
|
||||
echo "back in parent"
|
||||
|
||||
- name: "subshell can set its own EXIT trap"
|
||||
known_failure: true # TODO(traps): EXIT trap in subshell
|
||||
stdin: |
|
||||
trap 'echo "[parent exit]"' EXIT
|
||||
echo "parent"
|
||||
(trap 'echo "[subshell exit]"' EXIT; echo "subshell")
|
||||
echo "back in parent"
|
||||
|
||||
- name: "subshell inherited EXIT trap does not fire"
|
||||
stdin: |
|
||||
trap 'echo "[exit]"' EXIT
|
||||
echo "parent"
|
||||
(echo "subshell")
|
||||
echo "back in parent"
|
||||
|
||||
#
|
||||
# Subshell trap inheritance in command substitution
|
||||
#
|
||||
- name: "command substitution inherits signal trap (trap -p display)"
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
result=$(trap -p INT)
|
||||
echo "$result"
|
||||
|
||||
- name: "command substitution does not fire inherited signal trap"
|
||||
known_failure: true # TODO(signals): signal handling in command substitution
|
||||
stdin: |
|
||||
trap 'echo "[int]" >&2' INT
|
||||
result=$(kill -INT $$; echo "after kill")
|
||||
echo "result: $result"
|
||||
|
||||
- name: "command substitution can set its own trap"
|
||||
stdin: |
|
||||
trap 'echo "[parent int]"' INT
|
||||
result=$(trap 'echo "[subshell int]"' INT; trap -p INT)
|
||||
echo "$result"
|
||||
|
||||
- name: "command substitution EXIT trap not inherited from parent"
|
||||
stdin: |
|
||||
trap 'echo "[parent exit]"' EXIT
|
||||
result=$(trap -p EXIT; echo "inside")
|
||||
echo "result: $result"
|
||||
|
||||
- name: "command substitution can have its own EXIT trap"
|
||||
known_failure: true # TODO: EXIT trap in command substitution subshell
|
||||
stdin: |
|
||||
trap 'echo "[parent exit]"' EXIT
|
||||
result=$(trap 'echo "[sub exit]"' EXIT; echo "inside")
|
||||
echo "result: $result"
|
||||
|
||||
- name: "nested command substitution trap inheritance"
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
outer=$(inner=$(trap -p INT); echo "inner: $inner")
|
||||
echo "outer: $outer"
|
||||
|
||||
#
|
||||
# Subshell trap inheritance in pipelines
|
||||
#
|
||||
- name: "pipeline component inherits signal trap (trap -p display)"
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
trap -p INT | cat
|
||||
|
||||
- name: "pipeline component can override trap"
|
||||
stdin: |
|
||||
trap 'echo "[parent int]"' INT
|
||||
{ trap 'echo "[pipe int]"' INT; trap -p INT; } | cat
|
||||
|
||||
- name: "pipeline EXIT trap behavior"
|
||||
stdin: |
|
||||
trap 'echo "[exit]"' EXIT
|
||||
{ trap -p EXIT; echo "in pipe"; } | cat
|
||||
echo "after pipe"
|
||||
|
||||
- name: "multiple pipeline stages trap inheritance"
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
trap -p INT | cat | cat
|
||||
|
||||
#
|
||||
# Subshell trap inheritance in process substitution
|
||||
#
|
||||
- name: "process substitution inherits signal trap"
|
||||
known_failure: true # TODO: process substitution trap inheritance
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
cat <(trap -p INT)
|
||||
|
||||
- name: "process substitution output trap inheritance"
|
||||
skip: true # TODO: process substitution trap inheritance / inconsistent across distros
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
echo "data" > >(trap -p INT; cat > /dev/null)
|
||||
# Give time for the process substitution to complete
|
||||
wait
|
||||
|
||||
- name: "process substitution can set own EXIT trap"
|
||||
known_failure: true # TODO: EXIT trap in process substitution
|
||||
stdin: |
|
||||
trap 'echo "[parent exit]"' EXIT
|
||||
cat <(trap 'echo "[sub exit]"' EXIT; echo "inside")
|
||||
echo "after"
|
||||
|
||||
#
|
||||
# Subshell trap inheritance in background jobs
|
||||
#
|
||||
- name: "background job inherits signal trap (trap -p display)"
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
trap -p INT &
|
||||
wait
|
||||
|
||||
- name: "background job does not fire inherited signal trap"
|
||||
known_failure: true # TODO(signals): background job signal handling
|
||||
stdin: |
|
||||
trap 'echo "[int]" >&2' INT
|
||||
{ kill -INT $$; echo "after kill"; } &
|
||||
wait
|
||||
echo "main done"
|
||||
|
||||
- name: "background job EXIT trap behavior"
|
||||
stdin: |
|
||||
trap 'echo "[exit]"' EXIT
|
||||
{ trap -p EXIT; echo "bg job"; } &
|
||||
wait
|
||||
echo "main continues"
|
||||
|
||||
- name: "background job can set own traps"
|
||||
stdin: |
|
||||
trap 'echo "[parent int]"' INT
|
||||
{ trap 'echo "[bg int]"' INT; trap -p INT; } &
|
||||
wait
|
||||
trap -p INT
|
||||
|
||||
#
|
||||
# Coproc trap inheritance (all coproc tests consolidated here)
|
||||
#
|
||||
# Note: "coproc - inherits signal trap" test is skipped due to bash version
|
||||
# differences. Older bash resets traps in coproc, newer bash (5.3.9) inherits them.
|
||||
# This causes inconsistent test results across CI runners.
|
||||
- name: "coproc - EXIT trap behavior"
|
||||
skip: true # TODO: this test is too inconsistent
|
||||
min_oracle_version: "5.3.0"
|
||||
incompatible_os:
|
||||
- fedora
|
||||
- arch
|
||||
stdin: |
|
||||
trap 'echo "[exit]"' EXIT
|
||||
coproc { trap -p EXIT; echo "coproc"; }
|
||||
cat <&${COPROC[0]}
|
||||
wait
|
||||
echo "main continues"
|
||||
|
||||
- name: "coproc - DEBUG trap inheritance"
|
||||
skip: true # TODO: this test is too inconsistent
|
||||
min_oracle_version: "5.3.0"
|
||||
incompatible_os:
|
||||
- fedora
|
||||
stdin: |
|
||||
trap 'echo "[debug]"' DEBUG
|
||||
coproc { trap -p DEBUG; echo "coproc"; }
|
||||
cat <&${COPROC[0]}
|
||||
wait
|
||||
|
||||
- name: "coproc - ERR trap with errtrace"
|
||||
skip: true # TODO: this test is too inconsistent
|
||||
min_oracle_version: "5.3.0"
|
||||
incompatible_os:
|
||||
- fedora
|
||||
- arch
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "[err]"' ERR
|
||||
coproc { trap -p ERR; false; echo "after false"; }
|
||||
cat <&${COPROC[0]}
|
||||
wait
|
||||
|
||||
#
|
||||
# RETURN trap - basic behavior
|
||||
#
|
||||
- name: "RETURN trap - fires on function return"
|
||||
stdin: |
|
||||
trap 'echo "[return]"' RETURN
|
||||
myfunc() {
|
||||
echo "in func"
|
||||
}
|
||||
myfunc
|
||||
echo "after func"
|
||||
|
||||
- name: "RETURN trap - fires with return value"
|
||||
stdin: |
|
||||
trap 'echo "[return: $?]"' RETURN
|
||||
myfunc() {
|
||||
echo "in func"
|
||||
return 42
|
||||
}
|
||||
myfunc
|
||||
echo "after func"
|
||||
|
||||
- name: "RETURN trap - fires on sourced script completion"
|
||||
known_failure: true # TODO(traps): RETURN trap not firing for sourced scripts
|
||||
test_files:
|
||||
- path: "sourced.sh"
|
||||
contents: |
|
||||
echo "in sourced"
|
||||
stdin: |
|
||||
trap 'echo "[return]"' RETURN
|
||||
. ./sourced.sh
|
||||
echo "after source"
|
||||
|
||||
- name: "RETURN trap - not inherited in function without functrace"
|
||||
stdin: |
|
||||
set +T
|
||||
trap 'echo "[return]"' RETURN
|
||||
outer() {
|
||||
inner() {
|
||||
echo "in inner"
|
||||
}
|
||||
inner
|
||||
echo "after inner"
|
||||
}
|
||||
outer
|
||||
echo "done"
|
||||
|
||||
- name: "RETURN trap - inherited with functrace"
|
||||
known_failure: true # TODO(traps): RETURN trap inheritance with functrace not implemented
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[return: ${FUNCNAME[0]:-main}]"' RETURN
|
||||
outer() {
|
||||
inner() {
|
||||
echo "in inner"
|
||||
}
|
||||
inner
|
||||
echo "after inner"
|
||||
}
|
||||
outer
|
||||
echo "done"
|
||||
|
||||
- name: "RETURN trap - in explicit subshell"
|
||||
stdin: |
|
||||
trap 'echo "[return]"' RETURN
|
||||
(
|
||||
myfunc() { echo "in func"; }
|
||||
myfunc
|
||||
)
|
||||
echo "after subshell"
|
||||
|
||||
- name: "RETURN trap - does not fire for regular commands"
|
||||
stdin: |
|
||||
trap 'echo "[return]"' RETURN
|
||||
echo "cmd1"
|
||||
echo "cmd2"
|
||||
true
|
||||
echo "done"
|
||||
|
||||
- name: "RETURN trap - does not fire for subshell completion"
|
||||
stdin: |
|
||||
trap 'echo "[return]"' RETURN
|
||||
(echo "in subshell")
|
||||
echo "after subshell"
|
||||
|
||||
#
|
||||
# Signal trap execution verification
|
||||
#
|
||||
- name: "trap SIGUSR1"
|
||||
known_failure: true # TODO(signals): signal handling
|
||||
stdin: |
|
||||
trap 'echo "[usr1 received]"' USR1
|
||||
trap -p USR1
|
||||
kill -USR1 $$
|
||||
echo "after signal"
|
||||
|
||||
- name: "trap SIGUSR2"
|
||||
known_failure: true # TODO(signals): signal handling
|
||||
stdin: |
|
||||
trap 'echo "[usr2 received]"' SIGUSR2
|
||||
trap -p USR2
|
||||
kill -USR2 $$
|
||||
echo "after signal"
|
||||
|
||||
- name: "trap multiple signals with same handler"
|
||||
known_failure: true # TODO(signals): signal handling
|
||||
stdin: |
|
||||
trap 'echo "[signal received]"' USR1 USR2
|
||||
kill -USR1 $$
|
||||
kill -USR2 $$
|
||||
echo "done"
|
||||
|
||||
- name: "trap handler can inspect signal"
|
||||
known_failure: true # TODO(signals): signal handling
|
||||
stdin: |
|
||||
handler() {
|
||||
echo "handler called"
|
||||
}
|
||||
trap handler USR1
|
||||
kill -USR1 $$
|
||||
echo "after"
|
||||
|
||||
#
|
||||
# Trap reset and unset
|
||||
#
|
||||
- name: "trap - clears trap"
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
trap -p INT
|
||||
trap - INT
|
||||
trap -p INT
|
||||
|
||||
- name: "trap '' ignores signal"
|
||||
known_failure: true # TODO(signals): signal handling
|
||||
stdin: |
|
||||
trap '' INT
|
||||
trap -p INT
|
||||
kill -INT $$
|
||||
echo "survived"
|
||||
|
||||
- name: "trap with empty string can be cleared"
|
||||
stdin: |
|
||||
trap '' INT
|
||||
trap -p INT
|
||||
trap - INT
|
||||
trap -p INT
|
||||
|
||||
#
|
||||
# trap -p edge cases
|
||||
#
|
||||
- name: "trap -p - with no args shows all traps"
|
||||
stdin: |
|
||||
trap 'echo a' INT
|
||||
trap 'echo b' TERM
|
||||
trap 'echo c' EXIT
|
||||
trap -p | sort
|
||||
|
||||
- name: "trap -p - with specific signal"
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
trap 'echo "[term]"' TERM
|
||||
trap -p INT
|
||||
trap -p TERM
|
||||
|
||||
- name: "trap -p - with multiple signals"
|
||||
stdin: |
|
||||
trap 'echo "[int]"' INT
|
||||
trap 'echo "[term]"' TERM
|
||||
trap 'echo "[usr1]"' USR1
|
||||
trap -p INT TERM USR1
|
||||
|
||||
- name: "trap -p - for unset trap shows nothing"
|
||||
stdin: |
|
||||
trap -p INT
|
||||
echo "done"
|
||||
|
||||
- name: "trap -p - output is re-executable"
|
||||
stdin: |
|
||||
trap 'echo "hello world"' INT
|
||||
eval "$(trap -p INT)"
|
||||
trap -p INT
|
||||
|
||||
#
|
||||
# Trap handler quoting
|
||||
#
|
||||
- name: "trap handler - single quotes preserved"
|
||||
known_failure: true # TODO(traps): quote escaping in trap -p output differs
|
||||
stdin: |
|
||||
trap 'echo '\''quoted'\''' INT
|
||||
trap -p INT
|
||||
|
||||
- name: "trap handler - double quotes in handler"
|
||||
stdin: |
|
||||
trap 'echo "double quoted"' INT
|
||||
trap -p INT
|
||||
|
||||
- name: "trap handler - special characters"
|
||||
stdin: |
|
||||
trap 'echo $HOME; echo "done"' INT
|
||||
trap -p INT
|
||||
|
||||
- name: "trap handler - newlines in handler"
|
||||
stdin: |
|
||||
trap 'echo line1
|
||||
echo line2' INT
|
||||
trap -p INT
|
||||
|
||||
#
|
||||
# DEBUG trap - basic behavior
|
||||
#
|
||||
- name: "DEBUG trap - sees BASH_COMMAND"
|
||||
known_failure: true # TODO(BASH_COMMAND): not showing assignment, quotes differ
|
||||
stdin: |
|
||||
trap 'echo "cmd: $BASH_COMMAND"' DEBUG
|
||||
x=1
|
||||
echo "x is $x"
|
||||
|
||||
- name: "DEBUG trap - sees LINENO"
|
||||
known_failure: true # TODO(traps): LINENO in DEBUG trap not accurate
|
||||
stdin: |
|
||||
trap 'echo "line: $LINENO"' DEBUG
|
||||
echo first
|
||||
echo second
|
||||
|
||||
- name: "DEBUG trap - fires before each simple command"
|
||||
known_failure: true # TODO(traps): DEBUG trap not firing for trap command itself
|
||||
stdin: |
|
||||
count=0
|
||||
trap '((count++))' DEBUG
|
||||
echo a
|
||||
echo b
|
||||
echo c
|
||||
echo "count: $count"
|
||||
|
||||
- name: "DEBUG trap - in loop"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
for i in 1 2; do
|
||||
echo "iter $i"
|
||||
done
|
||||
|
||||
#
|
||||
# EXIT trap - advanced cases
|
||||
#
|
||||
- name: "EXIT trap - has access to exit status"
|
||||
stdin: |
|
||||
trap 'echo "exiting with: $?"' EXIT
|
||||
exit 42
|
||||
|
||||
- name: "EXIT trap - runs even after errexit"
|
||||
stdin: |
|
||||
set -e
|
||||
trap 'echo "[exit trap ran]"' EXIT
|
||||
false
|
||||
echo "not reached"
|
||||
|
||||
- name: "EXIT trap - can modify exit status"
|
||||
known_failure: true # TODO(traps): EXIT trap exit doesn't modify status
|
||||
stdin: |
|
||||
trap 'exit 0' EXIT
|
||||
exit 99
|
||||
|
||||
- name: "EXIT trap - multiple assignments last one wins"
|
||||
stdin: |
|
||||
trap 'echo "first"' EXIT
|
||||
trap 'echo "second"' EXIT
|
||||
echo "done"
|
||||
|
||||
#
|
||||
# EXIT trap in -c mode
|
||||
#
|
||||
- name: "EXIT trap fires in -c mode"
|
||||
args:
|
||||
- "-c"
|
||||
- "trap 'echo exiting' EXIT; echo hello"
|
||||
expected_stdout: |
|
||||
hello
|
||||
exiting
|
||||
|
||||
- name: "EXIT trap in -c mode has access to exit status"
|
||||
args:
|
||||
- "-c"
|
||||
- "trap 'echo \"status: $?\"' EXIT; exit 42"
|
||||
expected_stdout: "status: 42\n"
|
||||
|
||||
- name: "EXIT trap in -c mode runs after errexit"
|
||||
args:
|
||||
- "-c"
|
||||
- "set -e; trap 'echo trapped' EXIT; false"
|
||||
expected_stdout: "trapped\n"
|
||||
|
||||
- name: "EXIT trap in -c mode with multiple assignments last one wins"
|
||||
args:
|
||||
- "-c"
|
||||
- "trap 'echo first' EXIT; trap 'echo second' EXIT; echo done"
|
||||
expected_stdout: |
|
||||
done
|
||||
second
|
||||
|
||||
#
|
||||
# Trap with multiple signals
|
||||
#
|
||||
- name: "trap - clear multiple signals at once"
|
||||
stdin: |
|
||||
trap 'echo "[handler]"' INT TERM USR1
|
||||
trap -p INT
|
||||
trap -p TERM
|
||||
trap -p USR1
|
||||
echo "---"
|
||||
trap - INT TERM USR1
|
||||
trap -p INT
|
||||
trap -p TERM
|
||||
trap -p USR1
|
||||
echo "done"
|
||||
|
||||
- name: "trap - ignore multiple signals"
|
||||
stdin: |
|
||||
trap '' INT TERM
|
||||
trap -p INT
|
||||
trap -p TERM
|
||||
echo "done"
|
||||
@@ -0,0 +1,7 @@
|
||||
name: "true and false"
|
||||
cases:
|
||||
- name: "true and false ignore all args"
|
||||
stdin: |
|
||||
builtin false --help; echo "builtin false --help => $?"
|
||||
builtin true --help; echo "builtin true --help => $?"
|
||||
builtin true s87tien5; echo "builtin true s87tien5 => $?"
|
||||
@@ -0,0 +1,86 @@
|
||||
name: "Builtins: type"
|
||||
cases:
|
||||
- name: "Test type with no arguments"
|
||||
stdin: type
|
||||
|
||||
- name: "Test type with a valid command"
|
||||
stdin: type ls
|
||||
|
||||
- name: "Test type with an invalid command"
|
||||
ignore_stderr: true
|
||||
stdin: type invalid_command
|
||||
|
||||
- name: "Test type with -t option and a builtin command"
|
||||
stdin: type -t cd
|
||||
|
||||
- name: "Test type with -t option and an external command"
|
||||
stdin: type -t ls
|
||||
|
||||
- name: "Test type with -t option and non-existent command"
|
||||
ignore_stderr: true
|
||||
stdin: type -t non-existent
|
||||
|
||||
- name: "Test type with -a option and non-existent command"
|
||||
ignore_stderr: true
|
||||
stdin: type -a non-existent
|
||||
|
||||
- name: "Test type with -a option and a command with multiple definitions"
|
||||
stdin: type -a true
|
||||
|
||||
- name: Test type with -p option and a builtin command
|
||||
stdin: type -p cd
|
||||
|
||||
- name: Test type with -p option and an external command
|
||||
stdin: type -p ls
|
||||
|
||||
- name: Test type with -p option and non-existent command
|
||||
stdin: type -p non-existent
|
||||
|
||||
- name: Test type with -P option and a builtin command
|
||||
stdin: type -P cd
|
||||
|
||||
- name: Test type with -P option and an external command
|
||||
stdin: type -P ls
|
||||
|
||||
- name: Test type with -P option and non-existent command
|
||||
stdin: type -P non-existent
|
||||
|
||||
- name: Test type with -f option and a function
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
function myfunc() { echo "Hello, world!"; }
|
||||
type -f myfunc
|
||||
|
||||
- name: Test type with -f option and a command
|
||||
stdin: type -f ls
|
||||
|
||||
- name: Test type with -a option and a function
|
||||
stdin: |
|
||||
function myfunc() { echo "Hello, world!"; }
|
||||
type -a myfunc
|
||||
|
||||
- name: Test type with hashed path
|
||||
stdin: |
|
||||
hash -p /some/ls ls
|
||||
type ls
|
||||
|
||||
- name: Test type -a with hashed path
|
||||
stdin: |
|
||||
hash -p /some/ls ls
|
||||
type -a ls
|
||||
|
||||
- name: Test type -P with hashed path
|
||||
stdin: |
|
||||
hash -p /some/ls ls
|
||||
type -P ls
|
||||
|
||||
- name: Test type -P -a with hashed path
|
||||
min_oracle_version: 5.3 # Behavior changed in bash 5.3
|
||||
stdin: |
|
||||
hash -p /some/ls ls
|
||||
type -P -a ls
|
||||
|
||||
- name: Test type -p -a with hashed path
|
||||
stdin: |
|
||||
hash -p /some/ls ls
|
||||
type -p -a ls
|
||||
@@ -0,0 +1,10 @@
|
||||
name: "Builtins: typeset"
|
||||
|
||||
cases:
|
||||
- name: "Display vars"
|
||||
stdin: |
|
||||
typeset myvar=something
|
||||
typeset -p myvar
|
||||
|
||||
typeset myarr=(a b c)
|
||||
typeset -p myarr
|
||||
@@ -0,0 +1,37 @@
|
||||
name: "Builtins: ulimit"
|
||||
cases:
|
||||
- name: "ulimit -c"
|
||||
stdin: |
|
||||
ulimit -c
|
||||
|
||||
- name: "ulimit -d"
|
||||
stdin: |
|
||||
ulimit -d
|
||||
|
||||
- name: "ulimit -f"
|
||||
stdin: |
|
||||
ulimit -f
|
||||
|
||||
- name: "ulimit -l"
|
||||
stdin: |
|
||||
ulimit -l
|
||||
|
||||
- name: "ulimit -m"
|
||||
stdin: |
|
||||
ulimit -m
|
||||
|
||||
- name: "ulimit -n"
|
||||
stdin: |
|
||||
ulimit -n
|
||||
|
||||
- name: "ulimit -d unlimited"
|
||||
stdin: |
|
||||
ulimit -d unlimited
|
||||
|
||||
- name: "ulimit -f unlimited"
|
||||
stdin: |
|
||||
ulimit -f unlimited
|
||||
|
||||
- name: "ulimit -m unlimited"
|
||||
stdin: |
|
||||
ulimit -m unlimited
|
||||
@@ -0,0 +1,15 @@
|
||||
name: "Builtins: unalias"
|
||||
cases:
|
||||
- name: "Unalias basic usage"
|
||||
stdin: |
|
||||
shopt -s expand_aliases
|
||||
alias echo='echo prefixed'
|
||||
echo 'something'
|
||||
unalias echo
|
||||
echo 'something'
|
||||
|
||||
- name: "Unalias non-existent alias"
|
||||
ignore_stderr: true # Slightly different error messages
|
||||
stdin: |
|
||||
shopt -s expand_aliases
|
||||
unalias not_an_alias
|
||||
@@ -0,0 +1,261 @@
|
||||
name: "Builtins: unset"
|
||||
cases:
|
||||
- name: "Basic unset usage"
|
||||
stdin: |
|
||||
var=value
|
||||
unset var
|
||||
echo "var: ${var}"
|
||||
|
||||
- name: "Unset multiple variables"
|
||||
stdin: |
|
||||
var1=value1
|
||||
var2=value2
|
||||
unset var1 var2
|
||||
echo "var1: ${var1}, var2: ${var2}"
|
||||
|
||||
- name: "Unset function"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
function myfunc() {
|
||||
echo "Hello, world!"
|
||||
}
|
||||
unset myfunc
|
||||
myfunc
|
||||
|
||||
- name: "Unset -f function"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
function myfunc() {
|
||||
echo "Hello, world!"
|
||||
}
|
||||
unset -f myfunc
|
||||
myfunc
|
||||
|
||||
- name: "Unset indexed array element"
|
||||
stdin: |
|
||||
declare -a myarray=(a b c)
|
||||
|
||||
unset myarray[0]
|
||||
declare -p myarray
|
||||
|
||||
unset myarray[3]
|
||||
declare -p myarray
|
||||
|
||||
unset myarray[2]
|
||||
declare -p myarray
|
||||
|
||||
unset myarray[1]
|
||||
declare -p myarray
|
||||
|
||||
- name: "Unset associative array element"
|
||||
stdin: |
|
||||
echo "[1]"
|
||||
|
||||
declare -A myarray=([key]=value)
|
||||
|
||||
unset myarray[key]
|
||||
declare -p myarray
|
||||
|
||||
echo "[2]"
|
||||
|
||||
declare -A myarray=([key]=value)
|
||||
|
||||
unset "myarray[key]"
|
||||
declare -p myarray
|
||||
|
||||
echo "[3]"
|
||||
|
||||
declare -A myarray=([key]=value)
|
||||
key="x:y:z"
|
||||
|
||||
unset "myarray[key]"
|
||||
declare -p myarray
|
||||
|
||||
- name: "Unset array element with interesting expression"
|
||||
stdin: |
|
||||
declare -a myarray=(a b c d e)
|
||||
|
||||
echo "Initial array:"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
i=4
|
||||
unset myarray[i]
|
||||
echo "After removing element at index 4:"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
unset myarray[6/2]
|
||||
echo "After removing element at index 3:"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
- name: "Unset array element with negative index"
|
||||
stdin: |
|
||||
declare -a myarray=(a b c d e)
|
||||
|
||||
echo "Initial array:"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
unset "myarray[-1]"
|
||||
echo "After removing last element (index -1):"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
unset "myarray[-1]"
|
||||
echo "After removing new last element (index -1 again):"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
unset "myarray[-2]"
|
||||
echo "After removing second-to-last element (index -2):"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
- name: "Unset array element with negative index out of bounds"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
declare -a myarray=(a b c)
|
||||
|
||||
echo "Initial array:"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
unset "myarray[-4]"
|
||||
echo "After attempting to remove with index -4 (out of bounds):"
|
||||
declare -p myarray
|
||||
echo "Return code: $?"
|
||||
|
||||
- name: "Unset array element with variable negative index"
|
||||
stdin: |
|
||||
declare -a myarray=(10 20 30 40 50)
|
||||
|
||||
echo "Initial array:"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
i=-1
|
||||
unset "myarray[i]"
|
||||
echo "After removing with variable index i=-1:"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
i=-2
|
||||
unset "myarray[i]"
|
||||
echo "After removing with variable index i=-2:"
|
||||
declare -p myarray
|
||||
echo
|
||||
|
||||
- name: "Unset local in same function"
|
||||
stdin: |
|
||||
var="global"
|
||||
|
||||
myfunc() {
|
||||
echo "In myfunc"
|
||||
local -i var="10"
|
||||
declare -p var
|
||||
unset var
|
||||
echo "After unset"
|
||||
declare -p var
|
||||
var="20"
|
||||
}
|
||||
|
||||
echo "Before call: var=${var}"
|
||||
myfunc
|
||||
echo "After call: var=${var}"
|
||||
|
||||
- name: "Unset locals in callers"
|
||||
stdin: |
|
||||
firstfunc() {
|
||||
local var="first"
|
||||
echo "entered firstfunc: var=${var}"
|
||||
secondfunc
|
||||
echo "leaving firstfunc: var=${var}"
|
||||
}
|
||||
|
||||
secondfunc() {
|
||||
local var="second"
|
||||
echo "entered secondfunc: var=${var}"
|
||||
thirdfunc
|
||||
echo "leaving secondfunc: var=${var}"
|
||||
}
|
||||
|
||||
thirdfunc() {
|
||||
echo "entered thirdfunc: var=${var}"
|
||||
unset -v var
|
||||
echo "after first unset: var=${var}"
|
||||
var+=":updated"
|
||||
echo " ...and updated: var=${var}"
|
||||
unset -v var
|
||||
echo "after second unset: var=${var}"
|
||||
var+=":updated"
|
||||
echo " ...and updated: var=${var}"
|
||||
unset -v var
|
||||
echo "after third unset: var=${var}"
|
||||
var+=":updated"
|
||||
echo " ...and updated: var=${var}"
|
||||
echo "leaving firstfunc: var=${var}"
|
||||
}
|
||||
|
||||
var="global"
|
||||
echo "before calls: var=${var}"
|
||||
firstfunc
|
||||
echo "after calls: var=${var}"
|
||||
|
||||
- name: "Unset with nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
declare -n ref=var
|
||||
var="value"
|
||||
|
||||
echo "[Before unset]"
|
||||
echo "ref: ${ref}"
|
||||
echo "var: ${var}"
|
||||
|
||||
unset ref
|
||||
|
||||
echo "[After unset]"
|
||||
echo "ref: ${ref}"
|
||||
echo "var: ${var}"
|
||||
|
||||
- name: "Unset -n removes nameref itself"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
target="value"
|
||||
declare -n ref=target
|
||||
echo "before: target=$target ref=$ref"
|
||||
unset -n ref
|
||||
echo "after unset -n: target=$target"
|
||||
declare -p ref 2>/dev/null || echo "ref is gone"
|
||||
declare -p target 2>/dev/null && echo "target still declared"
|
||||
|
||||
- name: "Unset array element through nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
target=(a b c d e)
|
||||
declare -n ref=target
|
||||
unset 'ref[2]'
|
||||
declare -p target
|
||||
unset 'ref[0]'
|
||||
declare -p target
|
||||
|
||||
- name: "Unset odd function names"
|
||||
stdin: |
|
||||
echo "[Checking existing]"
|
||||
type [
|
||||
|
||||
echo "[Defining function]"
|
||||
function [() {
|
||||
:
|
||||
}
|
||||
|
||||
echo "[Checking defined]"
|
||||
type [
|
||||
|
||||
echo "[Unsetting function]"
|
||||
unset [
|
||||
echo "unset result: $?"
|
||||
|
||||
echo "[Checking after unset]"
|
||||
type [
|
||||
@@ -0,0 +1,52 @@
|
||||
name: "Builtins: wait"
|
||||
cases:
|
||||
- name: "wait for specific failed PID"
|
||||
known_failure: true # TODO(jobs): wait should return job's exit status
|
||||
stdin: |
|
||||
(exit 5) &
|
||||
pid=$!
|
||||
wait $pid
|
||||
echo "status: $?"
|
||||
|
||||
- name: "wait for successful PID"
|
||||
stdin: |
|
||||
(exit 0) &
|
||||
pid=$!
|
||||
wait $pid
|
||||
echo "status: $?"
|
||||
|
||||
- name: "wait in conditional for failed PID"
|
||||
known_failure: true # TODO(jobs): wait should return job's exit status
|
||||
stdin: |
|
||||
(exit 5) &
|
||||
pid=$!
|
||||
if wait $pid; then
|
||||
echo "then"
|
||||
else
|
||||
echo "else"
|
||||
fi
|
||||
|
||||
- name: "wait with no arguments"
|
||||
stdin: |
|
||||
(exit 0) &
|
||||
(exit 0) &
|
||||
wait
|
||||
echo "after wait all"
|
||||
|
||||
- name: "wait -n not implemented"
|
||||
known_failure: true # TODO(builtins): wait -n not implemented
|
||||
stdin: |
|
||||
(exit 0) &
|
||||
(exit 5) &
|
||||
wait -n
|
||||
echo "first done, status: $?"
|
||||
wait -n
|
||||
echo "second done, status: $?"
|
||||
|
||||
- name: "Background job failure with wait"
|
||||
known_failure: true # TODO(jobs): wait should return job's exit status
|
||||
stdin: |
|
||||
{ sleep 0.01; exit 3; } &
|
||||
pid=$!
|
||||
wait $pid
|
||||
echo "status: $?"
|
||||
@@ -0,0 +1,637 @@
|
||||
name: "Call stack"
|
||||
common_test_files:
|
||||
- path: "callstack_helpers.sh"
|
||||
contents: |
|
||||
# Helper to dump frame-independent call stack state.
|
||||
# Optionally takes a prefix string to use in all output.
|
||||
dump_source_info() {
|
||||
local prefix="${1:-}"
|
||||
|
||||
# Normalize shell name to make sure it matches.
|
||||
# TODO: Once all tests are run on 5.3, this will need to be revisited.
|
||||
echo "${prefix} caller: $(caller 2>&1 || echo '<failed>')" | sed -e "s|$0|main|g" | sed -e "s/environment/main/g"
|
||||
|
||||
echo "${prefix} LINENO: ${LINENO}"
|
||||
echo "${prefix} FUNCNAME[@]: ${FUNCNAME[*]}"
|
||||
|
||||
# Normalize shell name to make sure it matches.
|
||||
# TODO: Once all tests are run on 5.3, this will need to be revisited.
|
||||
echo "${prefix} BASH_SOURCE[@]: ${BASH_SOURCE[*]}" | sed -e "s|$0|main|g" | sed -e "s/environment/main/g"
|
||||
|
||||
# TODO(BASH_LINENO): implement correct BASH_LINENO tracking
|
||||
# echo "${prefix} BASH_LINENO[@]: ${BASH_LINENO[*]}"
|
||||
}
|
||||
|
||||
# Helper to dump frame-specific call stack state
|
||||
dump_frame() {
|
||||
local prefix="${1:-}"
|
||||
local frame="${2:-0}"
|
||||
|
||||
echo "${prefix}[Frame $frame]"
|
||||
|
||||
# Normalize shell name to make sure it matches.
|
||||
# TODO: Once all tests are run on 5.3, this will need to be revisited.
|
||||
echo "${prefix} caller N: $(caller $frame 2>&1 || echo '<failed>')" | sed -e "s|$0|main|g" | sed -e "s/environment/main/g"
|
||||
}
|
||||
|
||||
# Helper to dump all stack info
|
||||
dump_stack() {
|
||||
local prefix="${1:-}"
|
||||
|
||||
dump_source_info "$prefix"
|
||||
|
||||
local max_frames="${2:-20}"
|
||||
local i
|
||||
for ((i=0; i<max_frames; i++)); do
|
||||
if ! caller $i >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
dump_frame "$prefix" $i
|
||||
done
|
||||
}
|
||||
|
||||
cases:
|
||||
- name: "No calls - top level"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
echo "=== Top level ==="
|
||||
dump_stack
|
||||
|
||||
- name: "Simple function call"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_function() {
|
||||
echo "=== In my_function ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
my_function
|
||||
|
||||
- name: "Nested function calls"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
level3() {
|
||||
echo "=== In level3 ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
level2() {
|
||||
echo "=== In level2 ==="
|
||||
dump_stack " "
|
||||
level3
|
||||
}
|
||||
|
||||
level1() {
|
||||
echo "=== In level1 ==="
|
||||
dump_stack " "
|
||||
level2
|
||||
}
|
||||
|
||||
level1
|
||||
|
||||
- name: "Function call from sourced script"
|
||||
test_files:
|
||||
- path: "sourced.sh"
|
||||
contents: |
|
||||
sourced_function() {
|
||||
echo "=== In sourced_function ==="
|
||||
dump_stack " "
|
||||
}
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
source sourced.sh
|
||||
sourced_function
|
||||
|
||||
- name: "Nested sourcing"
|
||||
test_files:
|
||||
- path: "outer.sh"
|
||||
contents: |
|
||||
echo "=== In outer.sh top level ==="
|
||||
dump_stack " "
|
||||
source inner.sh
|
||||
- path: "inner.sh"
|
||||
contents: |
|
||||
echo "=== In inner.sh top level ==="
|
||||
dump_stack " "
|
||||
|
||||
inner_func() {
|
||||
echo "=== In inner_func ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
inner_func
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
command source outer.sh
|
||||
|
||||
- name: "Command substitution in top level"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
result=$(dump_stack " [CmdSub] ")
|
||||
echo "$result"
|
||||
|
||||
- name: "Command substitution in function"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_function() {
|
||||
echo "=== In my_function ==="
|
||||
result=$(dump_stack " [CmdSub] ")
|
||||
echo "$result"
|
||||
}
|
||||
|
||||
my_function
|
||||
|
||||
- name: "Command substitution invoking function"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_function() {
|
||||
echo "=== In my_function via cmdSub ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
result=$(my_function)
|
||||
echo "$result"
|
||||
|
||||
- name: "Nested command substitutions"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
inner_func() {
|
||||
echo "=== In inner_func ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
outer_func() {
|
||||
echo "=== In outer_func ==="
|
||||
result=$(inner_func)
|
||||
echo "$result"
|
||||
}
|
||||
|
||||
final=$(outer_func)
|
||||
echo "$final"
|
||||
|
||||
- name: "eval at top level"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
eval 'echo "=== In eval ==="; dump_stack " "'
|
||||
|
||||
- name: "eval in function"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_function() {
|
||||
echo "=== In my_function ==="
|
||||
eval 'echo "=== In eval ==="; dump_stack " "'
|
||||
}
|
||||
|
||||
my_function
|
||||
|
||||
- name: "eval calling function"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_function() {
|
||||
echo "=== In my_function via eval ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
eval 'my_function'
|
||||
|
||||
- name: "Nested evals"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_function() {
|
||||
echo "=== In my_function ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
eval 'eval "my_function"'
|
||||
|
||||
- name: "eval with command substitution"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_function() {
|
||||
echo "=== In my_function ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
result=$(eval 'my_function')
|
||||
echo "$result"
|
||||
|
||||
- name: "Trap handler - DEBUG"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
trap_handler() {
|
||||
echo "=== In DEBUG trap handler ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
trap 'trap_handler' DEBUG
|
||||
|
||||
echo "Command 1"
|
||||
echo "Command 2"
|
||||
|
||||
- name: "Trap handler - DEBUG in function"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
trap_handler() {
|
||||
echo "=== In DEBUG trap handler ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
trap 'trap_handler' DEBUG
|
||||
|
||||
my_function() {
|
||||
echo "In my_function"
|
||||
echo "Still in my_function"
|
||||
}
|
||||
|
||||
my_function
|
||||
|
||||
- name: "Trap handler - EXIT"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
trap 'echo "=== In EXIT trap ==="; dump_stack " "' EXIT
|
||||
|
||||
echo "Main script"
|
||||
|
||||
- name: "Trap handler calling function"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
trap_function() {
|
||||
echo "=== In trap_function ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
trap 'trap_function' EXIT
|
||||
|
||||
echo "Main script"
|
||||
|
||||
- name: "LINENO tracking across contexts"
|
||||
stdin: |
|
||||
echo "Line 1: LINENO=${LINENO}"
|
||||
echo "Line 2: LINENO=${LINENO}"
|
||||
|
||||
my_function() {
|
||||
echo "In function line 1: LINENO=${LINENO}"
|
||||
echo "In function line 2: LINENO=${LINENO}"
|
||||
}
|
||||
|
||||
my_function
|
||||
|
||||
echo "Line 3: LINENO=${LINENO}"
|
||||
|
||||
- name: "LINENO in eval"
|
||||
stdin: |
|
||||
echo "Before eval: LINENO=${LINENO}"
|
||||
eval 'echo "In eval: LINENO=${LINENO}"'
|
||||
echo "After eval: LINENO=${LINENO}"
|
||||
|
||||
- name: "LINENO in command substitution"
|
||||
stdin: |
|
||||
echo "Before cmdSub: LINENO=${LINENO}"
|
||||
result=$(echo "In cmdSub: LINENO=${LINENO}")
|
||||
echo "$result"
|
||||
echo "After cmdSub: LINENO=${LINENO}"
|
||||
|
||||
- name: "LINENO in sourced script"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "Sourced line 1: LINENO=${LINENO}"
|
||||
echo "Sourced line 2: LINENO=${LINENO}"
|
||||
|
||||
sourced_func() {
|
||||
echo "In sourced_func: LINENO=${LINENO}"
|
||||
}
|
||||
|
||||
sourced_func
|
||||
stdin: |
|
||||
echo "Main line 1: LINENO=${LINENO}"
|
||||
source script.sh
|
||||
echo "Main line 2: LINENO=${LINENO}"
|
||||
|
||||
- name: "different depths"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
level3() {
|
||||
dump_stack
|
||||
}
|
||||
|
||||
level2() {
|
||||
level3
|
||||
}
|
||||
|
||||
level1() {
|
||||
level2
|
||||
}
|
||||
|
||||
level1
|
||||
|
||||
- name: "BASH_SOURCE and FUNCNAME arrays"
|
||||
test_files:
|
||||
- path: "outer.sh"
|
||||
contents: |
|
||||
outer_func() {
|
||||
echo "=== In outer_func ==="
|
||||
echo " FUNCNAME[@]: ${FUNCNAME[*]}"
|
||||
echo " BASH_SOURCE[@]: ${BASH_SOURCE[*]}"
|
||||
source inner.sh
|
||||
}
|
||||
- path: "inner.sh"
|
||||
contents: |
|
||||
inner_func() {
|
||||
echo "=== In inner_func ==="
|
||||
echo " FUNCNAME[@]: ${FUNCNAME[*]}"
|
||||
echo " BASH_SOURCE[@]: ${BASH_SOURCE[*]}"
|
||||
}
|
||||
|
||||
inner_func
|
||||
stdin: |
|
||||
source outer.sh
|
||||
outer_func
|
||||
|
||||
- name: "Mixed: eval in sourced function with command substitution"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
script_func() {
|
||||
echo "=== In script_func ==="
|
||||
result=$(eval 'dump_stack " [Eval->CmdSub] "')
|
||||
echo "$result"
|
||||
}
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
source script.sh
|
||||
script_func
|
||||
|
||||
- name: "Complex nesting: function -> eval -> cmdSub -> function"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
inner_func() {
|
||||
echo "=== In inner_func ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
outer_func() {
|
||||
echo "=== In outer_func ==="
|
||||
eval 'result=$(inner_func); echo "$result"'
|
||||
}
|
||||
|
||||
outer_func
|
||||
|
||||
# Tests with -c flag
|
||||
- name: "Call stack with -c flag"
|
||||
args:
|
||||
["-c", "source callstack_helpers.sh; echo '=== Via -c ==='; dump_stack"]
|
||||
|
||||
- name: "Function call with -c flag"
|
||||
args:
|
||||
[
|
||||
"-c",
|
||||
"my_func() { echo '=== In my_func via -c ==='; dump_stack; }; source callstack_helpers.sh; my_func",
|
||||
]
|
||||
|
||||
- name: "Nested functions with -c flag"
|
||||
args:
|
||||
[
|
||||
"-c",
|
||||
"f2() { echo 'In f2'; dump_stack; }; f1() { echo 'In f1'; f2; }; source callstack_helpers.sh; f1",
|
||||
]
|
||||
|
||||
- name: "eval with -c flag"
|
||||
args:
|
||||
[
|
||||
"-c",
|
||||
"eval 'source callstack_helpers.sh; echo In eval via -c; dump_stack'",
|
||||
]
|
||||
|
||||
- name: "Command substitution with -c flag"
|
||||
args:
|
||||
[
|
||||
"-c",
|
||||
"source callstack_helpers.sh; result=$(echo LINENO=${LINENO}; dump_stack); echo $result",
|
||||
]
|
||||
|
||||
# Interactive mode tests
|
||||
- name: "Interactive mode - simple function"
|
||||
known_failure: true # TODO(LINENO): implement accurate interactive mode tracking
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
f() { dump_stack; }
|
||||
f
|
||||
|
||||
- name: "Interactive mode - FUNCNAME and BASH_SOURCE"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
f() { echo "FUNCNAME: ${FUNCNAME[@]}"; echo "BASH_SOURCE: ${BASH_SOURCE[@]}"; }
|
||||
f
|
||||
|
||||
# PROMPT_COMMAND tests
|
||||
- name: "PROMPT_COMMAND with call stack"
|
||||
known_failure: true # TODO(LINENO): implement accurate PROMPT_COMMAND line tracking
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
PROMPT_COMMAND='dump_stack "[PC] "'
|
||||
echo test
|
||||
|
||||
- name: "PROMPT_COMMAND calling function"
|
||||
known_failure: true # TODO(LINENO): implement accurate PROMPT_COMMAND line tracking
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
pc_func() { echo "[PC_FUNC] In function"; dump_stack; }
|
||||
PROMPT_COMMAND='pc_func'
|
||||
echo test
|
||||
|
||||
- name: "PROMPT_COMMAND with nested calls"
|
||||
known_failure: true # TODO(LINENO): implement accurate PROMPT_COMMAND line tracking
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
inner() { dump_stack; }
|
||||
outer() { inner; }
|
||||
PROMPT_COMMAND='outer'
|
||||
echo test
|
||||
|
||||
# Additional edge cases
|
||||
- name: "Recursive function call stack"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
recursive_func() {
|
||||
local depth=$1
|
||||
echo "=== Depth $depth ==="
|
||||
|
||||
if [ $depth -gt 0 ]; then
|
||||
recursive_func $((depth - 1))
|
||||
else
|
||||
dump_stack " "
|
||||
fi
|
||||
}
|
||||
|
||||
recursive_func 3
|
||||
|
||||
- name: "Call stack with process substitution"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_func() {
|
||||
echo "=== In my_func ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
# Process substitution context
|
||||
cat <(my_func)
|
||||
|
||||
- name: "Call stack across pipe"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_func() {
|
||||
echo "=== In my_func ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
my_func | cat
|
||||
|
||||
- name: "Call stack in subshell"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_func() {
|
||||
echo "=== In my_func (subshell) ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
(my_func)
|
||||
|
||||
- name: "Call stack with background job"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_func() {
|
||||
echo "=== In my_func (background) ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
my_func &
|
||||
wait
|
||||
|
||||
- name: "BASH_SOURCE indexing"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "In script.sh"
|
||||
echo " BASH_SOURCE[0]: ${BASH_SOURCE[0]}"
|
||||
echo " BASH_SOURCE[1]: ${BASH_SOURCE[1]}"
|
||||
|
||||
script_func() {
|
||||
echo "In script_func"
|
||||
echo " BASH_SOURCE[0]: ${BASH_SOURCE[0]}"
|
||||
echo " BASH_SOURCE[1]: ${BASH_SOURCE[1]}"
|
||||
echo " BASH_SOURCE[2]: ${BASH_SOURCE[2]}"
|
||||
}
|
||||
|
||||
script_func
|
||||
stdin: |
|
||||
echo "Main script"
|
||||
echo " BASH_SOURCE[0]: ${BASH_SOURCE[0]}"
|
||||
source script.sh
|
||||
|
||||
- name: "FUNCNAME at top level"
|
||||
stdin: |
|
||||
echo "Top level FUNCNAME: '${FUNCNAME}'"
|
||||
echo "Top level FUNCNAME[@]: '${FUNCNAME[@]}'"
|
||||
echo "Top level FUNCNAME[*]: '${FUNCNAME[*]}'"
|
||||
|
||||
- name: "Empty BASH_LINENO at top level"
|
||||
stdin: |
|
||||
echo "Top level BASH_LINENO[@]: '${BASH_LINENO[@]}'"
|
||||
echo "Top level BASH_LINENO[*]: '${BASH_LINENO[*]}'"
|
||||
|
||||
- name: "Call stack in trap with function call"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
trap_func() {
|
||||
echo "=== In trap_func ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
my_func() {
|
||||
echo "=== In my_func ==="
|
||||
echo "About to trigger trap"
|
||||
}
|
||||
|
||||
trap 'trap_func' DEBUG
|
||||
my_func
|
||||
|
||||
- name: "Call stack: source within eval within function"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo "=== In sourced script ==="
|
||||
dump_stack " "
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_func() {
|
||||
echo "=== In my_func ==="
|
||||
eval 'source script.sh'
|
||||
}
|
||||
|
||||
my_func
|
||||
|
||||
- name: "Multiple eval levels"
|
||||
stdin: |
|
||||
source callstack_helpers.sh
|
||||
|
||||
my_func() {
|
||||
echo "=== In my_func ==="
|
||||
dump_stack " "
|
||||
}
|
||||
|
||||
eval 'eval "eval \"my_func\""'
|
||||
|
||||
- name: "LINENO persistence in function"
|
||||
stdin: |
|
||||
my_func() {
|
||||
local line1=${LINENO}
|
||||
local line2=${LINENO}
|
||||
local line3=${LINENO}
|
||||
echo "line1: $line1"
|
||||
echo "line2: $line2"
|
||||
echo "line3: $line3"
|
||||
}
|
||||
|
||||
my_func
|
||||
@@ -0,0 +1,9 @@
|
||||
name: "Complete commands"
|
||||
cases:
|
||||
- name: "Multi-command sequence"
|
||||
stdin: |
|
||||
echo 1; echo 2; echo 3
|
||||
|
||||
- name: "Semicolon-terminated sequence"
|
||||
stdin: |
|
||||
echo 1; echo 2;
|
||||
@@ -0,0 +1,16 @@
|
||||
name: "Compound commands: arithmetic"
|
||||
cases:
|
||||
- name: "Basic arithmetic statements"
|
||||
stdin: |
|
||||
((0 == 0)) && echo "0 == 0"
|
||||
((0 != 0)) && echo "0 != 0"
|
||||
|
||||
- name: "Arithmetic statements with parens"
|
||||
stdin: |
|
||||
(( (0) )) && echo "0"
|
||||
(( (1) )) && echo "1"
|
||||
|
||||
- name: "Arithmetic statements with parens and operators"
|
||||
stdin: |
|
||||
(( (0) == 0 )) && echo "0 == 0"
|
||||
(( (1) != 0 )) && echo "1 != 0"
|
||||
@@ -0,0 +1,110 @@
|
||||
name: "Compound commands: arithmetic for"
|
||||
cases:
|
||||
- name: "Single-line arithmetic for loop"
|
||||
stdin: |
|
||||
for ((i = 0; i < 5; i++)); do echo $i; done
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Arithmetic for loop without separator"
|
||||
stdin: |
|
||||
for ((i = 0; i < 5; i++)) do echo $i; done
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Arithmetic for loop with empty condition"
|
||||
stdin: |
|
||||
for (( ; ; )); do
|
||||
echo "In loop; status: $?"
|
||||
break
|
||||
done
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Arithmetic for loop with ;;"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
for ((;;)); do
|
||||
echo "In loop; status: $?"
|
||||
break
|
||||
done
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Break in arithmetic for loop"
|
||||
stdin: |
|
||||
for ((i = 0; i < 5; i++)); do
|
||||
echo $i
|
||||
break
|
||||
done
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Continue in arithmetic for loop"
|
||||
stdin: |
|
||||
for ((i = 0; i < 5; i++)); do
|
||||
continue
|
||||
echo "Should not print"
|
||||
done
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Arithmetic for with break in condition inside outer loop"
|
||||
stdin: |
|
||||
for j in 1 2 3; do
|
||||
echo "Outer iteration $j"
|
||||
for ((i = 0; break; i++)); do
|
||||
echo "Inner body"
|
||||
done
|
||||
echo "After inner for"
|
||||
done
|
||||
echo "After outer loop"
|
||||
|
||||
- name: "Arithmetic for with continue in condition inside outer loop"
|
||||
stdin: |
|
||||
for j in 1 2 3; do
|
||||
echo "Outer iteration $j"
|
||||
for ((i = 0; continue; i++)); do
|
||||
echo "Inner body"
|
||||
done
|
||||
echo "After inner for"
|
||||
done
|
||||
echo "After outer loop"
|
||||
|
||||
- name: "Arithmetic for with return in body"
|
||||
stdin: |
|
||||
f() {
|
||||
for ((i = 0; i < 5; i++)); do
|
||||
echo "Iteration $i"
|
||||
if [ $i -eq 2 ]; then
|
||||
return 42
|
||||
fi
|
||||
done
|
||||
echo "After for"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "Arithmetic for with exit in body"
|
||||
stdin: |
|
||||
for ((i = 0; i < 5; i++)); do
|
||||
echo "Iteration $i"
|
||||
if [ $i -eq 2 ]; then
|
||||
exit 42
|
||||
fi
|
||||
done
|
||||
echo "After for"
|
||||
|
||||
- name: "Arithmetic for with alternate syntax"
|
||||
stdin: |
|
||||
for ((i = 0; i < 5; i++)) {
|
||||
echo "Iteration $i"
|
||||
}
|
||||
|
||||
- name: "Arithmetic for with alternate syntax (one line)"
|
||||
stdin: |
|
||||
for ((i = 0; i < 5; i++)) { echo "Iteration $i"; }
|
||||
|
||||
- name: "Arithmetic for loop with nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
target=0
|
||||
declare -n ref=target
|
||||
for ((ref=1; ref<=3; ref++)); do
|
||||
echo "ref=$ref target=$target"
|
||||
done
|
||||
echo "final target=$target"
|
||||
@@ -0,0 +1,5 @@
|
||||
name: "Compound commands: brace"
|
||||
cases:
|
||||
- name: "Brace command"
|
||||
stdin: |
|
||||
{ echo 'hi'; }
|
||||
@@ -0,0 +1,185 @@
|
||||
name: "Compound commands: case"
|
||||
cases:
|
||||
- name: "Basic case statement with double semi"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
case x in
|
||||
x) echo hi;;
|
||||
esac
|
||||
args: ["./script.sh"]
|
||||
|
||||
- name: "Basic catch-all pattern against multi-line input"
|
||||
stdin: |
|
||||
case "$(echo hi; echo there)" in
|
||||
"not-it") echo "did not work" ;;
|
||||
*) echo "caught it" ;;
|
||||
esac
|
||||
|
||||
- name: "One-line case statement with double semi"
|
||||
stdin: |
|
||||
case x in x) echo "hi";; esac
|
||||
|
||||
- name: "Interesting characters in cases"
|
||||
stdin: |
|
||||
case "{" in
|
||||
{) echo "curly brace" ;;
|
||||
*) echo "unhandled case" ;;
|
||||
esac
|
||||
|
||||
- name: "Case with case insensitive pattern"
|
||||
stdin: |
|
||||
shopt -s nocasematch
|
||||
case "A" in
|
||||
a) echo "matched" ;;
|
||||
*) echo "did not match" ;;
|
||||
esac
|
||||
|
||||
- name: "Interesting patterns in cases"
|
||||
stdin: |
|
||||
for word in "-a" "!b" "*c" "(d" "{e" ":f" "'g"; do
|
||||
case "${word}" in
|
||||
\!*) echo "starts with exclamation" ;;
|
||||
-*) echo "starts with hyphen" ;;
|
||||
\**) echo "starts with asterisk" ;;
|
||||
\(*) echo "starts with open parenthesis" ;;
|
||||
\{*) echo "starts with open curly brace" ;;
|
||||
:*) echo "starts with colon" ;;
|
||||
\'*) echo "starts with single quote" ;;
|
||||
*) echo "unhandled case" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
- name: "Empty case"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
case abc in
|
||||
*b*) ;;
|
||||
*) return 33;;
|
||||
esac
|
||||
|
||||
echo "Dropped out"
|
||||
}
|
||||
|
||||
myfunc
|
||||
|
||||
- name: "Case with non-dsemi"
|
||||
stdin: |
|
||||
case "b" in
|
||||
a) echo "a";;
|
||||
b) echo "b"
|
||||
esac
|
||||
|
||||
- name: "Case with fall-through"
|
||||
stdin: |
|
||||
case "b" in
|
||||
a) echo "a";;
|
||||
b) echo "b";&
|
||||
c) echo "c";;
|
||||
d) echo "d";;
|
||||
esac
|
||||
|
||||
- name: "Case with resuming switch"
|
||||
stdin: |
|
||||
case "b" in
|
||||
a) echo "a";;
|
||||
b) echo "b";;&
|
||||
c) echo "c";;
|
||||
*) echo "*";;
|
||||
d) echo "d";;
|
||||
esac
|
||||
|
||||
- name: "Case status values"
|
||||
stdin: |
|
||||
function yield() {
|
||||
return $1
|
||||
}
|
||||
|
||||
case "a" in
|
||||
x) yield 10;;
|
||||
a) yield 11;;
|
||||
b) yield 12;;
|
||||
esac
|
||||
echo "1: $?"
|
||||
|
||||
case "a" in
|
||||
b) yield 10;;
|
||||
c) yield 11;;
|
||||
esac
|
||||
echo "2: $?"
|
||||
|
||||
case "a" in
|
||||
a) yield 10;&
|
||||
b) yield 11;;
|
||||
c) yield 12;;
|
||||
esac
|
||||
echo "3: $?"
|
||||
|
||||
case "a" in
|
||||
a) yield 10;;&
|
||||
*) yield 11;;
|
||||
x) yield 12;;
|
||||
esac
|
||||
echo "4: $?"
|
||||
|
||||
- name: "Case with reserved words"
|
||||
stdin: |
|
||||
case case in
|
||||
case) echo "saw case";;
|
||||
esac
|
||||
|
||||
- name: "Case with return in function"
|
||||
stdin: |
|
||||
f() {
|
||||
case "a" in
|
||||
a) echo "matched a"; return 42;;
|
||||
b) echo "matched b";;
|
||||
esac
|
||||
echo "After case"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "Case with exit"
|
||||
stdin: |
|
||||
case "a" in
|
||||
a) echo "matched a"; exit 42;;
|
||||
b) echo "matched b";;
|
||||
esac
|
||||
echo "After case"
|
||||
|
||||
- name: "Case with break in loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
case "$i" in
|
||||
1) echo "one";;
|
||||
2) echo "two"; break;;
|
||||
3) echo "three";;
|
||||
esac
|
||||
echo "After case iteration $i"
|
||||
done
|
||||
echo "After loop"
|
||||
|
||||
- name: "Case with continue in loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
case "$i" in
|
||||
1) echo "one";;
|
||||
2) echo "two"; continue;;
|
||||
3) echo "three";;
|
||||
esac
|
||||
echo "After case iteration $i"
|
||||
done
|
||||
echo "After loop"
|
||||
|
||||
- name: "Case with return and fall-through"
|
||||
stdin: |
|
||||
f() {
|
||||
case "a" in
|
||||
a) echo "matched a"; return 42;&
|
||||
b) echo "matched b";;
|
||||
esac
|
||||
echo "After case"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
@@ -0,0 +1,839 @@
|
||||
name: "Compound commands: coproc"
|
||||
|
||||
# NOTE: Coproc tests must defend against TWO race conditions caused by bash's
|
||||
# SIGCHLD handler:
|
||||
#
|
||||
# 1. The handler clears the COPROC array when the coproc child exits, so
|
||||
# ${COPROC[0]} can become empty before the parent captures it.
|
||||
# 2. The handler ALSO closes the underlying pipe FDs, so even after capturing
|
||||
# the integer FD, reading from it returns EOF immediately.
|
||||
#
|
||||
# We defend against both by:
|
||||
# (a) Having the coproc body block on `read -r _ < _sync` first, so it can't
|
||||
# exit before the parent dups the FD (no SIGCHLD can fire yet).
|
||||
# (b) Using `exec 9<&${COPROC[0]}` to dup the pipe to a fresh FD that bash
|
||||
# does NOT manage as part of the coproc, so it survives SIGCHLD cleanup.
|
||||
# (c) Then `echo x > _sync` releases the coproc to do its real work.
|
||||
#
|
||||
# We use explicit FD 9 (rather than `{fd}<&...` auto-assignment) for portability.
|
||||
|
||||
cases:
|
||||
#
|
||||
# Basic invocation
|
||||
#
|
||||
- name: "Unnamed coproc with brace group"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; echo "hello from coproc"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Unnamed coproc with subshell"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc ( read -r _ < _sync; echo "hello from subshell coproc" )
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Named coproc with brace group"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc MYCOPROC { read -r _ < _sync; echo "named coproc"; }
|
||||
exec 9<&${MYCOPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Named coproc with subshell"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc MYSUB ( read -r _ < _sync; echo "named subshell coproc" )
|
||||
exec 9<&${MYSUB[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc with simple command"
|
||||
skip: true # TODO(coproc): hangs - wait never completes after bidirectional coproc I/O
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc cat
|
||||
rfd=${COPROC[0]}
|
||||
wfd=${COPROC[1]}
|
||||
echo "input line" >&$wfd
|
||||
exec {wfd}>&-
|
||||
read -r line <&$rfd
|
||||
echo "got: $line"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc with multi-statement brace group"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
echo "line1"
|
||||
echo "line2"
|
||||
echo "line3"
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# COPROC and COPROC_PID variable validation via functional use
|
||||
#
|
||||
- name: "COPROC array read FD is functional"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; echo "via read fd"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
read -r line <&9
|
||||
echo "got: $line"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Named coproc FDs are functional"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc MYPROC { read -r _ < _sync; echo "named fd test"; }
|
||||
exec 9<&${MYPROC[0]}
|
||||
echo x > _sync
|
||||
read -r line <&9
|
||||
echo "got: $line"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Reading from coproc stdout
|
||||
#
|
||||
- name: "Read coproc output with cat"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; echo "output from coproc"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Read coproc output with read builtin"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; echo "readline test"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
read -r line <&9
|
||||
echo "got: $line"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Read multiple lines from coproc"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
echo "first"
|
||||
echo "second"
|
||||
echo "third"
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Read multiple lines with read builtin"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
echo "line1"
|
||||
echo "line2"
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
{
|
||||
read -r first
|
||||
read -r second
|
||||
} <&9
|
||||
echo "first=$first"
|
||||
echo "second=$second"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Named coproc read"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc READER { read -r _ < _sync; echo "from named"; }
|
||||
exec 9<&${READER[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Writing to coproc stdin
|
||||
#
|
||||
- name: "Write to coproc stdin and read back"
|
||||
skip: true # TODO(coproc): hangs - wait never completes after bidirectional coproc I/O
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc cat
|
||||
rfd=${COPROC[0]}
|
||||
wfd=${COPROC[1]}
|
||||
echo "hello coproc" >&$wfd
|
||||
exec {wfd}>&-
|
||||
read -r line <&$rfd
|
||||
echo "got: $line"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Bidirectional communication"
|
||||
skip: true # TODO(coproc): hangs - wait never completes after bidirectional coproc I/O
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc {
|
||||
while read -r line; do
|
||||
echo "processed: $line"
|
||||
done
|
||||
}
|
||||
rfd=${COPROC[0]}
|
||||
wfd=${COPROC[1]}
|
||||
echo "msg1" >&$wfd
|
||||
echo "msg2" >&$wfd
|
||||
exec {wfd}>&-
|
||||
{
|
||||
read -r out1
|
||||
read -r out2
|
||||
} <&$rfd
|
||||
echo "$out1"
|
||||
echo "$out2"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Named coproc bidirectional"
|
||||
skip: true # TODO(coproc): hangs - wait never completes after bidirectional coproc I/O
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc BIDIR {
|
||||
while read -r line; do
|
||||
echo "echo: $line"
|
||||
done
|
||||
}
|
||||
rfd=${BIDIR[0]}
|
||||
wfd=${BIDIR[1]}
|
||||
echo "test1" >&$wfd
|
||||
echo "test2" >&$wfd
|
||||
exec {wfd}>&-
|
||||
{
|
||||
read -r out1
|
||||
read -r out2
|
||||
} <&$rfd
|
||||
echo "$out1"
|
||||
echo "$out2"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Close write FD to signal EOF"
|
||||
skip: true # TODO(coproc): hangs - wait never completes after bidirectional coproc I/O
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc {
|
||||
count=0
|
||||
while read -r line; do
|
||||
count=$((count + 1))
|
||||
done
|
||||
echo "read $count lines"
|
||||
}
|
||||
rfd=${COPROC[0]}
|
||||
wfd=${COPROC[1]}
|
||||
echo "a" >&$wfd
|
||||
echo "b" >&$wfd
|
||||
echo "c" >&$wfd
|
||||
exec {wfd}>&-
|
||||
read -r result <&$rfd
|
||||
echo "$result"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Bidirectional with cat for EOF"
|
||||
skip: true # TODO(coproc): hangs - stdout pipe not fully closed after coproc exits
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc {
|
||||
while read -r line; do
|
||||
echo "processed: $line"
|
||||
done
|
||||
}
|
||||
rfd=${COPROC[0]}
|
||||
wfd=${COPROC[1]}
|
||||
echo "msg1" >&$wfd
|
||||
echo "msg2" >&$wfd
|
||||
exec {wfd}>&-
|
||||
cat <&$rfd
|
||||
wait
|
||||
echo "DONE"
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Coproc with compound command bodies
|
||||
#
|
||||
- name: "Coproc running a for loop"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
for i in 1 2 3; do
|
||||
echo "iteration $i"
|
||||
done
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc running a while loop"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
n=1
|
||||
while [ $n -le 3 ]; do
|
||||
echo "count $n"
|
||||
n=$((n + 1))
|
||||
done
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc with conditionals"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
if true; then
|
||||
echo "true branch"
|
||||
else
|
||||
echo "false branch"
|
||||
fi
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc with case statement"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
val="hello"
|
||||
case $val in
|
||||
hello) echo "matched hello";;
|
||||
*) echo "no match";;
|
||||
esac
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc body with pipeline"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
echo -e "banana\napple\ncherry" | sort
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc body with command substitution"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
result=$(echo "hello" | tr a-z A-Z)
|
||||
echo "$result"
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc with arithmetic operations"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
a=5
|
||||
b=3
|
||||
echo $((a + b))
|
||||
echo $((a * b))
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Exit status and lifecycle
|
||||
#
|
||||
- name: "Coproc statement returns 0"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; echo done; }
|
||||
status=$?
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
echo "exit status: $status"
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc with failing body returns 0 immediately"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc { exit 42; }
|
||||
echo "coproc start status: $?"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Wait with no args waits for coproc"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; echo "coproc done"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
echo "after wait"
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Error cases
|
||||
#
|
||||
- name: "Invalid coproc name"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc 123bad { echo x; }
|
||||
echo "exit: $?"
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Coproc in different contexts
|
||||
#
|
||||
- name: "Coproc inside a function"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
start_coproc() {
|
||||
coproc { read -r _ < _sync; echo "from function coproc"; }
|
||||
}
|
||||
start_coproc
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc inside a conditional"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
if true; then
|
||||
coproc { read -r _ < _sync; echo "conditional coproc"; }
|
||||
fi
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc inside a loop body"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
for i in 1; do
|
||||
coproc { read -r _ < _sync; echo "loop coproc $i"; }
|
||||
done
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Named coproc inside a function"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
f() {
|
||||
coproc FUNCPROC { read -r _ < _sync; echo "named in func"; }
|
||||
}
|
||||
f
|
||||
exec 9<&${FUNCPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Multiple coprocs and variable lifecycle
|
||||
#
|
||||
- name: "Second unnamed coproc replaces first"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; echo "first"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
exec 9<&-
|
||||
wait
|
||||
coproc { read -r _ < _sync; echo "second"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Sequential named coprocs with different names"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc PROC1 { read -r _ < _sync; echo "proc1"; }
|
||||
exec 9<&${PROC1[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
exec 9<&-
|
||||
wait
|
||||
coproc PROC2 { read -r _ < _sync; echo "proc2"; }
|
||||
exec 9<&${PROC2[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Environment and variable scope
|
||||
#
|
||||
- name: "Coproc inherits parent environment"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
export MY_VAR="inherited"
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; echo "MY_VAR=$MY_VAR"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc inherits non-exported variables"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
LOCAL_VAR="visible"
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; echo "LOCAL_VAR=$LOCAL_VAR"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc cannot modify parent variables"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
PARENT_VAR="original"
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; PARENT_VAR="modified"; echo "in coproc: $PARENT_VAR"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
echo "in parent: $PARENT_VAR"
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc inherits functions"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
greet() { echo "hello $1"; }
|
||||
export -f greet
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; greet "world"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc inherits aliases"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
shopt -s expand_aliases
|
||||
alias myecho='echo ALIASED:'
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; myecho "test"; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Coproc with redirections in body
|
||||
#
|
||||
- name: "Coproc body with stderr redirection"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
echo "stdout msg"
|
||||
echo "stderr msg" >&2
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc body writing to a file"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
echo "file content" > /tmp/coproc_test_$$
|
||||
echo "wrote file"
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
cat /tmp/coproc_test_$$
|
||||
rm -f /tmp/coproc_test_$$
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Coproc with various data patterns
|
||||
#
|
||||
- name: "Coproc producing empty output"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc { read -r _ < _sync; true; }
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
echo "after empty coproc"
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc producing multiple lines of output"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||||
echo "line $i"
|
||||
done
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
count=0
|
||||
while read -r line; do
|
||||
count=$((count + 1))
|
||||
done <&9
|
||||
echo "read $count lines"
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc with multiline string output"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
cat <<'HEREDOC'
|
||||
first line
|
||||
second line
|
||||
third line
|
||||
HEREDOC
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
cat <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Coproc output transformation
|
||||
#
|
||||
- name: "Transform coproc output to uppercase"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
echo "hello"
|
||||
echo "world"
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
while read -r line; do
|
||||
echo "$line" | tr a-z A-Z
|
||||
done <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc output filtered with case"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
mkfifo _sync
|
||||
coproc {
|
||||
read -r _ < _sync
|
||||
echo "apple"
|
||||
echo "banana"
|
||||
echo "avocado"
|
||||
echo "cherry"
|
||||
}
|
||||
exec 9<&${COPROC[0]}
|
||||
echo x > _sync
|
||||
while read -r line; do
|
||||
case $line in
|
||||
a*) echo "starts with a: $line";;
|
||||
esac
|
||||
done <&9
|
||||
wait
|
||||
args: ["./test.sh"]
|
||||
|
||||
#
|
||||
# Tests requiring wait with job specs (known failures)
|
||||
#
|
||||
- name: "Wait for specific coproc PID"
|
||||
known_failure: true # TODO(wait): wait with job specs not implemented
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc { echo "done"; }
|
||||
fd=${COPROC[0]}
|
||||
cat <&$fd
|
||||
wait $COPROC_PID
|
||||
echo "wait status: $?"
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Coproc with failing body - wait captures exit status"
|
||||
known_failure: true # TODO(wait): wait with job specs not implemented
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc { exit 42; }
|
||||
wait $COPROC_PID
|
||||
echo "exit status: $?"
|
||||
args: ["./test.sh"]
|
||||
|
||||
- name: "Wait for named coproc PID"
|
||||
known_failure: true # TODO(wait): wait with job specs not implemented
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
coproc MYPROC { echo "done"; }
|
||||
fd=${MYPROC[0]}
|
||||
cat <&$fd
|
||||
wait $MYPROC_PID
|
||||
echo "status: $?"
|
||||
args: ["./test.sh"]
|
||||
@@ -0,0 +1,195 @@
|
||||
name: "Compound commands: for"
|
||||
cases:
|
||||
- name: "Single-line for loop"
|
||||
stdin: |
|
||||
for f in 1 2 3; do echo $f; done
|
||||
|
||||
- name: "Multi-line for loop"
|
||||
stdin: |
|
||||
for f in 1 2 3; do
|
||||
echo $f
|
||||
done
|
||||
|
||||
- name: "Empty for loop"
|
||||
stdin: |
|
||||
for f in; do echo $f; done
|
||||
|
||||
- name: "for loop without in"
|
||||
stdin: |
|
||||
set -- a b c
|
||||
|
||||
echo "Loop 1"
|
||||
for f; do echo $f; done
|
||||
|
||||
echo "Loop 2: no semicolon"
|
||||
for f do echo $f; done
|
||||
|
||||
- name: "for loop without in but spaces"
|
||||
stdin: |
|
||||
set -- a "b c" d
|
||||
|
||||
echo "Loop 1"
|
||||
for f; do echo $f; done
|
||||
|
||||
echo "Loop 2: no semicolon"
|
||||
for f do echo $f; done
|
||||
|
||||
- name: "Break in for loop"
|
||||
stdin: |
|
||||
for f in 1 2 3; do
|
||||
echo $f
|
||||
break
|
||||
done
|
||||
|
||||
- name: "Break in nested for loop"
|
||||
stdin: |
|
||||
for f in 1 2 3; do
|
||||
for g in a b c; do
|
||||
echo "f=$f g=$g"
|
||||
true && break
|
||||
done
|
||||
echo "Left inner loop"
|
||||
done
|
||||
|
||||
- name: "Break 1 in nested for loops"
|
||||
stdin: |
|
||||
for f in 1 2 3; do
|
||||
for g in a b c; do
|
||||
echo "f=$f g=$g"
|
||||
break 1
|
||||
done
|
||||
done
|
||||
|
||||
- name: "Break 2 in nested for loops"
|
||||
stdin: |
|
||||
for f in 1 2 3; do
|
||||
for g in a b c; do
|
||||
echo "f=$f g=$g"
|
||||
break 2
|
||||
done
|
||||
done
|
||||
|
||||
- name: "Break out of nested for/while loops"
|
||||
stdin: |
|
||||
for f in 1 2 3; do
|
||||
while true; do
|
||||
echo "f=$f"
|
||||
break 2
|
||||
done
|
||||
done
|
||||
|
||||
- name: "Continue in for loop"
|
||||
stdin: |
|
||||
for f in 1 2 3; do
|
||||
echo $f
|
||||
continue
|
||||
echo $f
|
||||
done
|
||||
|
||||
- name: "Continue with bad N"
|
||||
known_failure: true # Fails differently
|
||||
stdin: |
|
||||
for f in 1 2 3; do
|
||||
echo $f
|
||||
continue 0
|
||||
echo result: $?
|
||||
done
|
||||
|
||||
- name: "Continue in nested for loop"
|
||||
stdin: |
|
||||
for f in a b c; do
|
||||
for g in 1 2 3; do
|
||||
echo "f=$f g=$g"
|
||||
continue
|
||||
echo "Should not print"
|
||||
done
|
||||
echo "Left inner loop"
|
||||
done
|
||||
|
||||
- name: "Continue in nested for loop with explicit N=1"
|
||||
stdin: |
|
||||
for f in a b c; do
|
||||
for g in 1 2 3; do
|
||||
echo "f=$f g=$g"
|
||||
continue 1
|
||||
echo "Should not print"
|
||||
done
|
||||
echo "Left inner loop"
|
||||
done
|
||||
|
||||
- name: "Continue in nested for loop with explicit N=2"
|
||||
stdin: |
|
||||
for f in a b c; do
|
||||
for g in 1 2 3; do
|
||||
echo "f=$f g=$g"
|
||||
continue 2
|
||||
echo "Should not print"
|
||||
done
|
||||
echo "Left inner loop"
|
||||
done
|
||||
|
||||
- name: "Continue in nested for loop with too large N"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
for f in a b c; do
|
||||
for g in 1 2 3; do
|
||||
echo "f=$f g=$g"
|
||||
continue 3
|
||||
echo "Should not print"
|
||||
done
|
||||
echo "Left inner loop"
|
||||
done
|
||||
|
||||
- name: "Multi-line for loop"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
for f in 1 2 3; do
|
||||
echo $f
|
||||
done
|
||||
args: ["./script.sh"]
|
||||
|
||||
- name: "For loop piped"
|
||||
stdin: |
|
||||
for f in ab ac bd ef; do echo $f; done | grep b
|
||||
|
||||
- name: "For loop output redirection"
|
||||
stdin: |
|
||||
for f in a b c; do echo $f; done > ./out.txt
|
||||
|
||||
- name: "For reserved word handling"
|
||||
stdin: |
|
||||
for for in for; do echo $for; done
|
||||
|
||||
- name: "For with return in body"
|
||||
stdin: |
|
||||
f() {
|
||||
for i in 1 2 3; do
|
||||
echo "Iteration $i"
|
||||
if [ "$i" = "2" ]; then
|
||||
return 42
|
||||
fi
|
||||
done
|
||||
echo "After for"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "For with exit in body"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Iteration $i"
|
||||
if [ "$i" = "2" ]; then
|
||||
exit 42
|
||||
fi
|
||||
done
|
||||
echo "After for"
|
||||
|
||||
- name: "For loop with nameref variable"
|
||||
stdin: |
|
||||
target=""
|
||||
declare -n ref=target
|
||||
for ref in a b c; do
|
||||
echo "loop: target=$target"
|
||||
done
|
||||
echo "final: target=$target"
|
||||
@@ -0,0 +1,99 @@
|
||||
name: "Compound commands: if"
|
||||
cases:
|
||||
- name: "Basic if"
|
||||
stdin: |
|
||||
if false; then echo 1; else echo 2; fi
|
||||
if false; then echo 3; elif false; then echo 4; else echo 5; fi
|
||||
if true; then echo 6; else echo 7; fi
|
||||
if false; then echo 8; elif true; then echo 10; else echo 11; fi
|
||||
|
||||
- name: "Multi-line if"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
if false; then
|
||||
echo 1
|
||||
else
|
||||
echo 2
|
||||
fi
|
||||
|
||||
if false; then
|
||||
echo 3
|
||||
elif false; then
|
||||
echo 4
|
||||
else
|
||||
echo 5
|
||||
fi
|
||||
|
||||
if true; then
|
||||
echo 6
|
||||
else
|
||||
echo 7
|
||||
fi
|
||||
|
||||
if false; then
|
||||
echo 8
|
||||
elif true; then
|
||||
echo 10
|
||||
else
|
||||
echo 11
|
||||
fi
|
||||
args: ["./script.sh"]
|
||||
|
||||
- name: "If with return in condition"
|
||||
stdin: |
|
||||
f() {
|
||||
if return 42; then
|
||||
echo "Then branch"
|
||||
else
|
||||
echo "Else branch"
|
||||
fi
|
||||
echo "After if"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "If with exit in condition"
|
||||
stdin: |
|
||||
if exit 42; then
|
||||
echo "Then branch"
|
||||
else
|
||||
echo "Else branch"
|
||||
fi
|
||||
echo "After if"
|
||||
|
||||
- name: "If with return in elif condition"
|
||||
stdin: |
|
||||
f() {
|
||||
if false; then
|
||||
echo "Then branch"
|
||||
elif return 42; then
|
||||
echo "Elif then branch"
|
||||
else
|
||||
echo "Else branch"
|
||||
fi
|
||||
echo "After if"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "If with break in condition inside loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
if break; then
|
||||
echo "Then branch"
|
||||
fi
|
||||
echo "After if"
|
||||
done
|
||||
echo "After loop"
|
||||
|
||||
- name: "If with continue in condition inside loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Iteration $i"
|
||||
if continue; then
|
||||
echo "Then branch"
|
||||
fi
|
||||
echo "After if"
|
||||
done
|
||||
echo "After loop"
|
||||
@@ -0,0 +1,10 @@
|
||||
name: "Select statement"
|
||||
cases:
|
||||
- name: "basic"
|
||||
known_failure: true # TODO(select): select statement not implemented
|
||||
stdin: |
|
||||
echo "1" | select opt in a b c; do
|
||||
echo "selected: $opt"
|
||||
break
|
||||
done
|
||||
echo "after select"
|
||||
@@ -0,0 +1,38 @@
|
||||
name: "Compound commands: subshell"
|
||||
cases:
|
||||
- name: "Basic subshell usage"
|
||||
stdin: |
|
||||
(echo hi)
|
||||
|
||||
- name: "Subshells in sequence"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
(echo hi)(echo there)
|
||||
|
||||
- name: "Subshell env usage"
|
||||
stdin: |
|
||||
(subshell_var=value && echo "subshell_var: ${subshell_var}")
|
||||
echo "subshell_var: ${subshell_var}"
|
||||
|
||||
- name: "Subshell output redirection"
|
||||
stdin: |
|
||||
(echo Hello; echo world) >out.txt
|
||||
echo "Dumping out.txt..."
|
||||
cat out.txt
|
||||
|
||||
- name: "Piped subshell usage"
|
||||
stdin: |
|
||||
(echo hi) | wc -l
|
||||
|
||||
- name: "Breaks in subshell"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo $i
|
||||
(for i in 1 2 3; do break 2; done)
|
||||
done
|
||||
|
||||
- name: "Exec in subshell"
|
||||
stdin: |
|
||||
echo "Before subshell"
|
||||
(exec cat <<<"Hello from exec in subshell")
|
||||
echo "After subshell"
|
||||
@@ -0,0 +1,101 @@
|
||||
name: "Compound commands: until"
|
||||
cases:
|
||||
- name: "Single-line until loop"
|
||||
stdin: |
|
||||
until true; do echo 'In loop'; done
|
||||
|
||||
- name: "Arithmetic in until loop"
|
||||
incompatible_configs: ["sh"]
|
||||
stdin: |
|
||||
i=5
|
||||
until ((i == 0)); do echo $i; i=$((i - 1)); done
|
||||
|
||||
- name: "Until with return in condition"
|
||||
stdin: |
|
||||
f() {
|
||||
until return 42; do
|
||||
echo "Body"
|
||||
done
|
||||
echo "After until"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "Until with exit in condition"
|
||||
stdin: |
|
||||
until exit 42; do
|
||||
echo "Body"
|
||||
done
|
||||
echo "After until"
|
||||
|
||||
- name: "Until with break in condition inside outer loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Outer iteration $i"
|
||||
until break; do
|
||||
echo "Until body"
|
||||
done
|
||||
echo "After until"
|
||||
done
|
||||
echo "After outer loop"
|
||||
|
||||
- name: "Until with break 2 in condition inside outer loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Outer iteration $i"
|
||||
until break 2; do
|
||||
echo "Until body"
|
||||
done
|
||||
echo "After until"
|
||||
done
|
||||
echo "After outer loop"
|
||||
|
||||
- name: "Until with continue 2 in condition inside outer loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Outer iteration $i"
|
||||
until continue 2; do
|
||||
echo "Until body"
|
||||
done
|
||||
echo "After until"
|
||||
done
|
||||
echo "After outer loop"
|
||||
|
||||
- name: "Until with continue in condition inside outer loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Outer iteration $i"
|
||||
until continue; do
|
||||
echo "Until body"
|
||||
done
|
||||
echo "After until"
|
||||
done
|
||||
echo "After outer loop"
|
||||
|
||||
- name: "Until with return in body"
|
||||
stdin: |
|
||||
f() {
|
||||
i=0
|
||||
until [ $i -ge 3 ]; do
|
||||
echo "Iteration $i"
|
||||
if [ $i -eq 1 ]; then
|
||||
return 42
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo "After until"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "Until with exit in body"
|
||||
stdin: |
|
||||
i=0
|
||||
until [ $i -ge 3 ]; do
|
||||
echo "Iteration $i"
|
||||
if [ $i -eq 1 ]; then
|
||||
exit 42
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo "After until"
|
||||
@@ -0,0 +1,127 @@
|
||||
name: "Compound commands: while"
|
||||
cases:
|
||||
- name: "Single-line while loop"
|
||||
stdin: |
|
||||
while false; do echo 'In loop'; done
|
||||
|
||||
- name: "break in while loop"
|
||||
stdin: |
|
||||
while true; do
|
||||
echo 'In loop'
|
||||
break
|
||||
done
|
||||
|
||||
- name: "break 2 in nested loops"
|
||||
stdin: |
|
||||
while false; do
|
||||
echo 'Starting inner loop'
|
||||
while true; do
|
||||
echo 'In loop'
|
||||
break 2
|
||||
done
|
||||
echo 'Finished inner loop'
|
||||
done
|
||||
|
||||
- name: "Arithmetic in while loop"
|
||||
stdin: |
|
||||
i=5
|
||||
while ((i > 0)); do echo $i; i=$((i - 1)); done
|
||||
|
||||
- name: "Alternative arithmetic in while loop"
|
||||
stdin: |
|
||||
c=0
|
||||
limit=4
|
||||
while [ $c -lt $limit ]; do
|
||||
case "$c" in
|
||||
0)
|
||||
echo "0"
|
||||
;;
|
||||
1)
|
||||
echo "1"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
((c++))
|
||||
done
|
||||
echo "Done"
|
||||
|
||||
- name: "While with return in condition"
|
||||
stdin: |
|
||||
f() {
|
||||
while return 42; do
|
||||
echo "Body"
|
||||
done
|
||||
echo "After while"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "While with exit in condition"
|
||||
stdin: |
|
||||
while exit 42; do
|
||||
echo "Body"
|
||||
done
|
||||
echo "After while"
|
||||
|
||||
- name: "While with break in condition inside outer loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Outer iteration $i"
|
||||
while break; do
|
||||
echo "While body"
|
||||
done
|
||||
echo "After while"
|
||||
done
|
||||
echo "After outer loop"
|
||||
|
||||
- name: "While with break 2 in condition inside outer loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Outer iteration $i"
|
||||
while break 2; do
|
||||
echo "While body"
|
||||
done
|
||||
echo "After while"
|
||||
done
|
||||
echo "After outer loop"
|
||||
|
||||
- name: "While with continue 2 in condition inside outer loop"
|
||||
stdin: |
|
||||
for i in 1 2 3; do
|
||||
echo "Outer iteration $i"
|
||||
while continue 2; do
|
||||
echo "While body"
|
||||
done
|
||||
echo "After while"
|
||||
done
|
||||
echo "After outer loop"
|
||||
|
||||
- name: "While with return in body"
|
||||
stdin: |
|
||||
f() {
|
||||
i=0
|
||||
while [ $i -lt 3 ]; do
|
||||
echo "Iteration $i"
|
||||
if [ $i -eq 1 ]; then
|
||||
return 42
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo "After while"
|
||||
}
|
||||
f
|
||||
echo "Exit code: $?"
|
||||
|
||||
- name: "While with exit in body"
|
||||
stdin: |
|
||||
i=0
|
||||
while [ $i -lt 3 ]; do
|
||||
echo "Iteration $i"
|
||||
if [ $i -eq 1 ]; then
|
||||
exit 42
|
||||
fi
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo "After while"
|
||||
@@ -0,0 +1,399 @@
|
||||
name: "Error handling"
|
||||
cases:
|
||||
- name: "Fatal error (interactive/subshell)"
|
||||
ignore_stderr: true
|
||||
args: ["-i"]
|
||||
stdin: |
|
||||
echo "before expansion"
|
||||
(echo "${non_existent_var:?missing parameter}")
|
||||
echo "after expansion"
|
||||
|
||||
- name: "Fatal error (non-interactive/subshell)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before expansion"
|
||||
(echo "${non_existent_var:?missing parameter}")
|
||||
echo "after expansion"
|
||||
|
||||
- name: "Fatal error (interactive/command substitution)"
|
||||
ignore_stderr: true
|
||||
args: ["-i"]
|
||||
stdin: |
|
||||
echo "before expansion"
|
||||
x=$(echo "${non_existent_var:?missing parameter}")
|
||||
echo "after expansion"
|
||||
|
||||
- name: "Fatal error (non-interactive/command substitution)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before expansion"
|
||||
x=$(echo "${non_existent_var:?missing parameter}")
|
||||
echo "after expansion"
|
||||
|
||||
- name: "Fatal error (interactive/sourced file)"
|
||||
ignore_stderr: true
|
||||
args: ["-i"]
|
||||
test_files:
|
||||
- path: error.sh
|
||||
contents: |
|
||||
echo "before expansion"
|
||||
echo "${non_existent_var:?missing parameter}"
|
||||
echo "after expansion"
|
||||
stdin: |
|
||||
source error.sh
|
||||
echo "after sourcing script with error"
|
||||
|
||||
- name: "Fatal error (non-interactive/sourced file)"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: error.sh
|
||||
contents: |
|
||||
echo "before expansion"
|
||||
echo "${non_existent_var:?missing parameter}"
|
||||
echo "after expansion"
|
||||
stdin: |
|
||||
source error.sh
|
||||
echo "after sourcing script with error"
|
||||
|
||||
- name: "Fatal error (interactive/executed script)"
|
||||
ignore_stderr: true
|
||||
args: ["-i", "error.sh"]
|
||||
test_files:
|
||||
- path: error.sh
|
||||
contents: |
|
||||
echo "before expansion"
|
||||
echo "${non_existent_var:?missing parameter}"
|
||||
echo "after expansion"
|
||||
|
||||
- name: "Fatal error (non-interactive/executed script)"
|
||||
ignore_stderr: true
|
||||
args: ["error.sh"]
|
||||
test_files:
|
||||
- path: error.sh
|
||||
contents: |
|
||||
echo "before expansion"
|
||||
echo "${non_existent_var:?missing parameter}"
|
||||
echo "after expansion"
|
||||
|
||||
- name: "Shell language syntax error (command line)"
|
||||
incompatible_os: ["azurelinux"] # Needs investigation
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
[[ -% 3 ]]
|
||||
echo "after syntax error"
|
||||
|
||||
- name: "Shell language syntax error (non-interactive)"
|
||||
incompatible_os: ["azurelinux"] # Needs investigation
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
[[ -% 3 ]]
|
||||
echo "after syntax error"
|
||||
|
||||
- name: "Shell language syntax error (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
[[ -% 3 ]]
|
||||
echo "after syntax error"
|
||||
|
||||
- name: "Special built-in error (command line)"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
echo "before shift"
|
||||
set -- foo
|
||||
shift 2
|
||||
echo "after shift"
|
||||
|
||||
- name: "Special built-in error (non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before shift"
|
||||
set -- foo
|
||||
shift 2
|
||||
echo "after shift"
|
||||
|
||||
- name: "Special built-in error (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before shift"
|
||||
set -- foo
|
||||
shift 2
|
||||
echo "after shift"
|
||||
|
||||
- name: "Regular utility error (command line)"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
echo "before cat"
|
||||
cat /no/such/utility/path 2>/dev/null
|
||||
echo "after cat (status $?)"
|
||||
|
||||
- name: "Regular utility error (non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before cat"
|
||||
cat /no/such/utility/path 2>/dev/null
|
||||
echo "after cat (status $?)"
|
||||
|
||||
- name: "Regular utility error (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before cat"
|
||||
cat /no/such/utility/path 2>/dev/null
|
||||
echo "after cat (status $?)"
|
||||
|
||||
- name: "Special built-in redirection error (command line)"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
echo "before special redirection"
|
||||
: > missing_special_dir/out.txt
|
||||
echo "after special redirection"
|
||||
|
||||
- name: "Special built-in redirection error (non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before special redirection"
|
||||
: > missing_special_dir/out.txt
|
||||
echo "after special redirection"
|
||||
|
||||
- name: "Special built-in redirection error (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before special redirection"
|
||||
: > missing_special_dir/out.txt
|
||||
echo "after special redirection"
|
||||
|
||||
- name: "Compound command redirection error (command line)"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
echo "before compound redirection"
|
||||
if true; then echo inside; fi > missing_compound_dir/out.txt
|
||||
echo "after compound redirection"
|
||||
|
||||
- name: "Compound command redirection error (non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before compound redirection"
|
||||
if true; then echo inside; fi > missing_compound_dir/out.txt
|
||||
echo "after compound redirection"
|
||||
|
||||
- name: "Compound command redirection error (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before compound redirection"
|
||||
if true; then echo inside; fi > missing_compound_dir/out.txt
|
||||
echo "after compound redirection"
|
||||
|
||||
- name: "Function redirection error (command line)"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
demo_func() { echo inside; }
|
||||
echo "before function redirection"
|
||||
demo_func > missing_function_dir/out.txt
|
||||
echo "after function redirection"
|
||||
|
||||
- name: "Function redirection error (non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
demo_func() { echo inside; }
|
||||
echo "before function redirection"
|
||||
demo_func > missing_function_dir/out.txt
|
||||
echo "after function redirection"
|
||||
|
||||
- name: "Function redirection error (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
demo_func() { echo inside; }
|
||||
echo "before function redirection"
|
||||
demo_func > missing_function_dir/out.txt
|
||||
echo "after function redirection"
|
||||
|
||||
- name: "Utility redirection error (command line)"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
echo "before cat redirection"
|
||||
cat < missing_utility_dir/input.txt
|
||||
echo "after cat redirection"
|
||||
|
||||
- name: "Utility redirection error (non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before cat redirection"
|
||||
cat < missing_utility_dir/input.txt
|
||||
echo "after cat redirection"
|
||||
|
||||
- name: "Utility redirection error (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before cat redirection"
|
||||
cat < missing_utility_dir/input.txt
|
||||
echo "after cat redirection"
|
||||
|
||||
- name: "Variable assignment error (command line)"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
readonly value_guard=1
|
||||
echo "before readonly assignment"
|
||||
value_guard=2
|
||||
echo "after readonly assignment"
|
||||
|
||||
- name: "Variable assignment error (non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
readonly value_guard=1
|
||||
echo "before readonly assignment"
|
||||
value_guard=2
|
||||
echo "after readonly assignment"
|
||||
|
||||
- name: "Variable assignment error (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
readonly value_guard=1
|
||||
echo "before readonly assignment"
|
||||
value_guard=2
|
||||
echo "after readonly assignment"
|
||||
|
||||
- name: "Expansion error (command line)"
|
||||
known_failure: true # Needs investigation
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
echo "before expansion"
|
||||
echo "${non_existent_var:?missing parameter}"
|
||||
echo "after expansion"
|
||||
|
||||
- name: "Expansion error (non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before expansion"
|
||||
echo "${UNSET_REQUIRED:?missing parameter}"
|
||||
echo "after expansion"
|
||||
|
||||
- name: "Expansion error (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before expansion"
|
||||
echo "${UNSET_REQUIRED:?missing parameter}"
|
||||
echo "after expansion"
|
||||
|
||||
- name: "Command not found (command line)"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
missing_command
|
||||
echo "after missing command (status $?)"
|
||||
|
||||
- name: "Command not found (non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
missing_command
|
||||
echo "after missing command (status $?)"
|
||||
|
||||
- name: "Command not found (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
missing_command
|
||||
echo "after missing command (status $?)"
|
||||
|
||||
- name: "Unrecoverable read error (command line)"
|
||||
ignore_stderr: true
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
echo "before closing stdin"
|
||||
exec 0<&-
|
||||
echo "after closing stdin"
|
||||
|
||||
- name: "Unrecoverable read error (non-interactive)"
|
||||
known_failure: true
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before closing stdin"
|
||||
exec 0<&-
|
||||
echo "after closing stdin"
|
||||
|
||||
- name: "Unrecoverable read error (interactive)"
|
||||
known_failure: true
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before closing stdin"
|
||||
exec 0<&-
|
||||
echo "after closing stdin"
|
||||
|
||||
- name: "Failing special built-in (non-POSIX mode, non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
. ./non-existent-file
|
||||
echo "after builtin call (status $?)"
|
||||
|
||||
- name: "Failing special built-in (non-POSIX mode, interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
. ./non-existent-file
|
||||
echo "after builtin call (status $?)"
|
||||
|
||||
- name: "Failing special built-in (POSIX mode, non-interactive)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -o posix
|
||||
. ./non-existent-file
|
||||
echo "after builtin call (status $?)"
|
||||
|
||||
- name: "Failing special built-in (POSIX mode, interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -o posix
|
||||
. ./non-existent-file
|
||||
echo "after builtin call (status $?)"
|
||||
|
||||
- name: "errexit with failing command (non-interactive)"
|
||||
stdin: |
|
||||
set -e
|
||||
false
|
||||
echo "after false"
|
||||
|
||||
- name: "errexit with failing command (interactive)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -e
|
||||
echo "before false"
|
||||
false
|
||||
echo "after false"
|
||||
|
||||
- name: "errexit with unbound variable access (interactive)"
|
||||
args: ["-i", "-e", "-u", "-o", "pipefail"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "before false"
|
||||
false
|
||||
echo "after false"
|
||||
@@ -0,0 +1,434 @@
|
||||
name: "Extended tests"
|
||||
cases:
|
||||
- name: "Extended test with redirect"
|
||||
stdin: |
|
||||
[[ $(echo hello >&2) ]] 2>/dev/null
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "File extended tests"
|
||||
stdin: |
|
||||
[[ -a /tmp ]] && echo "-a correctly checked /tmp"
|
||||
[[ -a /some/non/existent/path ]] || echo "-a correctly checked non-existent path"
|
||||
|
||||
[[ -c /dev/null ]] && echo "-c correctly checked /dev/null"
|
||||
[[ -c /tmp ]] || echo "-c correctly checked /tmp"
|
||||
|
||||
- name: "Existence tests"
|
||||
stdin: |
|
||||
[[ -e '' ]] || echo "-e correctly identified empty string as non-existent"
|
||||
|
||||
[[ -e non-existent ]] || echo "-e correctly identified non-existent path"
|
||||
[[ ! -e non-existent ]] && echo "! -e correctly identified non-existent path"
|
||||
|
||||
touch test-file
|
||||
[[ -e test-file ]] && echo "-e correctly identified existing non-dir"
|
||||
|
||||
mkdir test-dir
|
||||
[[ -e test-dir ]] && echo "-d correctly identified existing dir"
|
||||
|
||||
- name: "Directory tests"
|
||||
stdin: |
|
||||
[[ -d '' ]] || echo "-d correctly identified empty string as non-existent"
|
||||
|
||||
[[ -d non-existent ]] || echo "-d correctly identified non-existent path"
|
||||
[[ ! -d non-existent ]] && echo "! -d correctly identified non-existent path"
|
||||
|
||||
touch test-file
|
||||
[[ -d test-file ]] || echo "-d correctly identified non-dir file"
|
||||
|
||||
mkdir test-dir
|
||||
[[ -d test-dir ]] && echo "-d correctly identified existing dir"
|
||||
|
||||
- name: "File regular tests"
|
||||
stdin: |
|
||||
[[ -f '' ]] || echo "-f correctly identified empty string as non-existent"
|
||||
|
||||
[[ -f non-existent ]] || echo "-f correctly identified non-existent path"
|
||||
[[ ! -f non-existent ]] && echo "! -f correctly identified non-existent path"
|
||||
|
||||
touch test-file
|
||||
[[ -f test-file ]] && echo "-f correctly identified regular file"
|
||||
|
||||
mkdir test-dir
|
||||
[[ -f test-dir ]] || echo "-f correctly identified directory as non-regular file"
|
||||
|
||||
- name: "File symbolic link tests"
|
||||
stdin: |
|
||||
[[ -L non-existent ]] || echo "-L correctly identified non-existent path"
|
||||
|
||||
touch test-file
|
||||
[[ -L test-file ]] || echo "-L correctly identified non-link file"
|
||||
|
||||
ln -s test-file valid-link
|
||||
[[ -L valid-link ]] && echo "-L correctly identified valid symbolic link"
|
||||
|
||||
ln -s non-existent-target dangling-link
|
||||
[[ -L dangling-link ]] && echo "-L correctly identified dangling symbolic link"
|
||||
|
||||
- name: "File sticky bit tests"
|
||||
stdin: |
|
||||
touch test-file
|
||||
[[ -k test-file ]] || echo "-k correctly identified file without sticky bit"
|
||||
|
||||
chmod 1600 test-file
|
||||
[[ -k test-file ]] && echo "-k correctly identified file with sticky bit"
|
||||
|
||||
- name: "Fifo test"
|
||||
stdin: |
|
||||
touch test-file
|
||||
[[ -p test-file ]] || echo "-p correctly identified non-fifo file"
|
||||
|
||||
mkfifo fifo-file
|
||||
[[ -p test-file ]] && echo "-p correctly identified fifo file"
|
||||
|
||||
- name: "File executable tests"
|
||||
stdin: |
|
||||
[[ -x non-existent ]] || echo "-x correctly identified non-existent path"
|
||||
|
||||
touch test-file
|
||||
ln -sf link test-file
|
||||
[[ -x test-file ]] || echo "-x correctly identified non-executable file"
|
||||
[[ -x link ]] || echo "-x correctly identified link to non-executable file"
|
||||
|
||||
chmod o+x test-file
|
||||
[[ -x test-file ]] || echo "-x correctly identified other-only-executable file"
|
||||
[[ -x link ]] || echo "-x correctly identified link to other-only-executable file"
|
||||
|
||||
chmod a+x test-file
|
||||
[[ -x test-file ]] && echo "-x correctly identified executable file"
|
||||
[[ -x link ]] && echo "-x correctly identified link to executable file"
|
||||
|
||||
- name: "File writable tests"
|
||||
stdin: |
|
||||
[[ -w non-existent ]] || echo "-w correctly identified non-existent path"
|
||||
|
||||
touch test-file
|
||||
ln -sf link test-file
|
||||
[[ -w test-file ]] || echo "-w correctly identified non-writable file"
|
||||
[[ -w link ]] || echo "-w correctly identified link to non-writable file"
|
||||
|
||||
chmod o+x test-file
|
||||
[[ -w test-file ]] || echo "-w correctly identified other-only-writable file"
|
||||
[[ -w link ]] || echo "-w correctly identified link to other-only-writable file"
|
||||
|
||||
chmod a+x test-file
|
||||
[[ -w test-file ]] && echo "-w correctly identified writable file"
|
||||
[[ -w link ]] && echo "-w correctly identified link to writable file"
|
||||
|
||||
- name: "Variable set and nameref tests"
|
||||
stdin: |
|
||||
foo="bar"
|
||||
declare -n bang=foo
|
||||
[[ -R bang ]] && echo "-R correctly identified nameref variable that is set"
|
||||
|
||||
declare -n somevar
|
||||
[[ -R somevar ]] || echo "-R correctly identified nameref variable that isn't set"
|
||||
|
||||
- name: "Files refer to same device and inode tests"
|
||||
stdin: |
|
||||
[[ /bin/sh -ef /bin/sh ]] && echo "-ef correctly identified device and inode numbers"
|
||||
|
||||
[[ ! /etc/os-release -ef /bin/sh ]] && echo "-ef correctly identified device and inode numbers that do not match"
|
||||
|
||||
- name: "File is newer"
|
||||
stdin: |
|
||||
touch -d "2 hours ago" bar
|
||||
touch foo
|
||||
|
||||
[[ foo -nt bar ]] && echo "-nt correctly identified newer file"
|
||||
[[ foo -nt foo ]] && echo "-nt incorrectly identified file as newer than itself"
|
||||
[[ foo -nt file_no_exists ]] && echo "-nt correctly identified when file2 does not exist"
|
||||
|
||||
- name: "File is older"
|
||||
stdin: |
|
||||
touch -d "2 hours ago" foo
|
||||
touch bar
|
||||
|
||||
[[ foo -ot bar ]] && echo "-ot correctly identified older file"
|
||||
[[ foo -ot foo ]] && echo "-ot incorrectly identified file as older than itself"
|
||||
[[ file_no_exists -ot foo ]] && echo "-ot correctly identified when file1 does not exist"
|
||||
|
||||
- name: "Unary string extended tests"
|
||||
stdin: |
|
||||
[[ -z "" ]] && echo "-z: Pass"
|
||||
[[ -z "something" ]] && echo "-z: Fail"
|
||||
|
||||
[[ -n "something" ]] && echo "-n: Pass"
|
||||
[[ -n "" ]] && echo "-n: Fail"
|
||||
|
||||
- name: "Shell option extended tests"
|
||||
stdin: |
|
||||
set -o emacs
|
||||
[[ -o emacs ]] && echo "1: option enabled"
|
||||
|
||||
set +o emacs
|
||||
[[ -o emacs ]] && echo "2: option enabled"
|
||||
|
||||
- name: "Binary string extended tests"
|
||||
stdin: |
|
||||
[[ "" == "" ]] && echo "1. Pass"
|
||||
[[ "" = "" ]] && echo "2. Pass"
|
||||
[[ "" != "" ]] && echo "3. Fail"
|
||||
|
||||
[[ "a" != "b" ]] && echo "4. Pass"
|
||||
[[ "a" = "b" ]] && echo "5. Fail"
|
||||
[[ "a" == "b" ]] && echo "6. Fail"
|
||||
|
||||
[[ "a" < "b" ]] && echo "7. Pass"
|
||||
[[ "a" < "a" ]] && echo "8. Fail"
|
||||
[[ "b" < "a" ]] && echo "9. Fail"
|
||||
[[ "a" > "b" ]] && echo "10. Fail"
|
||||
[[ "a" > "a" ]] && echo "11. Fail"
|
||||
[[ "b" > "a" ]] && echo "12. Pass"
|
||||
|
||||
- name: "Binary string matching"
|
||||
stdin: |
|
||||
[[ "abc" == a* ]] && echo "1. Pass"
|
||||
[[ "abc" != a* ]] && echo "2. Fail"
|
||||
[[ a* != "abc" ]] && echo "3. Pass"
|
||||
|
||||
- name: "Binary string matching with expansion"
|
||||
stdin: |
|
||||
exclude="0123456789"
|
||||
[[ "clue" == +([$exclude]) ]] && echo "1. Fail"
|
||||
[[ "8675309" == +([$exclude]) ]] && echo "2. Pass"
|
||||
|
||||
- name: "Quoted pattern binary string matching"
|
||||
stdin: |
|
||||
[[ "abc" == "a*" ]] && echo "1. Matches"
|
||||
[[ "abc" != "a*" ]] && echo "2. Matches"
|
||||
|
||||
- name: "Tilde binary string matching"
|
||||
stdin: |
|
||||
x='~/'
|
||||
[[ $x == ~* ]] && echo "1. Matches"
|
||||
|
||||
- name: "Arithmetic extended tests"
|
||||
stdin: |
|
||||
[[ 0 -eq 0 ]] && echo "1. Pass"
|
||||
[[ 0 -ne 0 ]] && echo "2. Fail"
|
||||
[[ 0 -lt 0 ]] && echo "3. Fail"
|
||||
[[ 0 -le 0 ]] && echo "4. Pass"
|
||||
[[ 0 -gt 0 ]] && echo "5. Fail"
|
||||
[[ 0 -ge 0 ]] && echo "6. Pass"
|
||||
|
||||
[[ 0 -eq 1 ]] && echo "7. Fail"
|
||||
[[ 0 -ne 1 ]] && echo "8. Pass"
|
||||
[[ 0 -lt 1 ]] && echo "9. Pass"
|
||||
[[ 0 -le 1 ]] && echo "10. Pass"
|
||||
[[ 0 -gt 1 ]] && echo "11. Fail"
|
||||
[[ 0 -ge 1 ]] && echo "12. Fail"
|
||||
|
||||
[[ 1 -eq 0 ]] && echo "13. Fail"
|
||||
[[ 1 -ne 0 ]] && echo "14. Pass"
|
||||
[[ 1 -lt 0 ]] && echo "15. Fail"
|
||||
[[ 1 -le 0 ]] && echo "16. Fail"
|
||||
[[ 1 -gt 0 ]] && echo "17. Pass"
|
||||
[[ 1 -ge 0 ]] && echo "18. Pass"
|
||||
|
||||
- name: "Regex"
|
||||
stdin: |
|
||||
[[ "a" =~ ^a$ ]] && echo "1. Pass"
|
||||
[[ "abc" =~ a* ]] && echo "2. Pass"
|
||||
[[ a =~ ^(a)$ ]] && echo "3. Pass"
|
||||
[[ a =~ ^(a|b)$ ]] && echo "4. Pass"
|
||||
[[ a =~ c ]] && echo "5. Pass"
|
||||
|
||||
- name: "Regex with case insensitivity"
|
||||
stdin: |
|
||||
shopt -u nocasematch
|
||||
[[ "a" =~ A ]] && echo "1. Pass"
|
||||
|
||||
shopt -s nocasematch
|
||||
[[ "a" =~ A ]] && echo "1. Pass"
|
||||
|
||||
- name: "Regex with capture"
|
||||
stdin: |
|
||||
pattern='(Hello), ([a-z]+)\.'
|
||||
if [[ "Hello, world." =~ ${pattern} ]]; then
|
||||
echo "Match found!"
|
||||
for i in "${!BASH_REMATCH[@]}"; do
|
||||
echo "$i: '${BASH_REMATCH[$i]}'"
|
||||
done
|
||||
fi
|
||||
|
||||
- name: "Regex with capture including optional matches"
|
||||
stdin: |
|
||||
pattern='(Hello)(,?) ([a-z]+)\.'
|
||||
if [[ "Hello world." =~ ${pattern} ]]; then
|
||||
echo "Match found!"
|
||||
for i in "${!BASH_REMATCH[@]}"; do
|
||||
echo "$i: '${BASH_REMATCH[$i]}'"
|
||||
done
|
||||
fi
|
||||
|
||||
- name: "Regex with quoting"
|
||||
stdin: |
|
||||
regex="^$"
|
||||
|
||||
# TODO(test): The commented out lines appear differ in behavior between versions of bash.
|
||||
# [[ "" =~ '' ]] && echo "1. Matched"
|
||||
[[ "" =~ '^$' ]] && echo "2. Matched"
|
||||
# [[ "" =~ "" ]] && echo "3. Matched"
|
||||
[[ "" =~ "^$" ]] && echo "4. Matched"
|
||||
[[ "" =~ ^$ ]] && echo "5. Matched"
|
||||
[[ "" =~ ${regex} ]] && echo "6. Matched"
|
||||
[[ "" =~ "${regex}" ]] && echo "7. Matched"
|
||||
|
||||
- name: "Regex with escaping"
|
||||
stdin: |
|
||||
[[ '' =~ ^\$$ ]] && echo "1. Matched"
|
||||
|
||||
- name: "Regex with double parens"
|
||||
stdin: |
|
||||
if [[ xy =~ x+((y)) ]]; then
|
||||
echo "Matches"
|
||||
fi
|
||||
|
||||
- name: "Regex with special chars in parens"
|
||||
stdin: |
|
||||
[[ "<" =~ (<) ]] && echo "1. Matched"
|
||||
[[ ">" =~ (<) ]] && echo "2. Matched"
|
||||
|
||||
- name: "Regex with spaces inside parens"
|
||||
stdin: |
|
||||
[[ "x" =~ (a| *) ]] && echo "1. Matched"
|
||||
[[ "a b" =~ ^(a |b) ]] && echo "2. Matched: [${BASH_REMATCH[0]}]"
|
||||
[[ "a b" =~ ^(a |b)$ ]] || echo "3. No match"
|
||||
[[ " " =~ ^( )$ ]] && echo "4. Matched: [${BASH_REMATCH[1]}]"
|
||||
|
||||
- name: "Regex with unescaped open bracket in character class"
|
||||
stdin: |
|
||||
[[ "[" =~ ^([x[]) ]] && echo "Matched"
|
||||
|
||||
- name: "Empty and space checks"
|
||||
stdin: |
|
||||
check() {
|
||||
var="$1"
|
||||
[[ ${var} && ! ${var//[[:space:]]/} ]]
|
||||
}
|
||||
|
||||
check "" && echo "1. Only space"
|
||||
check " " && echo "2. Only space"
|
||||
check $'\t' && echo "3. Only space"
|
||||
check " a " && echo "4. Only space"
|
||||
|
||||
- name: "Newlines in test expression"
|
||||
stdin: |
|
||||
[[
|
||||
"a" == "a"
|
||||
&&
|
||||
"b" == "b"
|
||||
]] && echo "Succeeded"
|
||||
|
||||
- name: "Variable set checks"
|
||||
stdin: |
|
||||
declare set_but_no_value
|
||||
[[ -v set_but_no_value ]] && echo "1. Set but no value"
|
||||
|
||||
declare set_with_value=xyz
|
||||
[[ -v set_with_value ]] && echo "2. Set with value"
|
||||
|
||||
[[ -v not_set ]] || echo "3. Not set"
|
||||
|
||||
- name: "Variables in extended tests"
|
||||
stdin: |
|
||||
var=10
|
||||
|
||||
[[ $var -eq 10 ]] && echo "1. Pass"
|
||||
[[ var -eq 10 ]] && echo "2. Pass"
|
||||
|
||||
- name: "Regex with min/max counts"
|
||||
stdin: |
|
||||
[[ z =~ ^z{2,6}$ ]] && echo "1. Matches"
|
||||
[[ zzzz =~ ^z{2,6}$ ]] && echo "2. Matches"
|
||||
[[ zzzzzzzzz =~ ^z{2,6}$ ]] && echo "3. Matches"
|
||||
|
||||
- name: "Regex with newline"
|
||||
stdin: |
|
||||
[[ $'\n' =~ . ]] && echo "1. Matches"
|
||||
|
||||
- name: "Arithmetic comparisons with whitespace in operands"
|
||||
stdin: |
|
||||
# Leading/trailing whitespace should be trimmed before comparison
|
||||
[[ 0 -eq ' 0' ]] && echo "1. leading space"
|
||||
[[ 0 -eq '0 ' ]] && echo "2. trailing space"
|
||||
[[ 0 -eq ' 0 ' ]] && echo "3. both spaces"
|
||||
|
||||
# Tabs
|
||||
[[ 0 -eq ' 0' ]] && echo "4. leading tab"
|
||||
[[ 0 -eq '0 ' ]] && echo "5. trailing tab"
|
||||
|
||||
# Newlines
|
||||
[[ 0 -eq ' 0
|
||||
' ]] && echo "6. newline"
|
||||
|
||||
# Other operators
|
||||
[[ 5 -lt ' 10' ]] && echo "7. -lt with space"
|
||||
[[ 10 -gt ' 5' ]] && echo "8. -gt with space"
|
||||
[[ 5 -ne ' 6' ]] && echo "9. -ne with space"
|
||||
[[ 5 -le ' 5' ]] && echo "10. -le with space"
|
||||
[[ 5 -ge ' 5' ]] && echo "11. -ge with space"
|
||||
|
||||
# Negative numbers with whitespace
|
||||
[[ -5 -eq ' -5' ]] && echo "12. negative with space"
|
||||
|
||||
- name: "Terminal fd check with whitespace"
|
||||
stdin: |
|
||||
# -t operator should trim whitespace before parsing fd number
|
||||
# These should all behave identically (false since not a terminal)
|
||||
[[ -t 0 ]]
|
||||
r1=$?
|
||||
[[ -t ' 0' ]]
|
||||
r2=$?
|
||||
[[ -t '0 ' ]]
|
||||
r3=$?
|
||||
|
||||
# All should have the same result
|
||||
[[ $r1 -eq $r2 && $r2 -eq $r3 ]] && echo "All -t results match"
|
||||
|
||||
- name: "Nameref -R with attribute removal"
|
||||
stdin: |
|
||||
target="value"
|
||||
declare -n ref=target
|
||||
[[ -R ref ]] && echo "ref is a nameref"
|
||||
[[ -R target ]] && echo "target is a nameref" || echo "target is not a nameref"
|
||||
declare +n ref
|
||||
[[ -R ref ]] && echo "still nameref" || echo "no longer a nameref"
|
||||
|
||||
- name: "Nameref -v follows nameref"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
target="value"
|
||||
declare -n ref=target
|
||||
[[ -v ref ]] && echo "ref is set (via target)"
|
||||
unset target
|
||||
[[ -v ref ]] && echo "ref still set" || echo "ref not set (target gone)"
|
||||
|
||||
- name: "Nameref -v to array element treats resolved name literally"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
arr=(a b c d)
|
||||
declare -n ref='arr[2]'
|
||||
[[ -v ref ]] && echo "set" || echo "unset"
|
||||
|
||||
- name: "Nameref -v with explicit subscript on nameref to whole array"
|
||||
# TODO(nameref): brush doesn't yet handle [[ -v "ref[N]" ]] where ref is
|
||||
# a nameref to a whole array. Bash resolves ref→arr and then checks arr[2],
|
||||
# but brush currently treats the subscript as part of the nameref target
|
||||
# string rather than applying it after resolution. The fix belongs in
|
||||
# extendedtests.rs (ShellVariableIsSetAndAssigned) — the operand "ref[2]"
|
||||
# needs to be split into name="ref" + subscript="2", the nameref resolved
|
||||
# on the name portion, then the subscript applied to the resolved target.
|
||||
known_failure: true
|
||||
stdin: |
|
||||
arr=(a b c d)
|
||||
declare -n ref2=arr
|
||||
[[ -v "ref2[2]" ]] && echo "set" || echo "unset"
|
||||
|
||||
- name: "Nameref -v to nonexistent array index"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
arr=(a b c)
|
||||
declare -n ref='arr[5]'
|
||||
[[ -v ref ]] && echo "set" || echo "unset"
|
||||
@@ -0,0 +1,177 @@
|
||||
name: "Functions"
|
||||
cases:
|
||||
- name: "Basic function invocation"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
myfunc() {
|
||||
echo "In myfunc()."
|
||||
}
|
||||
echo "Calling myfunc()..."
|
||||
myfunc
|
||||
echo "Returned."
|
||||
args: ["./script.sh"]
|
||||
|
||||
- name: "Function invocation with args"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
myfunc() {
|
||||
echo "In myfunc()"
|
||||
echo "1: $1"
|
||||
echo "*: $*"
|
||||
}
|
||||
echo "Calling myfunc()..."
|
||||
myfunc a b c
|
||||
echo "Returned."
|
||||
args: ["./script.sh"]
|
||||
|
||||
- name: "Function invocation with empty arg"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
echo "count: ${#*}"
|
||||
echo "\$1: $1"
|
||||
echo "\$2: $2"
|
||||
echo "\$3: $3"
|
||||
}
|
||||
|
||||
myfunc a b c
|
||||
myfunc a "" c
|
||||
|
||||
- name: "Function definition with output redirection"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
echo "In myfunc()"
|
||||
} >>./test.txt
|
||||
|
||||
myfunc
|
||||
myfunc
|
||||
|
||||
- name: "Function call with env variables"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
echo ${myvar}
|
||||
}
|
||||
|
||||
myvar="default"
|
||||
myfunc
|
||||
myvar="overridden" myfunc
|
||||
myfunc
|
||||
|
||||
- name: "Function definition without braces"
|
||||
stdin: |
|
||||
myfunc()
|
||||
if true; then
|
||||
echo true
|
||||
else
|
||||
echo false
|
||||
fi
|
||||
|
||||
myfunc
|
||||
|
||||
- name: "Nested function definition"
|
||||
stdin: |
|
||||
outer() {
|
||||
echo "Entered outer"
|
||||
|
||||
inner() {
|
||||
echo "In inner"
|
||||
}
|
||||
|
||||
echo "Invoking inner"
|
||||
|
||||
inner
|
||||
|
||||
echo "Returning from outer"
|
||||
}
|
||||
|
||||
echo "Calling outer from toplevel"
|
||||
outer
|
||||
|
||||
echo "Calling inner from toplevel"
|
||||
inner
|
||||
|
||||
- name: "Exporting functions to child instance"
|
||||
stdin: |
|
||||
mytestfunc() {
|
||||
echo "In mytestfunc"
|
||||
}
|
||||
|
||||
export -f mytestfunc
|
||||
|
||||
$0 -c 'mytestfunc'
|
||||
|
||||
- name: "Function names with interesting characters"
|
||||
stdin: |
|
||||
my/func() {
|
||||
echo "In my/func"
|
||||
}
|
||||
|
||||
my/func
|
||||
|
||||
- name: "Functions shadowing builtins"
|
||||
stdin: |
|
||||
.() {
|
||||
echo "In custom dot function"
|
||||
}
|
||||
|
||||
echo "Func result: $?"
|
||||
|
||||
.
|
||||
echo "Call result: $?"
|
||||
|
||||
- name: "Reserved words cannot be used as function names without function keyword"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
for (){ :; }
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "Reserved words can be used as function names with function keyword"
|
||||
stdin: |
|
||||
function for { echo "custom for"; }
|
||||
for x in 1; do echo "normal for: $x"; done
|
||||
|
||||
- name: "Reserved words can be used as function names with function keyword and parens"
|
||||
stdin: |
|
||||
function while() { echo "custom while"; }
|
||||
while false; do echo "nope"; done
|
||||
echo "done"
|
||||
|
||||
- name: "Functions shadowing builtins (POSIX mode)"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -o posix
|
||||
|
||||
.() {
|
||||
echo "In custom dot function"
|
||||
}
|
||||
|
||||
echo "Func result: $?"
|
||||
|
||||
.
|
||||
echo "Call result: $?"
|
||||
|
||||
- name: "Empty string arguments preserved with nullglob"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
|
||||
myfunc() {
|
||||
echo "arg count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "arg: '$arg'"
|
||||
done
|
||||
}
|
||||
|
||||
myfunc 3 - 2 "" 1.2.3b_alpha4
|
||||
|
||||
- name: "Multiple empty string arguments"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
echo "count: $#"
|
||||
echo "1: '$1'"
|
||||
echo "2: '$2'"
|
||||
echo "3: '$3'"
|
||||
echo "4: '$4'"
|
||||
}
|
||||
|
||||
myfunc "" "non-empty" "" "last"
|
||||
@@ -0,0 +1,283 @@
|
||||
name: "Here docs/strings"
|
||||
cases:
|
||||
- name: "Basic here doc"
|
||||
stdin: |
|
||||
cat <<END-MARKER
|
||||
Something here...
|
||||
...and here.
|
||||
END-MARKER
|
||||
echo "This is after"
|
||||
|
||||
- name: "Basic here doc in a script"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
cat <<END-MARKER
|
||||
Something here...
|
||||
...and here.
|
||||
END-MARKER
|
||||
echo "This is after"
|
||||
args: ["./script.sh"]
|
||||
|
||||
- name: "Here doc in a function"
|
||||
stdin: |
|
||||
function myfunc() {
|
||||
if true
|
||||
then
|
||||
cat << END-MARKER
|
||||
Something here...
|
||||
END-MARKER
|
||||
fi
|
||||
}
|
||||
|
||||
myfunc
|
||||
|
||||
- name: "Here doc with expansions"
|
||||
stdin: |
|
||||
cat <<END-MARKER
|
||||
Something here...
|
||||
...and here.
|
||||
$(echo "This is after")
|
||||
END-MARKER
|
||||
|
||||
- name: "Here doc with expansions but quoted tag"
|
||||
stdin: |
|
||||
cat <<"END-MARKER"
|
||||
Something here...
|
||||
...and here.
|
||||
$(echo "This is after")
|
||||
END-MARKER
|
||||
|
||||
- name: "Here doc with tab removal"
|
||||
stdin: |
|
||||
cat <<-END-MARKER
|
||||
Something here...
|
||||
...and here.
|
||||
END-MARKER
|
||||
|
||||
- name: "Basic here string"
|
||||
stdin: |
|
||||
shopt -ou posix
|
||||
cat <<<"Something here."
|
||||
wc -l <<<"Something"
|
||||
|
||||
- name: "Empty here doc"
|
||||
stdin: |
|
||||
wc -l <<EOF
|
||||
EOF
|
||||
|
||||
- name: "Basic here doc"
|
||||
stdin: "wc -l <<EOF\nSomething\nEOF"
|
||||
|
||||
- name: "Here doc with other tokens after tag"
|
||||
stdin: |
|
||||
wc -l <<EOF | wc -l
|
||||
A B C
|
||||
1 2 3
|
||||
EOF
|
||||
|
||||
- name: "Multiple here docs"
|
||||
stdin: |
|
||||
cat <<EOF1 <<EOF2
|
||||
A B C
|
||||
EOF1
|
||||
1 2 3
|
||||
EOF2
|
||||
|
||||
- name: "Here doc in a command substitution"
|
||||
stdin: |
|
||||
test1=$(cat <<EOF
|
||||
1
|
||||
2 3
|
||||
4 5 6
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "${test1}"
|
||||
|
||||
- name: "Here doc in a double-quoted command substitution"
|
||||
stdin: |
|
||||
test1="$(cat <<EOF
|
||||
something
|
||||
EOF
|
||||
)"
|
||||
|
||||
echo "${test1}"
|
||||
|
||||
- name: "Here doc in a double-quoted command substitution with spaces"
|
||||
stdin: |
|
||||
test1="$(cat << EOF
|
||||
something
|
||||
EOF
|
||||
)"
|
||||
|
||||
echo "${test1}"
|
||||
|
||||
- name: "Here doc in a command substitution with quotes"
|
||||
stdin: |
|
||||
test1=$(cat <<EOF
|
||||
something "quoted"
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "${test1}"
|
||||
|
||||
- name: "Here doc with parameter default containing quotes"
|
||||
stdin: |
|
||||
cat <<EOF
|
||||
${unset_var:-"default value"}
|
||||
EOF
|
||||
|
||||
- name: "Here doc preserves literal double quotes"
|
||||
stdin: |
|
||||
cat <<EOF
|
||||
"double quoted"
|
||||
she said "hello"
|
||||
EOF
|
||||
|
||||
- name: "Here doc preserves literal single quotes"
|
||||
stdin: |
|
||||
cat <<EOF
|
||||
'single quoted'
|
||||
it's a test
|
||||
EOF
|
||||
|
||||
- name: "Here doc with mixed quotes and expansion"
|
||||
stdin: |
|
||||
x=world
|
||||
cat <<EOF
|
||||
"hello $x"
|
||||
"$x" and more
|
||||
EOF
|
||||
|
||||
- name: "Here doc does not perform brace expansion"
|
||||
stdin: |
|
||||
cat <<EOF
|
||||
{a,b,c}
|
||||
{1..3}
|
||||
EOF
|
||||
|
||||
- name: "Here doc does not perform tilde expansion"
|
||||
stdin: |
|
||||
cat <<EOF
|
||||
~
|
||||
~/path
|
||||
EOF
|
||||
|
||||
- name: "Here doc escape sequences"
|
||||
stdin: |
|
||||
cat <<EOF
|
||||
\$HOME
|
||||
\\backslash
|
||||
\n not a newline
|
||||
\"not unquoted\"
|
||||
\'not unquoted\'
|
||||
EOF
|
||||
|
||||
- name: "Here doc with command substitution containing quotes"
|
||||
stdin: |
|
||||
cat <<EOF
|
||||
$(echo "hello world")
|
||||
$(echo 'single')
|
||||
EOF
|
||||
|
||||
- name: "Here doc with arithmetic expansion"
|
||||
stdin: |
|
||||
cat <<EOF
|
||||
$((2 + 3))
|
||||
$((10 * 2))
|
||||
EOF
|
||||
|
||||
- name: "Here doc in a command substitution with parentheses"
|
||||
stdin: |
|
||||
test1=$(cat <<EOF
|
||||
(something)
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "${test1}"
|
||||
|
||||
- name: "Here doc with parentheses"
|
||||
stdin: |
|
||||
cat <<EOF
|
||||
(something)
|
||||
EOF
|
||||
|
||||
- name: "Multiple here docs in a command substitution"
|
||||
stdin: |
|
||||
test1=$(cat <<EOF1 <<EOF2
|
||||
A B C
|
||||
EOF1
|
||||
D E F
|
||||
EOF2
|
||||
)
|
||||
|
||||
echo "${test1}"
|
||||
|
||||
- name: "Complex here docs in a command substitution"
|
||||
stdin: |
|
||||
test1=$(cat <<EOF1 <<EOF2 | wc -l
|
||||
A B C
|
||||
EOF1
|
||||
D E F
|
||||
EOF2
|
||||
)
|
||||
|
||||
echo "${test1}"
|
||||
|
||||
- name: "Here doc with quoted delimiter"
|
||||
stdin: |
|
||||
cat <<'EOF'
|
||||
something
|
||||
EOF
|
||||
|
||||
- name: "Here doc with double quoted delimiter"
|
||||
stdin: |
|
||||
cat <<"EOF"
|
||||
something
|
||||
EOF
|
||||
|
||||
- name: "Here doc with quoted delimiter inside command substitution"
|
||||
stdin: |
|
||||
$(cat <<'EOF'
|
||||
true
|
||||
EOF
|
||||
)
|
||||
|
||||
- name: "Here doc with double quoted delimiter inside command substitution"
|
||||
stdin: |
|
||||
$(cat <<"EOF"
|
||||
true
|
||||
EOF
|
||||
)
|
||||
|
||||
- name: "Here doc with unpaired single quote in command substitution"
|
||||
stdin: |
|
||||
echo "$(cat <<'EOF'
|
||||
it's here
|
||||
EOF
|
||||
)"
|
||||
|
||||
- name: "Here doc with unpaired backtick in command substitution"
|
||||
stdin: |
|
||||
echo "$(cat <<'EOF'
|
||||
a ` b
|
||||
EOF
|
||||
)"
|
||||
|
||||
- name: "Here doc with three single quotes in command substitution"
|
||||
stdin: |
|
||||
echo "$(cat <<'EOF'
|
||||
it's Bob's it's
|
||||
EOF
|
||||
)"
|
||||
|
||||
- name: "Here doc with unpaired single quote in command substitution (#420)"
|
||||
stdin: |
|
||||
var=$(
|
||||
sed 's/nice/cool/' - <<-EOF
|
||||
that's
|
||||
"nice"
|
||||
EOF
|
||||
)
|
||||
echo $var
|
||||
@@ -0,0 +1,405 @@
|
||||
name: "IFS"
|
||||
cases:
|
||||
- name: "Default IFS value"
|
||||
stdin: |
|
||||
# Default IFS is space, tab, newline
|
||||
[[ "$IFS" == $' \t\n' ]] && echo "IFS has default value"
|
||||
|
||||
- name: "Field splitting with default IFS"
|
||||
stdin: |
|
||||
var="one two three
|
||||
four"
|
||||
echo "Fields:" $var
|
||||
|
||||
- name: "Field splitting with custom single-char IFS"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
var="one:two:three"
|
||||
echo $var
|
||||
|
||||
- name: "Field splitting with multi-char IFS"
|
||||
stdin: |
|
||||
IFS=':,'
|
||||
var="one:two,three:four"
|
||||
echo $var
|
||||
|
||||
- name: "Empty IFS disables field splitting"
|
||||
stdin: |
|
||||
IFS=''
|
||||
var="one two three"
|
||||
echo "[$var]"
|
||||
# Should print with all whitespace preserved
|
||||
|
||||
- name: "Empty IFS with expansion"
|
||||
stdin: |
|
||||
IFS=''
|
||||
var="a b c"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "[$1]"
|
||||
|
||||
- name: "Unset IFS uses default splitting"
|
||||
stdin: |
|
||||
unset IFS
|
||||
var="one two three"
|
||||
echo $var
|
||||
|
||||
- name: "IFS whitespace vs non-whitespace splitting"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
# Non-whitespace IFS chars create empty fields
|
||||
IFS=':'
|
||||
var=":one::two:"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "Args: $@"
|
||||
|
||||
- name: "IFS whitespace splitting ignores leading/trailing"
|
||||
stdin: |
|
||||
# Whitespace IFS chars ignore leading/trailing
|
||||
IFS=' '
|
||||
var=" one two "
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "Args: $@"
|
||||
|
||||
- name: "Mixed whitespace and non-whitespace IFS"
|
||||
stdin: |
|
||||
IFS=' :'
|
||||
var="one:two three : four"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "[$arg]"
|
||||
done
|
||||
|
||||
- name: "Mixed IFS leading non-whitespace"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
IFS=': '
|
||||
var=":one: :two"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "[$arg]"
|
||||
done
|
||||
|
||||
- name: "Mixed IFS trailing non-whitespace"
|
||||
stdin: |
|
||||
IFS=' :'
|
||||
var="one :two: "
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "[$arg]"
|
||||
done
|
||||
|
||||
- name: "IFS only whitespace multiple chars"
|
||||
stdin: |
|
||||
IFS=$' \t'
|
||||
var=" one two three "
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "Args: $@"
|
||||
|
||||
- name: "IFS only non-whitespace multiple chars"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
IFS=':,'
|
||||
var=":,one,:,two,:"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "[$arg]"
|
||||
done
|
||||
|
||||
- name: "IFS only tab character"
|
||||
stdin: |
|
||||
IFS=$'\t'
|
||||
var=" a b "
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "Args: $@"
|
||||
|
||||
- name: "IFS only newline character"
|
||||
stdin: |
|
||||
IFS=$'\n'
|
||||
var="
|
||||
|
||||
a
|
||||
|
||||
b
|
||||
|
||||
"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "Args: $@"
|
||||
|
||||
- name: "IFS affects command substitution"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
result=$(echo "a:b:c")
|
||||
# Command substitution itself doesn't split, but expansion does
|
||||
set -- $result
|
||||
echo "$#"
|
||||
echo "$@"
|
||||
|
||||
- name: "IFS with command substitution multiline"
|
||||
stdin: |
|
||||
IFS=$'\n'
|
||||
result=$(printf "a\nb\nc")
|
||||
set -- $result
|
||||
echo "Count: $#"
|
||||
echo "$1|$2|$3"
|
||||
|
||||
- name: "IFS affects arithmetic expansion"
|
||||
stdin: |
|
||||
IFS=' '
|
||||
# Arithmetic expansion result is splittable
|
||||
set -- $((1 + 1)) $((2 + 2))
|
||||
echo "Count: $#"
|
||||
echo "$@"
|
||||
|
||||
- name: "IFS in array expansion"
|
||||
stdin: |
|
||||
arr=(one two three)
|
||||
IFS=':'
|
||||
echo "${arr[*]}"
|
||||
# With $* should join with first char of IFS
|
||||
|
||||
- name: "IFS with $* vs $@"
|
||||
stdin: |
|
||||
set -- one two three
|
||||
IFS=':'
|
||||
echo "$*"
|
||||
echo "$@"
|
||||
|
||||
- name: "IFS with $* unquoted"
|
||||
stdin: |
|
||||
set -- "a:b" "c:d"
|
||||
IFS=':'
|
||||
# Unquoted $* joins then splits
|
||||
set -- $*
|
||||
echo "Count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "[$arg]"
|
||||
done
|
||||
|
||||
- name: "IFS null char handling"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
var=""
|
||||
set -- $var
|
||||
echo "Empty var count: $#"
|
||||
|
||||
- name: "IFS preservation across commands"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
echo "First: $IFS"
|
||||
var="a:b"
|
||||
echo $var
|
||||
echo "After: $IFS"
|
||||
|
||||
- name: "IFS in subshell"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
(
|
||||
echo "In subshell: [$IFS]"
|
||||
var="a:b:c"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
)
|
||||
echo "Parent: [$IFS]"
|
||||
|
||||
- name: "IFS local to command"
|
||||
stdin: |
|
||||
var="a:b:c"
|
||||
IFS=':' echo $var
|
||||
# IFS assignment should only affect that command
|
||||
echo "After: [$IFS]"
|
||||
|
||||
- name: "IFS does not affect for loop literal words"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
IFS=':'
|
||||
for word in a:b:c; do
|
||||
echo "[$word]"
|
||||
done
|
||||
|
||||
- name: "IFS affects for loop variable expansion"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
var="a:b:c"
|
||||
for word in $var; do
|
||||
echo "[$word]"
|
||||
done
|
||||
|
||||
- name: "IFS affects for loop command substitution"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
for word in $(echo "x:y:z"); do
|
||||
echo "[$word]"
|
||||
done
|
||||
|
||||
- name: "IFS with quoted vs unquoted expansion"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
var="a:b:c"
|
||||
echo "Unquoted:" $var
|
||||
echo "Quoted: $var"
|
||||
|
||||
- name: "IFS with partially quoted expansion"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
IFS=':'
|
||||
var="a:b:c"
|
||||
set -- "$var":more
|
||||
echo "Count: $#"
|
||||
echo "$@"
|
||||
|
||||
- name: "IFS whitespace sequence handling"
|
||||
stdin: |
|
||||
IFS=' '
|
||||
var="a b"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "Args: $@"
|
||||
|
||||
- name: "IFS non-whitespace sequence handling"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
IFS=':'
|
||||
var="a::::b"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "[$arg]"
|
||||
done
|
||||
|
||||
- name: "IFS with parameter expansion"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
var="a:b:c"
|
||||
result=${var}
|
||||
set -- $result
|
||||
echo "$#"
|
||||
echo "$@"
|
||||
|
||||
- name: "IFS with parameter expansion default value"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
set -- ${unset:-a:b:c}
|
||||
echo "Count: $#"
|
||||
echo "$@"
|
||||
|
||||
- name: "IFS with parameter expansion empty default"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
set -- ${unset:-}
|
||||
echo "Count: $#"
|
||||
|
||||
- name: "IFS with parameter length"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
var="a:b:c"
|
||||
# Length expansion returns a number, not subject to further splitting
|
||||
len=${#var}
|
||||
set -- $len
|
||||
echo "Count: $#"
|
||||
echo "[$1]"
|
||||
|
||||
- name: "IFS with glob pattern literal"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
IFS=':'
|
||||
# Glob patterns as literals shouldn't split
|
||||
for word in *.txt:*.sh; do
|
||||
echo "[$word]"
|
||||
done
|
||||
|
||||
- name: "IFS newline handling"
|
||||
stdin: |
|
||||
IFS=$'\n'
|
||||
var="a
|
||||
b
|
||||
c"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "$1|$2|$3"
|
||||
|
||||
- name: "IFS tab handling"
|
||||
stdin: |
|
||||
IFS=$'\t'
|
||||
var="a b c"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "$1|$2|$3"
|
||||
|
||||
- name: "IFS with backslash in value"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
var='a\:b:c\:d'
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "[$arg]"
|
||||
done
|
||||
|
||||
- name: "IFS first char for joining"
|
||||
stdin: |
|
||||
set -- a b c
|
||||
IFS=':'
|
||||
# First char of IFS used for $* joining
|
||||
result="$*"
|
||||
echo "[$result]"
|
||||
|
||||
- name: "IFS first char empty IFS"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
set -- a b c
|
||||
IFS=''
|
||||
result="$*"
|
||||
echo "[$result]"
|
||||
|
||||
- name: "IFS multiple whitespace at start"
|
||||
stdin: |
|
||||
IFS=' '
|
||||
var=" a b"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "$@"
|
||||
|
||||
- name: "IFS multiple whitespace at end"
|
||||
stdin: |
|
||||
IFS=' '
|
||||
var="a b "
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
echo "$@"
|
||||
|
||||
- name: "IFS with read builtin integration"
|
||||
stdin: |
|
||||
IFS=':'
|
||||
echo "a:b:c" | (read x y z; echo "x=[$x] y=[$y] z=[$z]")
|
||||
|
||||
- name: "IFS whitespace before non-whitespace"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
IFS=' :'
|
||||
var=" :a"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "[$arg]"
|
||||
done
|
||||
|
||||
- name: "IFS non-whitespace before whitespace"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
IFS=': '
|
||||
var=": a"
|
||||
set -- $var
|
||||
echo "Count: $#"
|
||||
for arg in "$@"; do
|
||||
echo "[$arg]"
|
||||
done
|
||||
@@ -0,0 +1,48 @@
|
||||
name: "Interactive"
|
||||
incompatible_configs: ["sh"]
|
||||
cases:
|
||||
- name: "Basic interactive test"
|
||||
pty: true
|
||||
ignore_stdout: true # Needs investigation
|
||||
ignore_whitespace: true # There's an extra newline for now in test output
|
||||
stdin: |
|
||||
#expect-prompt
|
||||
echo hi
|
||||
#send:Enter
|
||||
#expect:hi
|
||||
#expect-prompt
|
||||
#send:Ctrl+D
|
||||
|
||||
- name: "Simple tab completion"
|
||||
skip: true # TODO(input): Need to find the right way to enable this with a rich input backend.
|
||||
pty: true
|
||||
ignore_stdout: true
|
||||
test_files:
|
||||
- path: "test-file.txt"
|
||||
contents: |
|
||||
Hello, world.
|
||||
stdin: |
|
||||
#expect-prompt
|
||||
cat test-file.
|
||||
#send:Tab
|
||||
#send:Enter
|
||||
#expect:Hello, world.
|
||||
#expect-prompt
|
||||
#send:Ctrl+D
|
||||
|
||||
- name: "PROMPT_COMMAND"
|
||||
ignore_stderr: true
|
||||
args: ["-i"]
|
||||
stdin: |
|
||||
declare -a calls=()
|
||||
command=0
|
||||
|
||||
PROMPT_COMMAND='calls+=("$command")'
|
||||
|
||||
command=first
|
||||
command=second
|
||||
command=third
|
||||
|
||||
unset PROMPT_COMMAND
|
||||
|
||||
declare -p calls
|
||||
@@ -0,0 +1,29 @@
|
||||
name: "List"
|
||||
cases:
|
||||
- name: "Ignore single quote in comment in list"
|
||||
stdin: |
|
||||
(
|
||||
# I'm
|
||||
echo "Batman"
|
||||
)
|
||||
|
||||
- name: "Ignore double quote in comment in list"
|
||||
stdin: |
|
||||
(
|
||||
# This " is not being
|
||||
echo "parsed"
|
||||
)
|
||||
|
||||
- name: "Ignore parentheses in comment in list"
|
||||
stdin: |
|
||||
(
|
||||
# :(
|
||||
echo "Sad"
|
||||
)
|
||||
|
||||
- name: "Ignore dollar in comment in list"
|
||||
stdin: |
|
||||
(
|
||||
# $
|
||||
echo "Mr. Crabs ^"
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
name: "BASH_XTRACEFD tests"
|
||||
cases:
|
||||
- name: "basic redirection to fd 3"
|
||||
stdin: |
|
||||
exec 3>trace.txt
|
||||
BASH_XTRACEFD=3
|
||||
|
||||
set -x
|
||||
echo "hello"
|
||||
echo "world"
|
||||
|
||||
set +x
|
||||
cat trace.txt
|
||||
|
||||
- name: "redirection takes effect immediately"
|
||||
stdin: |
|
||||
exec 3>trace.txt
|
||||
|
||||
set -x
|
||||
echo "before setting BASH_XTRACEFD"
|
||||
|
||||
BASH_XTRACEFD=3
|
||||
echo "after setting BASH_XTRACEFD"
|
||||
|
||||
set +x
|
||||
|
||||
echo "Content of trace file:"
|
||||
cat trace.txt
|
||||
|
||||
- name: "unsetting BASH_XTRACEFD returns to stderr"
|
||||
stdin: |
|
||||
exec 3>trace.txt
|
||||
BASH_XTRACEFD=3
|
||||
|
||||
set -x
|
||||
echo "redirected"
|
||||
|
||||
unset BASH_XTRACEFD
|
||||
echo "back to stderr"
|
||||
|
||||
set +x
|
||||
|
||||
echo "Content of trace file:"
|
||||
cat trace.txt
|
||||
|
||||
- name: "invalid fd value falls back to stderr"
|
||||
known_failure: true # bash validates BASH_XTRACEFD at assignment time, brush doesn't have that mechanism
|
||||
stdin: |
|
||||
BASH_XTRACEFD=999
|
||||
|
||||
set -x
|
||||
echo "should go to stderr"
|
||||
|
||||
set +x
|
||||
|
||||
- name: "non-numeric value falls back to stderr"
|
||||
known_failure: true # bash validates BASH_XTRACEFD at assignment time, brush doesn't have that mechanism
|
||||
stdin: |
|
||||
BASH_XTRACEFD=invalid
|
||||
|
||||
set -x
|
||||
echo "should go to stderr"
|
||||
|
||||
set +x
|
||||
|
||||
- name: "empty value falls back to stderr"
|
||||
stdin: |
|
||||
BASH_XTRACEFD=
|
||||
|
||||
set -x
|
||||
echo "should go to stderr"
|
||||
|
||||
- name: "redirect to file that fails writes"
|
||||
stdin: |
|
||||
# This test relies on /dev/full being present. We degrade gracefully
|
||||
# if it doesn't.
|
||||
[[ -e /dev/full ]] || exit
|
||||
|
||||
exec 3>/dev/full
|
||||
BASH_XTRACEFD=3
|
||||
|
||||
set -x
|
||||
echo "first echo"
|
||||
echo "first echo result: $?"
|
||||
@@ -0,0 +1,8 @@
|
||||
name: "Default options"
|
||||
cases:
|
||||
- name: "Default options"
|
||||
stdin: |
|
||||
echo "Default options: $-"
|
||||
|
||||
- name: "Default options (-c)"
|
||||
args: ["-c", 'echo "Default options: $-"']
|
||||
@@ -0,0 +1,213 @@
|
||||
name: "Options: errtrace (-E)"
|
||||
cases:
|
||||
#
|
||||
# Basic errtrace option behavior
|
||||
#
|
||||
- name: "errtrace disabled by default"
|
||||
stdin: |
|
||||
set +E 2>/dev/null || true
|
||||
echo "errtrace test"
|
||||
|
||||
- name: "errtrace can be enabled and disabled"
|
||||
stdin: |
|
||||
set -E
|
||||
echo "enabled: $-" | grep -q E && echo "E is set"
|
||||
set +E
|
||||
echo "disabled: $-" | grep -qv E && echo "E is unset"
|
||||
|
||||
#
|
||||
# ERR trap inheritance in functions (core errtrace behavior)
|
||||
#
|
||||
- name: "errtrace disabled - functions don't inherit ERR trap"
|
||||
stdin: |
|
||||
set +E
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
myfunc() { false; }
|
||||
myfunc
|
||||
echo "after"
|
||||
|
||||
- name: "errtrace enabled - functions inherit ERR trap"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
myfunc() { false; }
|
||||
myfunc
|
||||
echo "after"
|
||||
|
||||
- name: "errtrace with nested functions"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
inner() { false; }
|
||||
outer() { inner; }
|
||||
outer
|
||||
echo "after"
|
||||
|
||||
- name: "errtrace toggling mid-script"
|
||||
stdin: |
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
set -E
|
||||
myfunc1() { false; }
|
||||
myfunc1
|
||||
echo "after first call"
|
||||
set +E
|
||||
myfunc2() { false; }
|
||||
myfunc2
|
||||
echo "after second call"
|
||||
|
||||
#
|
||||
# ERR trap inheritance in subshells
|
||||
#
|
||||
- name: "errtrace disabled - subshell doesn't inherit ERR trap"
|
||||
stdin: |
|
||||
set +E
|
||||
trap 'echo "ERR"' ERR
|
||||
(
|
||||
trap -p ERR
|
||||
false
|
||||
echo "no ERR trap fired"
|
||||
)
|
||||
echo "after"
|
||||
|
||||
- name: "errtrace enabled - subshell inherits ERR trap"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "ERR"' ERR
|
||||
(
|
||||
trap -p ERR | grep -q ERR && echo "ERR trap inherited"
|
||||
false
|
||||
echo "after false in subshell"
|
||||
)
|
||||
echo "after"
|
||||
|
||||
#
|
||||
# ERR trap inheritance in command substitution
|
||||
#
|
||||
- name: "errtrace disabled - command substitution doesn't inherit ERR trap"
|
||||
stdin: |
|
||||
set +E
|
||||
trap 'echo "[ERR trapped]" >&2' ERR
|
||||
result=$(trap -p ERR; false; echo "no trap inherited")
|
||||
echo "result: $result"
|
||||
|
||||
- name: "errtrace enabled - command substitution inherits ERR trap"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "[ERR in subshell]" >&2' ERR
|
||||
result=$(false; echo "after false in subshell")
|
||||
echo "result: $result"
|
||||
|
||||
#
|
||||
# ERR trap inheritance in pipelines
|
||||
#
|
||||
- name: "errtrace with pipeline - last command fails"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "[ERR: $?]" >&2' ERR
|
||||
echo "data" | cat | false
|
||||
echo "after pipeline"
|
||||
|
||||
- name: "errtrace with pipeline - middle command fails (pipefail)"
|
||||
stdin: |
|
||||
set -E
|
||||
set -o pipefail
|
||||
trap 'echo "[ERR: $?]" >&2' ERR
|
||||
echo "data" | false | cat
|
||||
echo "after pipeline"
|
||||
|
||||
- name: "errtrace - ERR trap in pipeline subshells"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "[ERR: $BASH_COMMAND]" >&2' ERR
|
||||
echo "input" | { false; echo "after false in brace"; }
|
||||
echo "done"
|
||||
|
||||
#
|
||||
# ERR trap with errexit interaction
|
||||
#
|
||||
- name: "errtrace with errexit - ERR trap fires before exit"
|
||||
stdin: |
|
||||
set -e
|
||||
set -E
|
||||
trap 'echo "ERR trap fired"' ERR
|
||||
false
|
||||
echo "should not print"
|
||||
|
||||
- name: "errtrace with errexit in function"
|
||||
known_failure: true # TODO(traps): ERR trap not firing in function with errexit
|
||||
stdin: |
|
||||
set -e
|
||||
set -E
|
||||
trap 'echo "ERR trap"' ERR
|
||||
myfunc() { false; }
|
||||
myfunc
|
||||
echo "should not print"
|
||||
|
||||
#
|
||||
# ERR trap inheritance in deeply nested functions
|
||||
#
|
||||
- name: "errtrace - ERR trap in deeply nested function"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "ERR at level ${#FUNCNAME[@]}"' ERR
|
||||
level3() { false; }
|
||||
level2() { level3; }
|
||||
level1() { level2; }
|
||||
level1
|
||||
echo "after"
|
||||
|
||||
#
|
||||
# ERR trap modification within functions
|
||||
#
|
||||
- name: "errtrace - ERR trap can be changed inside function"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "outer ERR"' ERR
|
||||
myfunc() {
|
||||
trap 'echo "inner ERR"' ERR
|
||||
false
|
||||
}
|
||||
myfunc
|
||||
false
|
||||
echo "after"
|
||||
|
||||
- name: "errtrace - ERR trap can be cleared inside function"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "ERR"' ERR
|
||||
myfunc() {
|
||||
trap - ERR
|
||||
false
|
||||
echo "no ERR in func"
|
||||
}
|
||||
myfunc
|
||||
false
|
||||
echo "after"
|
||||
|
||||
#
|
||||
# ERR trap in background jobs with errtrace
|
||||
#
|
||||
- name: "errtrace - ERR trap in background job"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "ERR" >&2' ERR
|
||||
{ false; echo "bg done"; } &
|
||||
wait
|
||||
echo "main done"
|
||||
|
||||
#
|
||||
# Sourced scripts with errtrace
|
||||
#
|
||||
- name: "errtrace - ERR trap inherited in sourced script"
|
||||
test_files:
|
||||
- path: "failing.sh"
|
||||
contents: |
|
||||
echo "in sourced"
|
||||
false
|
||||
echo "after false in sourced"
|
||||
stdin: |
|
||||
set -E
|
||||
trap 'echo "ERR trapped"' ERR
|
||||
. ./failing.sh
|
||||
echo "after source"
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
name: "Options: extdebug"
|
||||
cases:
|
||||
#
|
||||
# extdebug implies functrace (-T)
|
||||
#
|
||||
- name: "extdebug implies functrace - DEBUG inherited in functions"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
myfunc() {
|
||||
echo "in func"
|
||||
}
|
||||
echo "before"
|
||||
myfunc
|
||||
echo "after"
|
||||
|
||||
- name: "extdebug implies functrace - DEBUG inherited in subshells"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
echo "before"
|
||||
(echo "in subshell")
|
||||
echo "after"
|
||||
|
||||
#
|
||||
# DEBUG trap return value effects (extdebug-specific)
|
||||
#
|
||||
- name: "DEBUG trap - returns 0 command executes normally"
|
||||
known_failure: true # TODO(extdebug): return in trap handler not supported
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap 'return 0' DEBUG
|
||||
echo "line 1"
|
||||
echo "line 2"
|
||||
echo "line 3"
|
||||
|
||||
- name: "DEBUG trap - returns 1 command is skipped"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap return value not implemented
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
skip_next=false
|
||||
trap '
|
||||
if $skip_next; then
|
||||
skip_next=false
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
' DEBUG
|
||||
echo "line 1"
|
||||
skip_next=true
|
||||
echo "this should be skipped"
|
||||
echo "line 3"
|
||||
|
||||
- name: "DEBUG trap - returns 2 simulates return from function"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap return value not implemented
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap '
|
||||
if [[ "${FUNCNAME[0]}" == "myfunc" && "$BASH_COMMAND" == *"should not"* ]]; then
|
||||
return 2
|
||||
fi
|
||||
return 0
|
||||
' DEBUG
|
||||
myfunc() {
|
||||
echo "start of func"
|
||||
echo "this should not print"
|
||||
echo "also should not print"
|
||||
}
|
||||
myfunc
|
||||
echo "after func: $?"
|
||||
|
||||
- name: "DEBUG trap - return value ignored without extdebug"
|
||||
known_failure: true # TODO(extdebug): return in trap handler error message differs
|
||||
stdin: |
|
||||
shopt -u extdebug
|
||||
trap 'return 1' DEBUG
|
||||
echo "line 1"
|
||||
echo "line 2"
|
||||
echo "line 3"
|
||||
|
||||
#
|
||||
# Command skipping scenarios (extdebug-specific)
|
||||
#
|
||||
- name: "Skip simple command"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap return value not implemented
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap '[[ "$BASH_COMMAND" == *"skip"* ]] && return 1; return 0' DEBUG
|
||||
echo "keep this"
|
||||
echo "skip this"
|
||||
echo "keep this too"
|
||||
|
||||
- name: "Skip in loop - iteration skipped but loop continues"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap return value not implemented
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap '[[ "$BASH_COMMAND" == *"skip"* ]] && return 1; return 0' DEBUG
|
||||
for i in 1 2 3; do
|
||||
echo "iter $i"
|
||||
echo "skip $i"
|
||||
echo "after skip $i"
|
||||
done
|
||||
echo "done"
|
||||
|
||||
- name: "Skip in conditional"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap return value not implemented
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap '[[ "$BASH_COMMAND" == *"skip"* ]] && return 1; return 0' DEBUG
|
||||
if true; then
|
||||
echo "in then"
|
||||
echo "skip in then"
|
||||
echo "after skip in then"
|
||||
fi
|
||||
echo "after if"
|
||||
|
||||
- name: "Return 2 in nested function - unwinds to caller"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap return value not implemented
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap '
|
||||
if [[ "${FUNCNAME[0]}" == "inner" && "$BASH_COMMAND" == *"inner cmd"* ]]; then
|
||||
return 2
|
||||
fi
|
||||
return 0
|
||||
' DEBUG
|
||||
inner() {
|
||||
echo "inner start"
|
||||
echo "inner cmd"
|
||||
echo "inner end"
|
||||
}
|
||||
outer() {
|
||||
echo "outer start"
|
||||
inner
|
||||
echo "outer after inner"
|
||||
}
|
||||
outer
|
||||
echo "main after outer"
|
||||
|
||||
#
|
||||
# BASH_ARGC and BASH_ARGV (extdebug-specific)
|
||||
#
|
||||
- name: "BASH_ARGC - contains argument counts per call frame"
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
show_argc() {
|
||||
echo "BASH_ARGC: ${BASH_ARGC[*]}"
|
||||
}
|
||||
func_with_args() {
|
||||
show_argc
|
||||
}
|
||||
func_with_args a b c
|
||||
|
||||
- name: "BASH_ARGV - contains arguments in reverse order"
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
show_argv() {
|
||||
echo "BASH_ARGV: ${BASH_ARGV[*]}"
|
||||
}
|
||||
func_with_args() {
|
||||
show_argv
|
||||
}
|
||||
func_with_args a b c
|
||||
|
||||
- name: "BASH_ARGC/ARGV - in nested calls"
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
inner() {
|
||||
echo "inner ARGC: ${BASH_ARGC[*]}"
|
||||
echo "inner ARGV: ${BASH_ARGV[*]}"
|
||||
}
|
||||
outer() {
|
||||
inner x y
|
||||
}
|
||||
outer a b c
|
||||
|
||||
- name: "BASH_ARGC/ARGV - empty when no arguments"
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
no_args() {
|
||||
echo "BASH_ARGC: '${BASH_ARGC[*]}'"
|
||||
echo "BASH_ARGV: '${BASH_ARGV[*]}'"
|
||||
}
|
||||
no_args
|
||||
|
||||
#
|
||||
# declare -F shows source info (extdebug-specific)
|
||||
#
|
||||
- name: "declare -F - shows function name only without extdebug"
|
||||
stdin: |
|
||||
myfunc() { echo hi; }
|
||||
declare -F myfunc
|
||||
|
||||
- name: "declare -F - shows filename and line number with extdebug"
|
||||
known_failure: true # TODO(extdebug): declare -F not showing source info
|
||||
test_files:
|
||||
- path: "funcs.sh"
|
||||
contents: |
|
||||
myfunc() {
|
||||
echo hi
|
||||
}
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
. ./funcs.sh
|
||||
declare -F myfunc | sed 's/[0-9]*/N/'
|
||||
|
||||
#
|
||||
# Interaction with other options
|
||||
#
|
||||
- name: "extdebug with errexit - skipped command doesn't trigger errexit"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap return value not implemented
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
set -e
|
||||
trap '[[ "$BASH_COMMAND" == "false" ]] && return 1; return 0' DEBUG
|
||||
echo "before"
|
||||
false
|
||||
echo "after - should print because false was skipped"
|
||||
|
||||
- name: "extdebug disabled after being enabled"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap return value not implemented
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap 'return 1' DEBUG
|
||||
echo "line 1 - should be skipped"
|
||||
shopt -u extdebug
|
||||
echo "line 2 - should print"
|
||||
echo "line 3 - should print"
|
||||
|
||||
- name: "extdebug with set -T - both affect DEBUG inheritance"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
set -T
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
myfunc() {
|
||||
echo "in func"
|
||||
}
|
||||
myfunc
|
||||
echo "done"
|
||||
|
||||
#
|
||||
# DEBUG trap with extdebug in various contexts
|
||||
#
|
||||
- name: "DEBUG trap with extdebug in function"
|
||||
known_failure: true # TODO(extdebug): return in trap handler not supported
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
count=0
|
||||
trap '
|
||||
((count++))
|
||||
echo "debug $count: $BASH_COMMAND"
|
||||
return 0
|
||||
' DEBUG
|
||||
myfunc() {
|
||||
local x=1
|
||||
echo "x=$x"
|
||||
}
|
||||
myfunc
|
||||
|
||||
- name: "DEBUG trap with extdebug controls command execution"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap return value not implemented
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
should_skip=false
|
||||
trap '
|
||||
if $should_skip; then
|
||||
should_skip=false
|
||||
echo "[skipping: $BASH_COMMAND]"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
' DEBUG
|
||||
echo "first"
|
||||
should_skip=true
|
||||
echo "second - will be skipped"
|
||||
echo "third"
|
||||
|
||||
#
|
||||
# Edge cases
|
||||
#
|
||||
- name: "extdebug with recursive function"
|
||||
known_failure: true # TODO(extdebug): DEBUG trap firing count differs
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap 'echo "depth: ${#FUNCNAME[@]}"' DEBUG
|
||||
recurse() {
|
||||
local n=$1
|
||||
if ((n > 0)); then
|
||||
recurse $((n - 1))
|
||||
fi
|
||||
}
|
||||
recurse 3
|
||||
|
||||
- name: "BASH_COMMAND shows compound command"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
trap 'echo "cmd: $BASH_COMMAND"' DEBUG
|
||||
if true; then echo "in if"; fi
|
||||
|
||||
- name: "DEBUG trap can modify variables"
|
||||
known_failure: true # TODO(extdebug): return in trap handler not supported
|
||||
stdin: |
|
||||
shopt -s extdebug
|
||||
counter=0
|
||||
trap '((counter++)); return 0' DEBUG
|
||||
echo "a"
|
||||
echo "b"
|
||||
echo "c"
|
||||
echo "counter: $counter"
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
name: "Options: functrace (-T)"
|
||||
cases:
|
||||
#
|
||||
# Basic functrace option behavior
|
||||
#
|
||||
- name: "functrace can be enabled and disabled"
|
||||
stdin: |
|
||||
set -T
|
||||
echo "enabled: $-" | grep -q T && echo "T is set"
|
||||
set +T
|
||||
echo "disabled: $-" | grep -qv T && echo "T is unset"
|
||||
|
||||
#
|
||||
# DEBUG trap inheritance in functions
|
||||
#
|
||||
- name: "DEBUG trap - does not fire in function without set -T"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
myfunc() {
|
||||
echo "in func"
|
||||
}
|
||||
echo "before func"
|
||||
myfunc
|
||||
echo "after func"
|
||||
|
||||
- name: "DEBUG trap - fires in function with set -T"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
myfunc() {
|
||||
echo "in func"
|
||||
}
|
||||
echo "before func"
|
||||
myfunc
|
||||
echo "after func"
|
||||
|
||||
#
|
||||
# DEBUG trap inheritance in subshells
|
||||
#
|
||||
- name: "DEBUG trap - does not fire in subshell without set -T"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
echo "before subshell"
|
||||
(echo "in subshell")
|
||||
echo "after subshell"
|
||||
|
||||
- name: "DEBUG trap - fires in subshell with set -T"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
echo "before subshell"
|
||||
(echo "in subshell")
|
||||
echo "after subshell"
|
||||
|
||||
#
|
||||
# DEBUG trap inheritance in command substitution
|
||||
#
|
||||
- name: "DEBUG trap - does not fire in command substitution without set -T"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
trap 'echo "[debug: $BASH_COMMAND]" >&2' DEBUG
|
||||
echo "before"
|
||||
result=$(echo "in substitution")
|
||||
echo "result: $result"
|
||||
|
||||
- name: "DEBUG trap - fires in command substitution with set -T"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[debug: $BASH_COMMAND]" >&2' DEBUG
|
||||
echo "before"
|
||||
result=$(echo "in substitution")
|
||||
echo "result: $result"
|
||||
|
||||
#
|
||||
# DEBUG trap inheritance in pipelines
|
||||
#
|
||||
- name: "DEBUG trap - does not fire in pipeline without set -T"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
trap 'echo "[debug: $BASH_COMMAND]" >&2' DEBUG
|
||||
echo "before"
|
||||
echo "hello" | cat | cat
|
||||
echo "after"
|
||||
|
||||
- name: "DEBUG trap - fires in pipeline with set -T"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[debug: $BASH_COMMAND]" >&2' DEBUG
|
||||
echo "before"
|
||||
echo "hello" | cat | cat
|
||||
echo "after"
|
||||
|
||||
#
|
||||
# RETURN trap inheritance in functions
|
||||
#
|
||||
- name: "RETURN trap - does not fire in called function without set -T"
|
||||
stdin: |
|
||||
trap 'echo "[return: $FUNCNAME]"' RETURN
|
||||
inner() {
|
||||
echo "in inner"
|
||||
}
|
||||
outer() {
|
||||
echo "in outer"
|
||||
inner
|
||||
echo "outer continuing"
|
||||
}
|
||||
outer
|
||||
echo "after"
|
||||
|
||||
- name: "RETURN trap - fires in called function with set -T"
|
||||
known_failure: true # TODO(functrace): RETURN trap inheritance not implemented
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[return: ${FUNCNAME:-main}]"' RETURN
|
||||
inner() {
|
||||
echo "in inner"
|
||||
}
|
||||
outer() {
|
||||
echo "in outer"
|
||||
inner
|
||||
echo "outer continuing"
|
||||
}
|
||||
outer
|
||||
echo "after"
|
||||
|
||||
- name: "RETURN trap - fires for each nested function return with set -T"
|
||||
known_failure: true # TODO(functrace): RETURN trap inheritance not implemented
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[return: ${FUNCNAME:-main} -> $?]"' RETURN
|
||||
level3() { echo "level3"; return 3; }
|
||||
level2() { echo "level2"; level3; return 2; }
|
||||
level1() { echo "level1"; level2; return 1; }
|
||||
level1
|
||||
echo "done"
|
||||
|
||||
#
|
||||
# Function-specific trace attribute (declare -t)
|
||||
#
|
||||
- name: "declare -t - enables trace for specific function"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
traced_func() {
|
||||
echo "in traced"
|
||||
}
|
||||
untraced_func() {
|
||||
echo "in untraced"
|
||||
}
|
||||
declare -t traced_func
|
||||
echo "calling untraced"
|
||||
untraced_func
|
||||
echo "calling traced"
|
||||
traced_func
|
||||
echo "done"
|
||||
|
||||
- name: "declare +t - disables trace for specific function"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
myfunc() {
|
||||
echo "in func"
|
||||
}
|
||||
declare +t myfunc
|
||||
echo "calling func"
|
||||
myfunc
|
||||
echo "done"
|
||||
|
||||
- name: "declare -f - shows trace attribute"
|
||||
stdin: |
|
||||
myfunc() { echo hi; }
|
||||
declare -t myfunc
|
||||
declare -f myfunc | head -1
|
||||
|
||||
#
|
||||
# Toggling set -T mid-script
|
||||
#
|
||||
- name: "set -T enabled mid-script affects subsequent calls"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
func1() { echo "in func1"; }
|
||||
func2() { echo "in func2"; }
|
||||
echo "calling func1 without -T"
|
||||
func1
|
||||
set -T
|
||||
echo "calling func2 with -T"
|
||||
func2
|
||||
echo "done"
|
||||
|
||||
- name: "set +T disables inheritance mid-script"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
|
||||
func1() { echo "in func1"; }
|
||||
func2() { echo "in func2"; }
|
||||
echo "calling func1 with -T"
|
||||
func1
|
||||
set +T
|
||||
echo "calling func2 without -T"
|
||||
func2
|
||||
echo "done"
|
||||
|
||||
#
|
||||
# DEBUG trap inheritance in background jobs
|
||||
#
|
||||
- name: "DEBUG trap - in background job with set -T"
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[debug]" >&2' DEBUG
|
||||
{
|
||||
echo "in background"
|
||||
} &
|
||||
wait
|
||||
echo "done"
|
||||
|
||||
#
|
||||
# DEBUG trap inheritance in process substitution
|
||||
#
|
||||
- name: "DEBUG trap - in process substitution with set -T"
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[debug]" >&2' DEBUG
|
||||
cat <(echo "from procsub")
|
||||
echo "done"
|
||||
|
||||
#
|
||||
# BASH_COMMAND visibility in DEBUG trap with functrace
|
||||
#
|
||||
- name: "BASH_COMMAND in function with set -T"
|
||||
known_failure: true # TODO(BASH_COMMAND): quotes differ
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "cmd: $BASH_COMMAND"' DEBUG
|
||||
myfunc() {
|
||||
local v=10
|
||||
echo "v=$v"
|
||||
}
|
||||
myfunc
|
||||
|
||||
#
|
||||
# Nested functions with varying trace states
|
||||
#
|
||||
- name: "Outer function traced, inner function not traced"
|
||||
stdin: |
|
||||
trap 'echo "[debug in ${FUNCNAME:-main}]"' DEBUG
|
||||
inner() {
|
||||
echo "inner"
|
||||
}
|
||||
outer() {
|
||||
echo "outer start"
|
||||
inner
|
||||
echo "outer end"
|
||||
}
|
||||
declare -t outer
|
||||
outer
|
||||
echo "done"
|
||||
|
||||
- name: "Only inner function traced"
|
||||
stdin: |
|
||||
trap 'echo "[debug in ${FUNCNAME:-main}]"' DEBUG
|
||||
inner() {
|
||||
echo "inner"
|
||||
}
|
||||
outer() {
|
||||
echo "outer start"
|
||||
inner
|
||||
echo "outer end"
|
||||
}
|
||||
declare -t inner
|
||||
outer
|
||||
echo "done"
|
||||
|
||||
#
|
||||
# RETURN trap with sourced scripts and set -T
|
||||
#
|
||||
- name: "RETURN trap - fires for function in sourced script with set -T"
|
||||
known_failure: true # TODO(functrace): RETURN trap inheritance not implemented
|
||||
test_files:
|
||||
- path: "sourced.sh"
|
||||
contents: |
|
||||
sourced_func() {
|
||||
echo "in sourced_func"
|
||||
}
|
||||
sourced_func
|
||||
stdin: |
|
||||
set -T
|
||||
trap 'echo "[return: ${FUNCNAME:-main}]"' RETURN
|
||||
. ./sourced.sh
|
||||
echo "after source"
|
||||
|
||||
#
|
||||
# Edge cases
|
||||
#
|
||||
- name: "DEBUG trap - count in deeply nested function calls with set -T"
|
||||
known_failure: true # TODO(functrace): DEBUG trap count differs
|
||||
stdin: |
|
||||
set -T
|
||||
count=0
|
||||
trap 'count=$((count + 1))' DEBUG
|
||||
a() { b; }
|
||||
b() { c; }
|
||||
c() { echo "deep"; }
|
||||
a
|
||||
echo "debug count: $count"
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
name: "Options: inherit_errexit"
|
||||
cases:
|
||||
- name: "inherit_errexit disabled by default"
|
||||
stdin: |
|
||||
shopt -q inherit_errexit && echo "enabled" || echo "disabled"
|
||||
|
||||
- name: "inherit_errexit disabled - command substitution ignores errexit"
|
||||
stdin: |
|
||||
set -e
|
||||
shopt -u inherit_errexit
|
||||
result=$(false; echo "still running")
|
||||
echo "result: $result"
|
||||
echo "after"
|
||||
|
||||
- name: "inherit_errexit enabled - command substitution inherits errexit"
|
||||
stdin: |
|
||||
set -e
|
||||
shopt -s inherit_errexit
|
||||
result=$(false; echo "should not print")
|
||||
echo "should not print"
|
||||
|
||||
- name: "inherit_errexit with nested command substitution"
|
||||
stdin: |
|
||||
set -e
|
||||
shopt -s inherit_errexit
|
||||
result=$(echo "outer: $(false; echo inner)")
|
||||
echo "should not print"
|
||||
|
||||
- name: "inherit_errexit disabled - nested substitution continues"
|
||||
stdin: |
|
||||
set -e
|
||||
shopt -u inherit_errexit
|
||||
result=$(echo "outer: $(false; echo inner)")
|
||||
echo "result: $result"
|
||||
echo "after"
|
||||
|
||||
- name: "inherit_errexit with backtick substitution"
|
||||
stdin: |
|
||||
set -e
|
||||
shopt -s inherit_errexit
|
||||
result=`false; echo "should not print"`
|
||||
echo "should not print"
|
||||
|
||||
- name: "inherit_errexit with assignment"
|
||||
stdin: |
|
||||
set -e
|
||||
shopt -s inherit_errexit
|
||||
var=$(false)
|
||||
echo "should not print"
|
||||
|
||||
- name: "inherit_errexit in function"
|
||||
stdin: |
|
||||
set -e
|
||||
shopt -s inherit_errexit
|
||||
myfunc() {
|
||||
local result=$(false; echo "should not print")
|
||||
echo "after local"
|
||||
}
|
||||
myfunc
|
||||
echo "should not print"
|
||||
|
||||
- name: "inherit_errexit toggling"
|
||||
stdin: |
|
||||
set -e
|
||||
shopt -s inherit_errexit
|
||||
result1=$(false; echo "should not print in first")
|
||||
echo "should not print 1"
|
||||
ignore_stderr: true
|
||||
|
||||
- name: "inherit_errexit with multiple commands in substitution"
|
||||
stdin: |
|
||||
set -e
|
||||
shopt -u inherit_errexit
|
||||
result=$(echo "start"; false; echo "after false")
|
||||
echo "result: $result"
|
||||
echo "after"
|
||||
@@ -0,0 +1,9 @@
|
||||
name: "set -B"
|
||||
cases:
|
||||
- name: "set -B"
|
||||
stdin: |
|
||||
set +B
|
||||
echo "+B: " ${a,b}
|
||||
|
||||
set -B
|
||||
echo "-B: " ${a,b}
|
||||
@@ -0,0 +1,27 @@
|
||||
name: "set -C"
|
||||
cases:
|
||||
- name: "set -C"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
touch existing-file
|
||||
|
||||
set -C
|
||||
|
||||
echo hi > non-existing-file
|
||||
echo "Result (non existing): $?"
|
||||
echo "File contents: $(cat non-existing-file)"
|
||||
echo
|
||||
|
||||
echo hi > /dev/null
|
||||
echo "Result (device file): $?"
|
||||
echo
|
||||
|
||||
echo hi > existing-file
|
||||
echo "Result (existing file): $?"
|
||||
echo "File contents: $(cat existing-file)"
|
||||
echo
|
||||
|
||||
echo hi >| existing-file
|
||||
echo "Result (clobber): $?"
|
||||
echo "File contents: $(cat existing-file)"
|
||||
echo
|
||||
@@ -0,0 +1,20 @@
|
||||
name: "set -a"
|
||||
cases:
|
||||
- name: "set -a"
|
||||
stdin: |
|
||||
v1=1 v2=2 v3=3 v4=4 v5=(a b c)
|
||||
declare -p v1 v2 v3 v4
|
||||
|
||||
set -a
|
||||
v1=reassigned
|
||||
v2+=appended
|
||||
declare -i v3
|
||||
v4+=(appended)
|
||||
v5[2]=updated
|
||||
v6=new
|
||||
v7[0]=new
|
||||
v8=(new)
|
||||
declare v9=new
|
||||
declare -a v10=(new)
|
||||
|
||||
declare -p v1 v2 v3 v4 v5 v6 v7 v8 v9 v10
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
name: "set -n"
|
||||
cases:
|
||||
- name: "set -n"
|
||||
stdin: |
|
||||
set -n
|
||||
touch somefile.txt
|
||||
ls
|
||||
@@ -0,0 +1,7 @@
|
||||
name: "set -t"
|
||||
cases:
|
||||
- name: "set -t"
|
||||
args: ["-t"]
|
||||
stdin: |
|
||||
echo first
|
||||
echo second
|
||||
@@ -0,0 +1,534 @@
|
||||
name: "Options: set -u"
|
||||
cases:
|
||||
# Basic behavior
|
||||
- name: "Basic set variable access"
|
||||
stdin: |
|
||||
set -u
|
||||
var="value"
|
||||
echo "$var"
|
||||
|
||||
- name: "Unset variable triggers error"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
echo "$unset_var"
|
||||
echo "should not print"
|
||||
|
||||
- name: "Command line -u"
|
||||
args: ["-u"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "$unset_var"
|
||||
echo "should not print"
|
||||
|
||||
# Special parameters - should NOT error even when unset
|
||||
- name: "Special parameter $@ does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "args: $@"
|
||||
|
||||
- name: "Special parameter $* does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "args: $*"
|
||||
|
||||
- name: "Special parameter $# does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "count: $#"
|
||||
|
||||
- name: "Special parameter $? does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
true
|
||||
echo "status: $?"
|
||||
|
||||
- name: "Special parameter $$ does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "pid: $$" > /dev/null
|
||||
echo "ok"
|
||||
|
||||
- name: "Special parameter $! does not error when no background jobs"
|
||||
known_failure: true
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
echo "last bg: ${!}" || echo "ok"
|
||||
|
||||
- name: "Special parameter $0 does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "shell: $0" > /dev/null
|
||||
echo "ok"
|
||||
|
||||
- name: "Special parameter $- does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "flags: $-" > /dev/null
|
||||
echo "ok"
|
||||
|
||||
# Positional parameters
|
||||
- name: "Unset positional parameter triggers error"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
myfunc() {
|
||||
echo "$1"
|
||||
}
|
||||
myfunc
|
||||
echo "should not print"
|
||||
|
||||
- name: "Set positional parameter does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
myfunc() {
|
||||
echo "$1"
|
||||
}
|
||||
myfunc "arg"
|
||||
|
||||
- name: "Positional parameters beyond provided args error"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
myfunc() {
|
||||
echo "$2"
|
||||
}
|
||||
myfunc "only_one_arg"
|
||||
echo "should not print"
|
||||
|
||||
# Parameter expansion forms
|
||||
- name: "Unset variable with default does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "${unset_var:-default}"
|
||||
|
||||
- name: "Empty vs unset with :- operator"
|
||||
stdin: |
|
||||
set -u
|
||||
empty=""
|
||||
echo "${empty:-default}"
|
||||
echo "${unset:-default}"
|
||||
|
||||
- name: "Empty vs unset with - operator"
|
||||
stdin: |
|
||||
set -u
|
||||
empty=""
|
||||
echo "${empty-default}"
|
||||
echo "${unset-default}"
|
||||
|
||||
# Arrays (if supported)
|
||||
- name: "Unset array element with default"
|
||||
stdin: |
|
||||
set -u
|
||||
arr=(a b c)
|
||||
echo "${arr[10]:-default}"
|
||||
|
||||
- name: "Unset array treated as unset variable"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "${unset_array[@]}"
|
||||
echo "should not print"
|
||||
|
||||
- name: "Unset array with default does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "${unset_array[@]:-default}"
|
||||
|
||||
# Conditionals
|
||||
- name: "Unset variable in conditional test errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
if [ "$unset_var" = "value" ]; then
|
||||
echo "then"
|
||||
fi
|
||||
echo "should not print"
|
||||
|
||||
- name: "Unset variable in [[ test errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
if [[ $unset_var = "value" ]]; then
|
||||
echo "then"
|
||||
fi
|
||||
echo "should not print"
|
||||
|
||||
- name: "Unset variable with default in conditional works"
|
||||
stdin: |
|
||||
set -u
|
||||
if [ "${unset_var:-}" = "" ]; then
|
||||
echo "empty"
|
||||
fi
|
||||
|
||||
- name: "test -v for unset variable"
|
||||
stdin: |
|
||||
set -u
|
||||
if test -v unset_var; then
|
||||
echo "set"
|
||||
else
|
||||
echo "unset"
|
||||
fi
|
||||
|
||||
- name: "test -z with unset variable errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
if test -z "$unset_var"; then
|
||||
echo "empty"
|
||||
fi
|
||||
echo "should not print"
|
||||
|
||||
# Command substitution
|
||||
- name: "Unset variable in command substitution errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
result=$(echo "$unset_var")
|
||||
echo "should not print"
|
||||
|
||||
- name: "Unset variable with default in command substitution"
|
||||
stdin: |
|
||||
set -u
|
||||
result=$(echo "${unset_var:-default}")
|
||||
echo "$result"
|
||||
|
||||
# Arithmetic expansion
|
||||
- name: "Unset variable in arithmetic defaults to zero"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
result=$((unset_var + 5))
|
||||
echo "$result"
|
||||
|
||||
# set +u to disable
|
||||
- name: "set +u disables unset variable check"
|
||||
stdin: |
|
||||
set -u
|
||||
set +u
|
||||
echo "$unset_var"
|
||||
echo "ok"
|
||||
|
||||
- name: "set -u can be re-enabled"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
set +u
|
||||
echo "$unset_var"
|
||||
set -u
|
||||
echo "$another_unset"
|
||||
echo "should not print"
|
||||
|
||||
- name: "set +u in function does not affect parent"
|
||||
stdin: |
|
||||
set -u
|
||||
myfunc() {
|
||||
set +u
|
||||
echo "$unset_in_func"
|
||||
}
|
||||
myfunc
|
||||
echo "$unset_in_parent"
|
||||
echo "should not print"
|
||||
|
||||
# Functions
|
||||
- name: "Unset variable in function body errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
myfunc() {
|
||||
echo "$unset_var"
|
||||
}
|
||||
myfunc
|
||||
echo "should not print"
|
||||
|
||||
- name: "Local unset variable errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
myfunc() {
|
||||
local unset_local
|
||||
echo "$unset_local"
|
||||
}
|
||||
myfunc
|
||||
echo "should not print"
|
||||
|
||||
- name: "Local variable declaration with value works"
|
||||
stdin: |
|
||||
set -u
|
||||
myfunc() {
|
||||
local my_var="value"
|
||||
echo "$my_var"
|
||||
}
|
||||
myfunc
|
||||
|
||||
- name: "Function parameter shadows unset global"
|
||||
stdin: |
|
||||
set -u
|
||||
myfunc() {
|
||||
local var="$1"
|
||||
echo "$var"
|
||||
}
|
||||
myfunc "arg"
|
||||
|
||||
# Subshells
|
||||
- name: "Unset variable in subshell errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
(echo "$unset_var")
|
||||
echo "should not print"
|
||||
|
||||
- name: "set +u in subshell does not affect parent"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
(
|
||||
set +u
|
||||
echo "$unset_in_subshell"
|
||||
)
|
||||
echo "$unset_in_parent"
|
||||
echo "should not print"
|
||||
|
||||
# Variable assignment and unset
|
||||
- name: "Variable set then unset errors on access"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
var="value"
|
||||
echo "$var"
|
||||
unset var
|
||||
echo "$var"
|
||||
echo "should not print"
|
||||
|
||||
- name: "Variable set then unset with default works"
|
||||
stdin: |
|
||||
set -u
|
||||
var="value"
|
||||
unset var
|
||||
echo "${var:-default}"
|
||||
|
||||
- name: "Unsetting non-existent variable does not error"
|
||||
stdin: |
|
||||
set -u
|
||||
unset non_existent
|
||||
echo "ok"
|
||||
|
||||
# Export and environment
|
||||
- name: "Exported unset variable errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
export unset_exported
|
||||
echo "$unset_exported"
|
||||
echo "should not print"
|
||||
|
||||
- name: "Exported variable with value works"
|
||||
stdin: |
|
||||
set -u
|
||||
export exported_var="value"
|
||||
echo "$exported_var"
|
||||
|
||||
# Pattern expansion
|
||||
- name: "Unset variable in pattern errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
echo ${unset_var#prefix}
|
||||
echo "should not print"
|
||||
|
||||
# Length operator
|
||||
- name: "Length of unset variable errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
echo ${#unset_var}
|
||||
echo "should not print"
|
||||
|
||||
- name: "Length of empty variable works"
|
||||
stdin: |
|
||||
set -u
|
||||
empty=""
|
||||
echo ${#empty}
|
||||
|
||||
# Indirect expansion
|
||||
- name: "Indirect expansion of unset variable errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
ref="unset_target"
|
||||
echo ${!ref}
|
||||
echo "should not print"
|
||||
|
||||
- name: "Indirect expansion to set variable works"
|
||||
stdin: |
|
||||
set -u
|
||||
target="value"
|
||||
ref="target"
|
||||
echo ${!ref}
|
||||
|
||||
# Case statements
|
||||
- name: "Unset variable in case pattern errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
case "$unset_var" in
|
||||
*) echo "matched" ;;
|
||||
esac
|
||||
echo "should not print"
|
||||
|
||||
- name: "Unset variable with default in case works"
|
||||
stdin: |
|
||||
set -u
|
||||
case "${unset_var:-default}" in
|
||||
default) echo "matched" ;;
|
||||
esac
|
||||
|
||||
# For loops
|
||||
- name: "Unset variable in for loop errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
for item in $unset_var; do
|
||||
echo "$item"
|
||||
done
|
||||
echo "should not print"
|
||||
|
||||
- name: "Unset variable with default in for loop works"
|
||||
stdin: |
|
||||
set -u
|
||||
for item in ${unset_var:-a b c}; do
|
||||
echo "$item"
|
||||
done
|
||||
|
||||
# While loops with unset variables
|
||||
- name: "Unset variable in while condition errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
while [ "$unset_var" != "stop" ]; do
|
||||
echo "loop"
|
||||
break
|
||||
done
|
||||
echo "should not print"
|
||||
|
||||
# Read command
|
||||
- name: "read into variable then access works"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "value" | read my_var || true
|
||||
echo "${my_var:-default}"
|
||||
|
||||
- name: "Variable unset before read then set works"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "test" | {
|
||||
read input
|
||||
echo "$input"
|
||||
}
|
||||
|
||||
# Compound assignments
|
||||
- name: "Associative array unset key with default"
|
||||
stdin: |
|
||||
set -u
|
||||
declare -A assoc
|
||||
echo "${assoc[key]:-default}"
|
||||
|
||||
# Pipelines
|
||||
- name: "Unset variable with default in pipeline works"
|
||||
stdin: |
|
||||
set -u
|
||||
echo "${unset_var:-default}" | cat
|
||||
|
||||
# Here documents and here strings
|
||||
- name: "Unset variable in here doc errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
cat <<EOF
|
||||
$unset_var
|
||||
EOF
|
||||
echo "should not print"
|
||||
|
||||
- name: "Unset variable with default in here doc works"
|
||||
stdin: |
|
||||
set -u
|
||||
cat <<EOF
|
||||
${unset_var:-default}
|
||||
EOF
|
||||
|
||||
# Edge cases
|
||||
- name: "Empty string is not unset"
|
||||
stdin: |
|
||||
set -u
|
||||
var=""
|
||||
echo "var is: '$var'"
|
||||
|
||||
- name: "Null value vs unset"
|
||||
stdin: |
|
||||
set -u
|
||||
null=""
|
||||
echo "${null-default}"
|
||||
echo "${unset-default}"
|
||||
|
||||
- name: "IFS unset behavior"
|
||||
stdin: |
|
||||
set -u
|
||||
unset IFS
|
||||
echo "ok"
|
||||
|
||||
- name: "PATH unset would error"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
saved_path="$PATH"
|
||||
unset PATH
|
||||
echo "$PATH"
|
||||
echo "should not print"
|
||||
|
||||
# Combination with other options
|
||||
- name: "set -u with set -e"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -eu
|
||||
echo "$unset_var"
|
||||
echo "should not print"
|
||||
|
||||
# Declare without assignment
|
||||
- name: "declare without value then access errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
declare my_var
|
||||
echo "$my_var"
|
||||
echo "should not print"
|
||||
|
||||
- name: "declare with value then access works"
|
||||
stdin: |
|
||||
set -u
|
||||
declare my_var="value"
|
||||
echo "$my_var"
|
||||
|
||||
- name: "readonly without value then access errors"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
readonly my_var
|
||||
echo "$my_var"
|
||||
echo "should not print"
|
||||
|
||||
- name: "Nameref to set variable with set -u"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
set -u
|
||||
target="value"
|
||||
declare -n ref=target
|
||||
echo "$ref"
|
||||
|
||||
- name: "Nameref to unset variable with set -u errors"
|
||||
known_failure: true
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
set -u
|
||||
declare -n ref=unset_target
|
||||
echo "$ref"
|
||||
echo "should not print"
|
||||
@@ -0,0 +1,81 @@
|
||||
name: "set -x"
|
||||
cases:
|
||||
- name: "basic"
|
||||
stdin: |
|
||||
set -x
|
||||
|
||||
ls
|
||||
|
||||
for f in 1 2 3; do echo ${f}; done
|
||||
|
||||
case 1 in
|
||||
1) echo "1";;
|
||||
*) echo "not";;
|
||||
esac
|
||||
|
||||
while false; do
|
||||
echo body
|
||||
done
|
||||
|
||||
newvar=$(echo "new")
|
||||
|
||||
x=$((3 + 7))
|
||||
|
||||
if [[ x == 10 && ! 0 ]]; then
|
||||
echo "Math checks"
|
||||
fi
|
||||
|
||||
var="x"
|
||||
[[ ${var} && ${var//[[:space:]]/} ]]
|
||||
|
||||
for ((i = 0; i < 3; i++)); do
|
||||
echo $i
|
||||
done
|
||||
|
||||
((x = 3)) || ((x = 4))
|
||||
|
||||
override=value echo some_output
|
||||
|
||||
- name: "sourced script"
|
||||
test_files:
|
||||
- path: script.sh
|
||||
contents: |
|
||||
echo "Entered script"
|
||||
for f in a b c; do echo $f; done
|
||||
echo "Exiting script"
|
||||
stdin: |
|
||||
set -x
|
||||
|
||||
echo "About to source script"
|
||||
source script.sh
|
||||
echo "Back from script"
|
||||
|
||||
- name: "functions"
|
||||
stdin: |
|
||||
set -x
|
||||
|
||||
myfunc() {
|
||||
echo "In myfunc"
|
||||
for i in 1 2; do echo "i is $i"; done
|
||||
}
|
||||
|
||||
echo "Before function call"
|
||||
myfunc
|
||||
echo "After function call"
|
||||
|
||||
- name: "command line"
|
||||
args: ["-x", "script.sh"]
|
||||
test_files:
|
||||
- path: script.sh
|
||||
contents: |
|
||||
echo "In command line script"
|
||||
for n in x y z; do echo $n; done
|
||||
|
||||
- name: "trap"
|
||||
known_failure: true # TODO(traps): review this
|
||||
stdin: |
|
||||
set -x
|
||||
|
||||
trap 'echo "In trap"' DEBUG
|
||||
|
||||
echo "Doing something"
|
||||
@@ -0,0 +1,79 @@
|
||||
name: "Options: failglob"
|
||||
cases:
|
||||
- name: "failglob errors on unmatched glob"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
echo non-existent-*.txt 2>/dev/null
|
||||
echo "exit=$?"
|
||||
|
||||
- name: "failglob does not affect matching glob"
|
||||
stdin: |
|
||||
touch realfile.txt
|
||||
shopt -s failglob
|
||||
echo real*.txt
|
||||
|
||||
- name: "failglob does not affect quoted glob chars"
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
echo 'non-existent-*.txt'
|
||||
|
||||
- name: "failglob with negation"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
! echo non-existent-*.txt 2>/dev/null
|
||||
echo "exit=$?"
|
||||
|
||||
- name: "failglob in subshell"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
(echo non-existent-*.txt; echo "inner=$?")
|
||||
echo "outer=$?"
|
||||
|
||||
- name: "failglob lone ] is not a glob"
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
echo foo]
|
||||
|
||||
- name: "failglob lone [ is not a glob"
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
echo [abc
|
||||
|
||||
- name: "failglob aborts compound list"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
echo *.nomatch; echo "after"
|
||||
|
||||
- name: "failglob in multi-member pipeline"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
echo *.nomatch | cat
|
||||
echo "after"
|
||||
|
||||
- name: "failglob extglob pattern"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
shopt -s extglob
|
||||
echo @(nomatch_pattern_xyz)
|
||||
echo "after"
|
||||
|
||||
- name: "failglob takes precedence over nullglob"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
shopt -s failglob nullglob
|
||||
echo non-existent-*.txt 2>/dev/null
|
||||
echo "exit=$?"
|
||||
|
||||
- name: "failglob on unquoted variable with glob chars"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
shopt -s failglob
|
||||
x='*.nomatch_xyz'
|
||||
echo $x 2>/dev/null
|
||||
echo "exit=$?"
|
||||
@@ -0,0 +1,11 @@
|
||||
name: "shopt lastpipe"
|
||||
cases:
|
||||
- name: "shopt -s lastpipe"
|
||||
stdin: |
|
||||
echo ignored | read var
|
||||
echo "1. var='${var}'"
|
||||
|
||||
shopt -s lastpipe
|
||||
set +o monitor
|
||||
echo ignored | read var
|
||||
echo "2. var='${var}'"
|
||||
@@ -0,0 +1,12 @@
|
||||
name: "shopt login_shell"
|
||||
cases:
|
||||
- name: "login_shell is on for login shells"
|
||||
args:
|
||||
- "--login"
|
||||
- "-c"
|
||||
- "shopt -q login_shell"
|
||||
|
||||
- name: "login_shell is off for non-login shells"
|
||||
args:
|
||||
- "-c"
|
||||
- "shopt -q login_shell"
|
||||
@@ -0,0 +1,79 @@
|
||||
name: "Options: nullglob"
|
||||
cases:
|
||||
- name: "nullglob removes unmatched glob"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
echo "result:" non-existent-*.txt
|
||||
|
||||
- name: "nullglob does not affect matching glob"
|
||||
stdin: |
|
||||
touch realfile.txt
|
||||
shopt -s nullglob
|
||||
echo real*.txt
|
||||
|
||||
- name: "nullglob does not affect quoted glob chars"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
echo 'non-existent-*.txt'
|
||||
|
||||
- name: "nullglob does not affect literal text without glob chars"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
echo hello world
|
||||
|
||||
- name: "nullglob with multiple unmatched patterns"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
echo "result:" *.zzz *.yyy *.xxx
|
||||
|
||||
- name: "nullglob with mix of matched and unmatched"
|
||||
stdin: |
|
||||
touch realfile.txt
|
||||
shopt -s nullglob
|
||||
echo real*.txt *.zzz
|
||||
|
||||
- name: "nullglob with question mark glob"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
echo "result:" ?.nonexistent
|
||||
|
||||
- name: "nullglob with bracket glob"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
echo "result:" [abc].nonexistent
|
||||
|
||||
- name: "nullglob disabled by default"
|
||||
stdin: |
|
||||
echo *.nonexistent
|
||||
|
||||
- name: "nullglob can be toggled"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
echo "on:" *.nonexistent
|
||||
shopt -u nullglob
|
||||
echo "off:" *.nonexistent
|
||||
|
||||
- name: "nullglob in subshell"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
(echo "inner:" *.nonexistent)
|
||||
echo "outer:" *.nonexistent
|
||||
|
||||
- name: "nullglob with for loop"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
for f in *.nonexistent; do
|
||||
echo "unexpected: $f"
|
||||
done
|
||||
echo "done"
|
||||
|
||||
- name: "nullglob with array assignment"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
arr=(*.nonexistent)
|
||||
echo "count: ${#arr[@]}"
|
||||
|
||||
- name: "nullglob with directory glob"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
echo "result:" nonexistent_dir/*/
|
||||
@@ -0,0 +1,333 @@
|
||||
name: "Filename expansion"
|
||||
cases:
|
||||
- name: "Single file expansion"
|
||||
stdin: "echo file1.txt"
|
||||
|
||||
- name: "Expansion with noglob"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
- path: "file2.txt"
|
||||
stdin: |
|
||||
set -f
|
||||
echo "*.txt:" *.txt
|
||||
|
||||
- name: "Multiple file expansion"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
- path: "file2.txt"
|
||||
stdin: |
|
||||
echo *.txt
|
||||
echo *."txt"
|
||||
|
||||
- name: "File expansion with nocaseglob"
|
||||
test_files:
|
||||
- path: "FILE1.TXT"
|
||||
- path: "file2.txt"
|
||||
stdin: |
|
||||
shopt -s nocaseglob
|
||||
echo "*.txt:" *.txt
|
||||
echo "*.TXT:" *.txt
|
||||
|
||||
- name: "Nested directory expansion"
|
||||
test_files:
|
||||
- path: "dir/file1.txt"
|
||||
stdin: "echo dir/*"
|
||||
|
||||
- name: "Multiple level directory expansion"
|
||||
test_files:
|
||||
- path: "dir/subdir/file1.txt"
|
||||
stdin: "echo dir/subdir/*.txt"
|
||||
|
||||
- name: "Expansion with no matches"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
stdin: "echo *.jpg"
|
||||
|
||||
- name: "Expansion with no matches + nullglob"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
stdin: |
|
||||
shopt -s nullglob
|
||||
echo "*.jpg:" *.jpg
|
||||
|
||||
- name: "Expansion with special characters"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
- path: "file2.txt"
|
||||
stdin: "echo file?.txt"
|
||||
|
||||
- name: "Expansion with brackets"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
- path: "file2.txt"
|
||||
stdin: "echo file[12].txt"
|
||||
|
||||
- name: "Expansion with range"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
- path: "file2.txt"
|
||||
stdin: "echo file[1-2].txt"
|
||||
|
||||
- name: "Expansion with negation"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
- path: "file2.txt"
|
||||
stdin: "echo file[!2].txt"
|
||||
|
||||
- name: "Expansion with tilde"
|
||||
env:
|
||||
HOME: /some/dir
|
||||
stdin: |
|
||||
echo ~/file1.txt
|
||||
echo ~/file1.txt:~/file1.txt
|
||||
|
||||
- name: "Expansion with dots"
|
||||
stdin: |
|
||||
echo ./file1.txt
|
||||
echo ../file1.txt
|
||||
|
||||
- name: "Expansion with mixed patterns"
|
||||
test_files:
|
||||
- path: "dir/subdir/file1.txt"
|
||||
stdin: "echo dir/*/file?.txt"
|
||||
|
||||
- name: "Expansion with escaped characters"
|
||||
test_files:
|
||||
- path: "file*.txt"
|
||||
stdin: "echo file\\*.txt"
|
||||
|
||||
- name: "Pathname expansion: extglob disabled"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "ab.txt"
|
||||
- path: "abc.txt"
|
||||
- path: "abd.txt"
|
||||
- path: "def.txt"
|
||||
- path: "abadac.txt"
|
||||
- path: "fabadac.txt"
|
||||
- path: "f.txt"
|
||||
- path: "script.sh"
|
||||
contents: |
|
||||
echo !(a*)
|
||||
echo "result: $?"
|
||||
|
||||
echo @(abc|abd).txt
|
||||
echo "result: $?"
|
||||
|
||||
echo ab?(c).txt
|
||||
echo "result: $?"
|
||||
|
||||
echo *(ab|ad|ac).txt
|
||||
echo "result: $?"
|
||||
|
||||
echo f+(ab|ad|ac).txt
|
||||
echo "result: $?"
|
||||
stdin: |
|
||||
shopt -u extglob
|
||||
chmod +x ./script.sh
|
||||
./script.sh
|
||||
|
||||
- name: "Pathname expansion: Inverted patterns"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "abc.txt"
|
||||
- path: "abd.txt"
|
||||
- path: "def.txt"
|
||||
stdin: |
|
||||
shopt -s extglob
|
||||
echo "1: " !(a*)
|
||||
echo "2: " !(abc.txt)
|
||||
echo "3: " !(abc)
|
||||
echo "4: " !(*)
|
||||
|
||||
- name: "Pathname expansion: Degenerate inverted pattern"
|
||||
test_files:
|
||||
- path: "abc.txt"
|
||||
- path: "abd.txt"
|
||||
- path: "def.txt"
|
||||
stdin: |
|
||||
shopt -s extglob
|
||||
echo !()
|
||||
|
||||
- name: "Pathname expansion: Extended patterns"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "abc.txt"
|
||||
- path: "abd.txt"
|
||||
stdin: |
|
||||
shopt -s extglob
|
||||
echo "1: " @(abc|abd).txt
|
||||
echo "2: " @(abc.txt)
|
||||
echo "3: " @(abc)
|
||||
echo "4: " @(|abc.txt)
|
||||
|
||||
- name: "Pathname expansion: Optional patterns"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "ab.txt"
|
||||
- path: "abc.txt"
|
||||
stdin: |
|
||||
shopt -s extglob
|
||||
echo ab?(c).txt
|
||||
|
||||
- name: "Pathname expansion: Star patterns"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "abadac.txt"
|
||||
- path: "ab.txt"
|
||||
stdin: |
|
||||
shopt -s extglob
|
||||
echo *(ab|ad|ac).txt
|
||||
|
||||
- name: "Pathname expansion: Plus patterns"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "fabadac.txt"
|
||||
- path: "f.txt"
|
||||
stdin: |
|
||||
shopt -s extglob
|
||||
echo f+(ab|ad|ac).txt
|
||||
|
||||
- name: "Pathname expansion: quoting"
|
||||
test_files:
|
||||
- path: test.txt
|
||||
- path: subdir/test.txt
|
||||
stdin: |
|
||||
echo "./test"*.txt
|
||||
echo "subdir"/*.txt
|
||||
echo "test.*"
|
||||
|
||||
- name: "Pathname expansion: dot files (no dotglob)"
|
||||
min_oracle_version: 5.2
|
||||
stdin: |
|
||||
touch .file
|
||||
touch .dir
|
||||
|
||||
shopt -u dotglob
|
||||
echo "* : " *
|
||||
echo "*i* : " *i*
|
||||
echo "./* : " ./*
|
||||
echo ".* : " .*
|
||||
echo "./.*: " ./.*
|
||||
|
||||
- name: "Glob results are sorted"
|
||||
test_files:
|
||||
- path: "cherry.txt"
|
||||
- path: "apple.txt"
|
||||
- path: "banana.txt"
|
||||
stdin: |
|
||||
echo *.txt
|
||||
|
||||
- name: "Multiple globs on one command line"
|
||||
test_files:
|
||||
- path: "a.txt"
|
||||
- path: "b.txt"
|
||||
- path: "x.log"
|
||||
- path: "y.log"
|
||||
stdin: |
|
||||
echo *.txt *.log
|
||||
|
||||
- name: "Glob does not expand in variable assignment"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
- path: "file2.txt"
|
||||
stdin: |
|
||||
x=*.txt
|
||||
echo "$x"
|
||||
|
||||
- name: "Glob expands in unquoted variable use"
|
||||
test_files:
|
||||
- path: "file1.txt"
|
||||
- path: "file2.txt"
|
||||
stdin: |
|
||||
x=*.txt
|
||||
echo $x
|
||||
|
||||
- name: "Glob with trailing slash matches directories"
|
||||
known_failure: true
|
||||
test_files:
|
||||
- path: "mydir/file.txt"
|
||||
- path: "myfile.txt"
|
||||
stdin: |
|
||||
echo */
|
||||
|
||||
- name: "Globstar disabled by default"
|
||||
known_failure: true
|
||||
test_files:
|
||||
- path: "a/b/c.txt"
|
||||
stdin: |
|
||||
shopt -u globstar
|
||||
echo **/c.txt
|
||||
|
||||
- name: "Globstar recursive matching"
|
||||
known_failure: true
|
||||
test_files:
|
||||
- path: "a/b/c.txt"
|
||||
- path: "a/d.txt"
|
||||
- path: "e.txt"
|
||||
stdin: |
|
||||
shopt -s globstar
|
||||
echo **/*.txt
|
||||
|
||||
- name: "GLOBIGNORE filters glob results"
|
||||
known_failure: true
|
||||
test_files:
|
||||
- path: "a.txt"
|
||||
- path: "b.txt"
|
||||
- path: "c.txt"
|
||||
stdin: |
|
||||
GLOBIGNORE="b.txt"
|
||||
echo *.txt
|
||||
|
||||
- name: "GLOBIGNORE with multiple patterns"
|
||||
known_failure: true
|
||||
test_files:
|
||||
- path: "a.txt"
|
||||
- path: "b.txt"
|
||||
- path: "c.txt"
|
||||
- path: "d.log"
|
||||
stdin: |
|
||||
GLOBIGNORE="b.txt:c.txt"
|
||||
echo *.txt
|
||||
echo *.log
|
||||
|
||||
- name: "GLOBIGNORE implicitly enables dotglob"
|
||||
known_failure: true
|
||||
min_oracle_version: 5.2
|
||||
test_files:
|
||||
- path: "a.txt"
|
||||
- path: ".hidden.txt"
|
||||
stdin: |
|
||||
GLOBIGNORE="a.txt"
|
||||
echo *.txt
|
||||
|
||||
- name: "Unsetting GLOBIGNORE restores behavior"
|
||||
known_failure: true
|
||||
test_files:
|
||||
- path: "a.txt"
|
||||
- path: "b.txt"
|
||||
stdin: |
|
||||
GLOBIGNORE="a.txt"
|
||||
echo *.txt
|
||||
unset GLOBIGNORE
|
||||
echo *.txt
|
||||
|
||||
- name: "Star does not match slash in path"
|
||||
test_files:
|
||||
- path: "dir/file.txt"
|
||||
- path: "top.txt"
|
||||
stdin: |
|
||||
echo *.txt
|
||||
|
||||
- name: "Pathname expansion: dot files (with dotglob)"
|
||||
min_oracle_version: 5.2
|
||||
stdin: |
|
||||
touch .file
|
||||
touch .dir
|
||||
|
||||
shopt -s dotglob
|
||||
echo "* : " *
|
||||
echo "*i* : " *i*
|
||||
echo "./* : " ./*
|
||||
echo ".* : " .*
|
||||
echo "./.*: " ./.*
|
||||
@@ -0,0 +1,174 @@
|
||||
name: "Patterns"
|
||||
cases:
|
||||
- name: "Expansion with escaped characters"
|
||||
test_files:
|
||||
- path: "file*.txt"
|
||||
stdin: "echo file\\*.txt"
|
||||
|
||||
- name: "Basic pattern matching"
|
||||
stdin: |
|
||||
test_pattern() {
|
||||
local str="$1"
|
||||
local pattern="$2"
|
||||
|
||||
if [[ "${str}" == ${pattern} ]]; then
|
||||
echo "Matched: ${str} using ${pattern}"
|
||||
else
|
||||
echo "No match: ${str} using ${pattern}"
|
||||
fi
|
||||
}
|
||||
|
||||
test_pattern "abc" "a*"
|
||||
test_pattern "abc" "b*"
|
||||
|
||||
test_pattern "?bc" "abc"
|
||||
test_pattern "?bc" "aabc"
|
||||
|
||||
test_pattern "ac" "[ab]c"
|
||||
test_pattern "bc" "[ab]c"
|
||||
test_pattern "cc" "[ab]c"
|
||||
|
||||
test_pattern "ad" "[a-c]d"
|
||||
test_pattern "bd" "[a-c]d"
|
||||
test_pattern "dd" "[a-c]d"
|
||||
|
||||
test_pattern "1" "[[:alpha:]]"
|
||||
test_pattern "a" "[[:alpha:]]"
|
||||
|
||||
test_pattern "a/b" "*b"
|
||||
test_pattern "a/b" "a/b"
|
||||
test_pattern "a/b" "*/*"
|
||||
|
||||
- name: "Extglob pattern matching"
|
||||
stdin: |
|
||||
shopt -s extglob
|
||||
|
||||
test_pattern() {
|
||||
local str="$1"
|
||||
local pattern="$2"
|
||||
|
||||
if [[ "${str}" == ${pattern} ]]; then
|
||||
echo "Matched: ${str} using ${pattern}"
|
||||
else
|
||||
echo "No match: ${str} using ${pattern}"
|
||||
fi
|
||||
}
|
||||
|
||||
test_pattern "aabc" "!(a*)"
|
||||
test_pattern "abc" "!(a*)"
|
||||
test_pattern "def" "!(a*)"
|
||||
|
||||
test_pattern "a.foo.tar.gz" "a.!(foo|bar).tar.gz"
|
||||
test_pattern "a.bar.tar.gz" "a.!(foo|bar).tar.gz"
|
||||
test_pattern "a.baz.tar.gz" "a.!(foo|bar).tar.gz"
|
||||
test_pattern "a.tar.tar.gz" "a.!(foo|bar).tar.gz"
|
||||
test_pattern "a..tar.gz" "a.!(foo|bar).tar.gz"
|
||||
test_pattern "a.tar.gz" "a.!(foo|bar).tar.gz"
|
||||
|
||||
test_pattern "abc" "@(abc|def)"
|
||||
test_pattern "def" "@(abc|def)"
|
||||
test_pattern "ghi" "@(abc|def)"
|
||||
|
||||
test_pattern "abc" "ab?(c)"
|
||||
test_pattern "ab" "ab?(c)"
|
||||
test_pattern "abd" "ab?(c)"
|
||||
|
||||
test_pattern "" "*(ab|ac)"
|
||||
test_pattern "ab" "*(ab|ac)"
|
||||
test_pattern "abab" "*(ab|ac)"
|
||||
test_pattern "ad" "*(ab|ac)"
|
||||
|
||||
test_pattern "" "+(ab|ac)"
|
||||
test_pattern "ab" "+(ab|ac)"
|
||||
test_pattern "abab" "+(ab|ac)"
|
||||
test_pattern "ad" "+(ab|ac)"
|
||||
|
||||
- name: "Extglob with escaping"
|
||||
stdin: |
|
||||
shopt -s extglob
|
||||
|
||||
[[ \( == +(\() ]] && echo "1. Matched"
|
||||
[[ x == +(\() ]] && echo "2. Matched"
|
||||
[[ \) == +(\)) ]] && echo "3. Matched"
|
||||
[[ x == +(\)) ]] && echo "4. Matched"
|
||||
|
||||
- name: "Patterns: quoting"
|
||||
stdin: |
|
||||
[[ "abc" == "a"* ]] && echo "1. Matched"
|
||||
[[ "abc" == a"*" ]] && echo "2. Matched"
|
||||
[[ "abc" == "a*" ]] && echo "3. Matched"
|
||||
|
||||
- name: "Patterns: escaped special characters"
|
||||
stdin: |
|
||||
myfunc() {
|
||||
if [[ $1 == \\* ]]; then
|
||||
echo "Matched: '$1'"
|
||||
else
|
||||
echo "Did *not* match: '$1'"
|
||||
fi
|
||||
}
|
||||
|
||||
myfunc abc
|
||||
myfunc "*"
|
||||
|
||||
- name: "Pattern matching: character ranges"
|
||||
stdin: |
|
||||
[[ "x" == [a-z] ]] && echo "1. Matched"
|
||||
[[ "x" == [0-9] ]] && echo "2. Matched"
|
||||
[[ "-" == [---] ]] && echo "3. Matched"
|
||||
[[ "*" == [\*-\*] ]] && echo "4. Matched"
|
||||
[[ "-" == [\*-\*] ]] && echo "5. Matched"
|
||||
[[ "." == [\*-/] ]] && echo "6. Matched"
|
||||
|
||||
- name: "Pattern matching: invalid character ranges"
|
||||
stdin: |
|
||||
[[ "a" == [f-a] ]] && echo "1. Matched"
|
||||
[[ "b" == [f-a] ]] && echo "2. Matched"
|
||||
[[ "f" == [f-a] ]] && echo "3. Matched"
|
||||
[[ "g" == [f-a] ]] && echo "4. Matched"
|
||||
|
||||
- name: "Pattern matching: escaped hyphen in bracket"
|
||||
stdin: |
|
||||
[[ "-" == [a\-\*] ]] && echo "1. Matched"
|
||||
[[ "*" == [a\-\*] ]] && echo "2. Matched"
|
||||
[[ "a" == [a\-\*] ]] && echo "3. Matched"
|
||||
[[ "b" == [a\-\*] ]] && echo "4. Matched"
|
||||
[[ "a" == [a\-f] ]] && echo "5. Matched"
|
||||
[[ "b" == [a\-f] ]] && echo "6. Matched"
|
||||
[[ "f" == [a\-f] ]] && echo "7. Matched"
|
||||
|
||||
- name: "Pattern matching: character sets"
|
||||
stdin: |
|
||||
[[ "x" == [abc] ]] && echo "1. Matched"
|
||||
[[ "x" == [xyz] ]] && echo "2. Matched"
|
||||
[[ "x" == [^xyz] ]] && echo "3. Matched"
|
||||
[[ "x" == [!xyz] ]] && echo "4. Matched"
|
||||
[[ "(" == [\(] ]] && echo "5. Matched"
|
||||
[[ "+" == [+-] ]] && echo "6. Matched"
|
||||
[[ "[" == [\[] ]] && echo "7. Matched"
|
||||
[[ "]" == [\]] ]] && echo "8. Matched"
|
||||
[[ "^" == [\^] ]] && echo "9. Matched"
|
||||
|
||||
- name: "Pattern matching: character classes"
|
||||
stdin: |
|
||||
[[ "1" == [[:digit:]] ]] && echo "1. Matched"
|
||||
[[ "1" == [[:alpha:]] ]] && echo "2. Matched"
|
||||
|
||||
- name: "Pattern matching: case sensitivity"
|
||||
stdin: |
|
||||
shopt -u nocasematch
|
||||
[[ "abc" == "ABC" ]] && echo "1. Matched"
|
||||
[[ "abc" == "[A-Z]BC" ]] && echo "2. Matched"
|
||||
|
||||
shopt -s nocasematch
|
||||
[[ "abc" == "ABC" ]] && echo "3. Matched"
|
||||
[[ "abc" == "[A-Z]BC" ]] && echo "4. Matched"
|
||||
|
||||
- name: "Pattern matching: stars in negative extglobs"
|
||||
stdin: |
|
||||
shopt -s extglob
|
||||
[[ 'd' == !(*d)d ]] && echo "1. Matched"
|
||||
|
||||
- name: "Pattern matching: quotes in character class"
|
||||
stdin: |
|
||||
[[ f == ['f'] ]] && echo "1. Matched"
|
||||
@@ -0,0 +1,60 @@
|
||||
name: "Pipeline"
|
||||
cases:
|
||||
- name: "Basic pipe"
|
||||
stdin: |
|
||||
echo hi | grep -l h
|
||||
|
||||
- name: "Longer pipe"
|
||||
stdin: |
|
||||
echo hi | grep h | wc -l
|
||||
|
||||
- name: "Invert result"
|
||||
stdin: |
|
||||
! false
|
||||
echo "! false: $?"
|
||||
! true
|
||||
echo "! true: $?"
|
||||
|
||||
- name: "Standalone negation (no command)"
|
||||
stdin: |
|
||||
!
|
||||
echo "After !: $?"
|
||||
! !
|
||||
echo "After ! !: $?"
|
||||
|
||||
- name: "Exit codes for piped commands"
|
||||
test_files:
|
||||
- path: "script.sh"
|
||||
executable: true
|
||||
contents: |
|
||||
#!/bin/sh
|
||||
(cat; echo -n "-> $1")
|
||||
exit $1
|
||||
stdin: |
|
||||
./script.sh 10 | ./script.sh 0 | ./script.sh 33
|
||||
|
||||
- name: "Side effects in pipe commands"
|
||||
stdin: |
|
||||
var=0
|
||||
echo "var: ${var}"
|
||||
{ var=1; }
|
||||
echo "var: ${var}"
|
||||
{ var=2; echo hi; } | cat
|
||||
echo "var: ${var}"
|
||||
echo hi | { var=3; cat; }
|
||||
echo "var: ${var}"
|
||||
|
||||
- name: "pipeline extension"
|
||||
stdin: |
|
||||
echo -e "hello" |& wc -l
|
||||
cat dfdfgdfgdf |& wc -l
|
||||
foo() { cat dfgdfg; } |& wc -l
|
||||
|
||||
- name: "Possible broken pipe case"
|
||||
stdin: |
|
||||
echo hello | var=value
|
||||
|
||||
- name: "printf broken pipe returns 141 in PIPESTATUS"
|
||||
stdin: |
|
||||
printf '%s\n' {0..10000} | x=1
|
||||
echo "Last: $?, PIPESTATUS: ${PIPESTATUS[*]}"
|
||||
@@ -0,0 +1,17 @@
|
||||
name: "Process"
|
||||
common_test_files:
|
||||
- path: "process-helpers.sh"
|
||||
source_path: "../../utils/process-helpers.sh"
|
||||
|
||||
cases:
|
||||
- name: "Basic process"
|
||||
stdin: |
|
||||
# TODO(jobs): Figure out how to make this work elsewhere
|
||||
if [[ "$(uname)" != "Linux" ]]; then
|
||||
echo "Skipping test on non-Linux platform"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
source process-helpers.sh
|
||||
echo "pid != ppid: $(( $(get-pid) != $(get-ppid) ))"
|
||||
echo "pid == pgrp: $(( $(get-pid) != $(get-pgrp) ))"
|
||||
@@ -0,0 +1,200 @@
|
||||
name: "Prompt"
|
||||
cases:
|
||||
- name: "Simple prompts"
|
||||
stdin: |
|
||||
prompt='$ '
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
prompt='a\r\n> '
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
prompt='\\'
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
prompt='\?'
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
prompt='\81'
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
- name: "Working dir based prompts"
|
||||
stdin: |
|
||||
cd /usr
|
||||
|
||||
prompt='\w '
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
prompt='\W '
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
arr=("\w" "\w")
|
||||
echo "Prompt: '${arr@P}'"
|
||||
echo "Prompt: '${arr[@]@P}'"
|
||||
echo "Prompt: '${arr[*]@P}'"
|
||||
|
||||
- name: "Non-printing chars"
|
||||
stdin: |
|
||||
prompt='\[\]Prompt>\[\]'
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
- name: "Hostname in prompts"
|
||||
stdin: |
|
||||
prompt='\h '
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
prompt='\H '
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
- name: "Shell name"
|
||||
stdin: |
|
||||
prompt='\s'
|
||||
[[ "${prompt@P}" == "$(basename $0)" ]]
|
||||
|
||||
- name: "Shell version info"
|
||||
stdin: |
|
||||
prompt='\v'
|
||||
[[ "${prompt@P}" =~ ^\d+\.\d+$ ]] && echo "Version is well-formatted"
|
||||
|
||||
prompt='\V'
|
||||
[[ "${prompt@P}" == ^\d+\.\d+\.\d+$ ]] && echo "Release is correct"
|
||||
|
||||
- name: "Simple date"
|
||||
stdin: |
|
||||
prompt='\d'
|
||||
[[ "${prompt@P}" == $(date +'%a %b %d') ]] && echo '\d date matches'
|
||||
|
||||
- name: "Date format with plain string"
|
||||
stdin: |
|
||||
prompt='\D{something}'
|
||||
echo "Date format with plain string: '${prompt@P}'"
|
||||
|
||||
- name: "Date format with year"
|
||||
stdin: |
|
||||
prompt='\d{%Y}'
|
||||
echo "Date format with year: '${prompt@P}'"
|
||||
|
||||
- name: "Time format: A"
|
||||
stdin: |
|
||||
prompt='\A'
|
||||
expanded=${prompt@P}
|
||||
roundtripped=$(date --date="${expanded}" +'%H:%M')
|
||||
[[ ${expanded} == ${roundtripped} ]] && echo "Time matches"
|
||||
|
||||
- name: "Time format: @"
|
||||
stdin: |
|
||||
prompt='\@'
|
||||
expanded=${prompt@P}
|
||||
roundtripped=$(date --date="${expanded}" +'%I:%M %p')
|
||||
[[ ${expanded} == ${roundtripped} ]] && echo "Time matches"
|
||||
|
||||
- name: "Time format: T"
|
||||
stdin: |
|
||||
prompt='\T'
|
||||
expanded=${prompt@P}
|
||||
roundtripped=$(date --date="${expanded}" +'%I:%M:%S')
|
||||
[[ ${expanded} == ${roundtripped} ]] && echo "Time matches"
|
||||
|
||||
- name: "Time format: t"
|
||||
stdin: |
|
||||
prompt='\t'
|
||||
expanded=${prompt@P}
|
||||
roundtripped=$(date --date="${expanded}" +'%H:%M:%S')
|
||||
[[ ${expanded} == ${roundtripped} ]] && echo "Time matches"
|
||||
|
||||
- name: "Current history number"
|
||||
known_failure: true
|
||||
stdin: |
|
||||
prompt='\!'
|
||||
expanded=${prompt@P}
|
||||
roundtripped=$(history | tail -n 1 | cut -d ' ' -f 1)
|
||||
[[ ${expanded} == ${roundtripped} ]] && echo "History number matches"
|
||||
|
||||
- name: "Current command number"
|
||||
known_failure: true # Issue #471
|
||||
stdin: |
|
||||
prompt='\#'
|
||||
expanded=${prompt@P}
|
||||
echo "${expanded}"
|
||||
|
||||
- name: "Escaped dollar-sign"
|
||||
stdin: |
|
||||
prompt='\$'
|
||||
echo "Prompt: '${prompt@P}'"
|
||||
|
||||
- name: "Var in prompt"
|
||||
stdin: |
|
||||
var=value
|
||||
|
||||
prompt="\$var"
|
||||
|
||||
shopt -s promptvars
|
||||
echo "Prompt (dollar-sign not escaped, with promptvars): ${prompt@P}"
|
||||
|
||||
shopt -u promptvars
|
||||
echo "Prompt (dollar-sign not escaped, without promptvars): ${prompt@P}"
|
||||
|
||||
prompt="\\\$var"
|
||||
|
||||
shopt -s promptvars
|
||||
echo "Prompt (dollar-sign escaped, with promptvars): ${prompt@P}"
|
||||
|
||||
shopt -u promptvars
|
||||
echo "Prompt (dollar-sign escaped, without promptvars): ${prompt@P}"
|
||||
|
||||
- name: "Tilde in prompt"
|
||||
stdin: |
|
||||
HOME=$(pwd)
|
||||
|
||||
prompt='~'
|
||||
|
||||
shopt -s promptvars
|
||||
echo "Prompt (with promptvars): ${prompt@P}"
|
||||
|
||||
shopt -u promptvars
|
||||
echo "Prompt (without promptvars): ${prompt@P}"
|
||||
|
||||
- name: "Command substitution in prompt"
|
||||
stdin: |
|
||||
prompt='$(echo Hello)'
|
||||
echo "Without spaces: ${prompt@P}"
|
||||
|
||||
prompt=' $(echo Hello) '
|
||||
echo "With spaces: ${prompt@P}"
|
||||
|
||||
- name: "Backquoted command substitution in prompt"
|
||||
stdin: |
|
||||
prompt='`echo Hello`'
|
||||
echo "Without spaces: ${prompt@P}"
|
||||
|
||||
prompt=' `echo Hello` '
|
||||
echo "With spaces: ${prompt@P}"
|
||||
|
||||
- name: "Parameter expansion with non-printing chars"
|
||||
stdin: |
|
||||
var=value
|
||||
prompt="\${var:+\[text\]}"
|
||||
echo "Conditional (set): '${prompt@P}'"
|
||||
|
||||
var=
|
||||
prompt="\${var:+\[text\]}"
|
||||
echo "Conditional (unset): '${prompt@P}'"
|
||||
|
||||
- name: "Command substitution in prompt and exit code"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
PS1='$(echo Hello)> '
|
||||
false
|
||||
echo $?
|
||||
|
||||
- name: "Brackets and var"
|
||||
stdin: |
|
||||
prompt='\[$var\]\u > '
|
||||
echo "Prompt: ${prompt@P}"
|
||||
|
||||
- name: "Brackets and var (interactive mode)"
|
||||
args: ["-i"]
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
prompt='\[$var\]\u > '
|
||||
echo "Prompt: ${prompt@P}"
|
||||
@@ -0,0 +1,277 @@
|
||||
name: "Redirection"
|
||||
cases:
|
||||
- name: "Redirection to null"
|
||||
stdin: |
|
||||
echo hi >/dev/null
|
||||
|
||||
- name: "Output redirection to file"
|
||||
stdin: |
|
||||
echo hi >out.txt
|
||||
ls out.txt
|
||||
cat out.txt
|
||||
|
||||
- name: "Output redirection to file (append)"
|
||||
stdin: |
|
||||
echo hi >>out.txt
|
||||
echo there >>out.txt
|
||||
ls out.txt
|
||||
cat out.txt
|
||||
|
||||
- name: "Input redirection from file"
|
||||
test_files:
|
||||
- path: "in.txt"
|
||||
contents: |
|
||||
Something here.
|
||||
stdin: |
|
||||
cat <in.txt
|
||||
|
||||
- name: "Redirection to fd"
|
||||
stdin: |
|
||||
echo hi >&2
|
||||
|
||||
- name: "Redirecting stdout + stderr to file"
|
||||
stdin: |
|
||||
function write_both() {
|
||||
echo "$1"
|
||||
echo "$2" >&2
|
||||
}
|
||||
|
||||
echo "[Writing]"
|
||||
write_both stdout stderr >& file.txt
|
||||
echo "[Contents of file.txt]"
|
||||
cat file.txt
|
||||
|
||||
- name: "Invalid stdout + stderr redir"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
function write_both() {
|
||||
echo "$1"
|
||||
echo "$2" >&2
|
||||
}
|
||||
|
||||
echo "[Writing]"
|
||||
write_both stdout stderr 2>& file.txt
|
||||
echo "[Contents of file.txt]"
|
||||
cat file.txt
|
||||
|
||||
- name: "Redirecting an fd"
|
||||
stdin: |
|
||||
echo hi-out 1>stdout.txt
|
||||
echo hi-error 2>stderr.txt >&2
|
||||
|
||||
- name: "Almost redirecting but not actually redirecting"
|
||||
stdin: |
|
||||
# Checks to make sure the '1' isn't treated as a file descriptor
|
||||
echo 1 >&2
|
||||
|
||||
- name: "Redirection with arbitrary fd number"
|
||||
stdin: |
|
||||
echo hi 4>file.txt >&4
|
||||
|
||||
- name: "Process substitution: basic"
|
||||
ignore_stdout: true # Specific fd-based paths may vary across runs
|
||||
stdin: |
|
||||
shopt -u -o posix
|
||||
echo <(:) >(:)
|
||||
echo <(:) <(:)
|
||||
echo >(:) >(:)
|
||||
|
||||
- name: "Process substitution: not in simple commands"
|
||||
known_failure: true # Known to fail because we are only handling them in simple commands now
|
||||
stdin: |
|
||||
shopt -u -o posix
|
||||
for f in <(echo hi); do echo $f; done
|
||||
|
||||
- name: "Process substitution: builtins"
|
||||
stdin: |
|
||||
source <(echo VAR=100)
|
||||
echo "var: ${VAR}"
|
||||
|
||||
- name: "Process substitution: input redirection"
|
||||
stdin: |
|
||||
shopt -u -o posix
|
||||
cat < <(echo hi)
|
||||
|
||||
- name: "Process substitution: output redirection"
|
||||
skip: true # TODO(redirection): Test is flaky; needs work.
|
||||
stdin: |
|
||||
shopt -u -o posix
|
||||
echo hi > >(wc -l)
|
||||
echo hi >> >(wc -l)
|
||||
|
||||
- name: "Process substitution: input"
|
||||
stdin: |
|
||||
shopt -u -o posix
|
||||
var="value"
|
||||
cat <(var="updated"; echo ${var})
|
||||
echo "Done."
|
||||
echo "${var}"
|
||||
|
||||
- name: "Input redirection from /dev/null"
|
||||
stdin: |
|
||||
cat < /dev/null
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "Redirect stderr to /dev/null"
|
||||
stdin: |
|
||||
ls /non-existent-path 2>/dev/null
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "Append redirection to /dev/null"
|
||||
stdin: |
|
||||
echo hi >>/dev/null
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "Redirect to /dev/null via variable"
|
||||
stdin: |
|
||||
target=/dev/null
|
||||
echo hi >"$target"
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "Read and write /dev/null"
|
||||
stdin: |
|
||||
cat <>/dev/null
|
||||
echo "exit: $?"
|
||||
|
||||
- name: "Redirect stdout and stderr"
|
||||
stdin: |
|
||||
ls -d . non-existent-dir &>/dev/null
|
||||
ls -d . non-existent-dir &>>/dev/null
|
||||
|
||||
- name: "Process substitution: input + output"
|
||||
stdin: |
|
||||
shopt -u -o posix
|
||||
cp <(echo hi) >(cat)
|
||||
|
||||
- name: "Redirection in command substitution"
|
||||
stdin: |
|
||||
echo $(echo hi >&2) 2>stderr.txt
|
||||
|
||||
- name: "Redirection in command substitution to non-standard fd"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo $(echo hi >&3) 3>output.txt
|
||||
|
||||
- name: "Redirection in command substitution in braces to non-standard fd"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
{ : $(echo hi >&3); } 3>output.txt
|
||||
|
||||
- name: "Redirection in command substitution in subshell to non-standard fd"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
( : $(echo hi >&3); ) 3>output.txt
|
||||
|
||||
- name: "Redirection failure"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo hi > /non-existent-dir/file.txt; echo following-command
|
||||
|
||||
- name: "Redirection for opening read and write"
|
||||
stdin: |
|
||||
echo hi >file.txt
|
||||
cat <>file.txt
|
||||
|
||||
- name: "Close stdout"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
echo "This should not appear" 1>&-
|
||||
echo "This should appear"
|
||||
|
||||
- name: "Close stderr"
|
||||
stdin: |
|
||||
non-existent-command 2>&-
|
||||
echo "This should appear"
|
||||
|
||||
- name: "Close output file descriptor"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
# Open a new output fd (3).
|
||||
exec 3>test.txt
|
||||
# Write to the file.
|
||||
echo "test" >&3
|
||||
# Close the file.
|
||||
exec 3>&-
|
||||
# Make sure we can't write to it.
|
||||
echo "after close" >&3 || echo "Cannot write to closed fd"
|
||||
|
||||
- name: "Close input file descriptor"
|
||||
ignore_stderr: true
|
||||
test_files:
|
||||
- path: "input.txt"
|
||||
contents: |
|
||||
line1
|
||||
line2
|
||||
stdin: |
|
||||
# Open an input file descriptor (3).
|
||||
exec 3<input.txt
|
||||
# Read from the file.
|
||||
read line <&3
|
||||
echo "Read: $line"
|
||||
# Close the file descriptor.
|
||||
exec 3<&-
|
||||
# Make sure we can't read from it.
|
||||
read line <&3 2>/dev/null || echo "Cannot read from closed fd"
|
||||
|
||||
- name: "Duplicate input file descriptor"
|
||||
test_files:
|
||||
- path: "input.txt"
|
||||
contents: |
|
||||
test input line
|
||||
stdin: |
|
||||
# Open an input file descriptor (3).
|
||||
exec 3<input.txt
|
||||
# Duplicate it to another descriptor (4).
|
||||
exec 4<&3
|
||||
# Read from the new descriptor.
|
||||
read line <&4
|
||||
echo "Read: $line"
|
||||
|
||||
- name: "Input file descriptor duplication from stdin"
|
||||
skip: true # Needs revisiting; doesn't behave as seems to have been expected.
|
||||
stdin: |
|
||||
exec 3<&0
|
||||
echo "test" | { read line <&3; echo "Read: $line"; }
|
||||
|
||||
- name: "Multiple file descriptor redirections"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
# Open multiple new output fds
|
||||
exec 3>fd3.txt 4>fd4.txt 5>fd5.txt
|
||||
# Write to the new fds
|
||||
echo "fd3" >&3
|
||||
echo "fd4" >&4
|
||||
echo "fd5" >&5
|
||||
# Close the fds
|
||||
exec 3>&- 4>&- 5>&-
|
||||
# Read from the files to verify
|
||||
echo "[3]"
|
||||
cat fd3.txt
|
||||
echo "[4]"
|
||||
fd4.txt
|
||||
echo "[5]"
|
||||
fd5.txt
|
||||
|
||||
- name: "High file descriptor numbers"
|
||||
stdin: |
|
||||
# Open high numbered file descriptor
|
||||
echo "test" 10>fd10.txt >&10
|
||||
# Dump the file
|
||||
echo "[10]"
|
||||
cat fd10.txt
|
||||
|
||||
- name: "Inheritance of fds from parent"
|
||||
test_files:
|
||||
- path: "test.sh"
|
||||
contents: |
|
||||
echo hi >&3
|
||||
stdin: |
|
||||
args=""
|
||||
|
||||
# NOTE: For now, brush doesn't inherit arbitrary all open fds by default like bash does.
|
||||
# We need to explicitly specify which fds to inherit.
|
||||
if [[ $BRUSH_VERSION ]]; then
|
||||
args+=" --inherit-fd=3"
|
||||
fi
|
||||
|
||||
$0 $args test.sh 3>output.txt
|
||||
@@ -0,0 +1,39 @@
|
||||
name: "Simple commands"
|
||||
cases:
|
||||
- name: "Simple command"
|
||||
stdin: |
|
||||
echo 1
|
||||
|
||||
- name: "Simple command with reserved word args"
|
||||
stdin: |
|
||||
echo then
|
||||
|
||||
- name: "Command that's a directory"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
mkdir test-dir
|
||||
./test-dir
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Non-existent command"
|
||||
ignore_stderr: true
|
||||
stdin: |
|
||||
./non-existent-command
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Redirection of errors"
|
||||
stdin: |
|
||||
./non-existent-command 2>/dev/null
|
||||
echo "Result: $?"
|
||||
|
||||
- name: "Simple command with non-existent cwd"
|
||||
# N.B. We intentionally fail here because there's no safe way to pick an
|
||||
# alternate working directory that we are okay with. To our knowledge,
|
||||
# there's no easy way to hold onto the current working directory past
|
||||
# its deletion.
|
||||
known_failure: true
|
||||
stdin: |
|
||||
mkdir test-dir
|
||||
cd test-dir
|
||||
rmdir $(pwd)
|
||||
ls
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user