Files
sausage/userland/brush/source/brush-shell/tests/cases/compat/builtins/declare.yaml
T
2026-11-11 22:04:16 +00:00

1461 lines
42 KiB
YAML

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 adjacent single quotes"
stdin: |
(testvar="a''b" && declare -p testvar && declare | grep testvar=)
echo "-------------------------"
(testvar="''" && declare -p testvar && declare | grep testvar=)
echo "-------------------------"
(testvar="'a" && declare -p testvar && declare | grep testvar=)
echo "-------------------------"
(testvar="a'" && declare -p testvar && declare | grep testvar=)
echo "-------------------------"
(declare -a arr=("a''b" "'") && declare -p arr)
echo "-------------------------"
(declare -A m=([k]="a''b") && declare -p m)
- 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: "Appending to an integer wraps around on overflow"
stdin: |
declare -i n=1
n+=9223372036854775807
echo "$n"
declare -ai arr=(1)
arr[0]+=9223372036854775807
declare -p arr
declare -Ai assoc=([key]=-2)
assoc[key]+=-9223372036854775807
declare -p assoc
- name: "Declare integer with non-integer string"
stdin: |
declare -i var=value
declare -p var
- name: "Removing integer attribute prevents arithmetic evaluation"
stdin: |
declare -i var=5
declare +i var=1+2
declare -p var
- name: "Invalid integer declaration does not evaluate value"
stdin: |
side_effect=0
declare -i 'invalid-name=side_effect=1' 2>/dev/null
echo "status: $?"
echo "side effect: $side_effect"
- name: "Readonly integer declaration does not evaluate value"
stdin: |
side_effect=0
readonly var=1
declare -i 'var=side_effect=1' 2>/dev/null
echo "status: $?"
echo "side effect: $side_effect"
- name: "Update integer with non-integer string"
stdin: |
declare -i var=10
declare -p var
var=value
declare -p var
- name: "Update integer indexed array with non-integer string"
stdin: |
declare -ai arr=()
declare -p arr
arr[0]="value"
declare -p arr
- name: "Update integer associative 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: "Appending via declare"
stdin: |
declare var="value"
declare -p var
declare var+="appended"
declare -p var
- name: "Multiple declare operands expand before assignments are applied"
stdin: |
unset first second
declare first=one second=$first
declare -p first second
- name: "Declare command substitutions expand left to right exactly once"
stdin: |
marker=marker.txt
: > "$marker"
declare first=$(printf one; printf x >> "$marker") second=$(read -r contents < "$marker"; printf %s "$contents")
declare -p first second
read -r contents < "$marker"
echo "marker: $contents"
- name: "Declare expansion error occurs before any operand is applied"
known_failure: true # Requires EXIT traps to fire at subshell exit; brush only runs them
# when the top-level shell exits, so the trap below never observes the state.
stdin: |
state=state.txt
: > "$state"
(trap 'declare -p first > "$state" 2>/dev/null || printf unset > "$state"' EXIT; set -u; declare first=one second=$missing) 2>/dev/null
echo "status: $?"
read -r contents < "$state"
echo "state: ${contents:-empty}"
- name: "Aliases preserve declaration assignment expansion"
stdin: |
shopt -s expand_aliases
alias d=declare
alias e=export
value=world
d declared=$value
e exported=$value
declare -p declared exported
- name: "Disabled declare uses ordinary argument expansion"
stdin: |
enable -n declare
value=world
declare disabled=$value 2>/dev/null
echo "status: $?"
enable declare
declare -p disabled 2>/dev/null || echo unset
- 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 functions with compound bodies"
stdin: |
f() { local x=1; if [ 1 ]; then echo "a b"; fi; for i in 1 2; do :; done; }
declare -f f
g() { if [ 1 ]; then echo a; echo b; else echo c; fi; }
declare -f g
h() { while :; do break; done; { echo a; }; case x in a) echo b;; esac; }
declare -f h
i() { until :; do echo x; done; ( echo s; echo t ); }
declare -f i
- name: "Displaying a function whose compound commands have redirections"
known_failure: true # brush-parser's Display puts no space between a compound command and its
# redirections (`fi2>& 1`, which doesn't parse again; `}2> /dev/null`), and
# prints `2>& 1` and `|cat` where bash prints `2>&1` and `| cat`
stdin: |
f() { if true; then :; fi 2>&1; while false; do :; done 3>/dev/null; { :; } 2>/dev/null; }
declare -f f
g() { [[ -n x ]] |& cat; }
declare -f g
- name: "Displaying a function with case patterns"
stdin: |
f() {
case "$1" in
a|b|c) echo abc;;
(d|e) echo de;;
single) echo one;;
*) ;;
esac
}
declare -f f
- 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: "Declare -p error shows expanded assignment"
stdin: |
value=world
output=$(declare -p missing=$value 2>&1)
status=$?
output=${output##*declare: }
echo "status: $status"
echo "error: $output"
- 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
- name: "Single-quoted scalar assignment"
known_failure: true # TODO: declare improvements required
stdin: |
declare 'x=hello'
echo "$x"
- name: "Single-quoted scalar with dollar sign preserved"
known_failure: true # TODO: declare improvements required
stdin: |
Y=world
declare 'x=$Y'
echo "$x"
- name: "Double-quoted scalar assignment expands"
known_failure: true # TODO: declare improvements required
stdin: |
Y=world
declare "x=$Y"
echo "$x"
- name: "Single-quoted array assignment with -a"
known_failure: true # TODO: declare improvements required
stdin: |
declare -a 'arr=(1 2 3)'
echo "${arr[@]}"
echo "${#arr[@]}"
- name: "Single-quoted array assignment with deferred expansion"
known_failure: true # TODO: declare improvements required
stdin: |
X="a b"
declare -a 'arr=(${X})'
echo "${arr[@]}"
echo "${#arr[@]}"
- name: "Single-quoted array assignment with IFS splitting"
known_failure: true # TODO: declare improvements required
stdin: |
X="a,b"
IFS=, declare -a 'arr=(${X})'
echo "${arr[@]}"
echo "${#arr[@]}"
- name: "Double-quoted compound assignment with escaped expansion"
known_failure: true # TODO: declare improvements required
stdin: |
X="a b"
declare -a "arr=(\${X})"
echo "${arr[@]}"
echo "${#arr[@]}"
- name: "Variable-generated compound assignment syntax"
known_failure: true # TODO: declare improvements required
stdin: |
X="a b"
value='(${X})'
declare -a "arr=$value"
echo "${arr[@]}"
echo "${#arr[@]}"
- name: "Command-substitution-generated compound assignment syntax"
known_failure: true # TODO: declare improvements required
stdin: |
X="a b"
declare -a "arr=$(printf '(%s)' '${X}')"
echo "${arr[@]}"
echo "${#arr[@]}"
- name: "Single-quoted compound value in parsed assignment is reparsed"
known_failure: true # TODO: declare improvements required
stdin: |
X="a b"
declare -a arr='(${X})'
echo "${arr[@]}"
echo "${#arr[@]}"
- name: "Single-quoted array assignment without -a is scalar"
known_failure: true # TODO: declare improvements required
stdin: |
declare 'arr=(1 2 3)'
declare -p arr
- name: "Single-quoted array assignment Gentoo rpm.eclass pattern"
known_failure: true # TODO: declare improvements required
stdin: |
RPM_COMPRESS_TYPE="lzma zstd"
IFS=, declare -a 'types=(${RPM_COMPRESS_TYPE})'
echo "${types[@]}"
echo "${#types[@]}"
- name: "local -a single-quoted array assignment"
known_failure: true # TODO: declare improvements required
stdin: |
f() {
X="a b"
local -a 'arr=(${X})'
echo "${arr[@]}"
}
f
- name: "Single-quoted indexed element assignment"
known_failure: true # TODO: declare improvements required
stdin: |
declare 'arr[0]=hello'
declare -p arr
- name: "Quoted array assignment does not execute value as shell code"
known_failure: true # TODO: declare improvements required
stdin: |
payload='(x); printf "INJECTED\n"; #'
declare -a "arr=$payload"
echo "status: $?"
declare -p arr
- name: "Quoted array assignment propagates assignment failure"
stdin: |
declare -ar arr=(old)
declare -a 'arr=(new)' 2>/dev/null
echo "status: $?"
declare -p arr
- name: "Quoted array assignment applies uppercase attribute"
known_failure: true # TODO: declare improvements required
stdin: |
declare -au 'arr=(ab Cd)'
declare -p arr
- name: "Quoted array assignment applies integer attribute"
# Integer assignment expression evaluation is a broader core assignment issue.
known_failure: true
stdin: |
declare -ai 'arr=(1+2 3*4)'
declare -p arr
- name: "Quoted assignment uses existing indexed array type"
known_failure: true # TODO: declare improvements required
stdin: |
arr=(old)
declare 'arr=(new values)'
declare -p arr
- name: "Quoted assignment uses existing associative array type"
known_failure: true # TODO: declare improvements required
stdin: |
declare -A map=([old]=value)
declare 'map=([new]=value [other]=two)'
declare -p map
- name: "Quoted assignment uses declaration target scope"
known_failure: true # TODO: declare improvements required
stdin: |
arr=(global)
f() {
local arr=scalar
declare -g 'arr=(new values)'
declare -p arr
}
f
declare -p arr
- name: "Quoted global assignment ignores local array type"
known_failure: true # TODO: declare improvements required
stdin: |
arr=global
f() {
local -a arr=(local)
declare -g 'arr=(new values)'
declare -p arr
}
f
declare -p arr
- name: "Quoted local declaration does not inherit global array type"
known_failure: true # TODO: declare improvements required
stdin: |
arr=(global)
f() {
declare 'arr=(local values)'
declare -p arr
}
f
declare -p arr
- name: "Single-quoted nonzero indexed element assignment"
known_failure: true # TODO: declare improvements required
stdin: |
declare 'arr[5]=hello'
declare -p arr
- name: "Single-quoted indexed element append assignment"
known_failure: true # TODO: declare improvements required
stdin: |
declare -a arr=([0]=one)
declare 'arr[0]+=two'
declare -p arr
- name: "Single-quoted associative element append assignment"
known_failure: true # TODO: declare improvements required
stdin: |
declare -A map=([key]=one)
declare 'map[key]+=two'
declare -p map
- name: "Single-quoted arithmetic indexed element assignment"
known_failure: true # TODO: declare improvements required
stdin: |
declare 'arr[2+2]=value'
declare -p arr
- name: "Single-quoted variable arithmetic indexed element assignment"
known_failure: true # TODO: declare improvements required
stdin: |
index=3
declare 'arr[index+1]=value'
declare -p arr
- name: "Single-quoted parameter-expanded indexed element assignment"
known_failure: true # TODO: declare improvements required
stdin: |
index=3
declare 'arr[$index+1]=value'
declare -p arr
- name: "Single-quoted command-substitution indexed element assignment"
known_failure: true # TODO: declare improvements required
stdin: |
index=3
declare 'arr[$(echo 2)+index]=value'
declare -p arr
- name: "Single-quoted parameter-expanded associative element assignment"
known_failure: true # TODO: declare improvements required
stdin: |
key="space key"
declare -A 'map[$key]=value'
declare -p map
- name: "Single-quoted indexed subscript expands without re-expanding value"
known_failure: true # TODO: declare improvements required
stdin: |
index=2
value=expanded
declare 'arr[$index]=$value'
declare -p arr
- name: "Single-quoted associative subscript expands without re-expanding value"
known_failure: true # TODO: declare improvements required
stdin: |
key="space key"
value=expanded
declare -A 'map[$key]=$value'
declare -p map
- name: "Single-quoted append assignment"
known_failure: true # TODO: declare improvements required
stdin: |
declare 'text=one'
declare 'text+=two' 2>/dev/null
echo "status: $?"
declare -p text
- name: "Single-quoted array-like scalar assignment with +a"
known_failure: true # TODO: declare improvements required
stdin: |
declare +a 'arr=(1 2)'
echo "status: $?"
declare -p arr
- name: "Cannot remove indexed array attribute with +a"
known_failure: true # TODO: declare improvements required
stdin: |
declare -a arr=(1 2)
declare +a arr 2>/dev/null
echo "status: $?"
declare -p arr
- name: "Cannot update indexed array while removing attribute with +a"
stdin: |
declare -a arr=(1 2)
declare +a 'arr=(3 4)' 2>/dev/null
echo "status: $?"
declare -p arr
- name: "Single-quoted associative array assignment"
known_failure: true # TODO: declare improvements required
stdin: |
declare -A 'map=([alpha]=one [beta]=two)' 2>/dev/null
echo "status: $?"
declare -p map 2>/dev/null
#
# Regression coverage: a shell function may shadow a declaration builtin. The builtin's
# arguments must still reach the function intact.
#
- name: "Function shadowing declare receives its arguments"
stdin: |
declare() { echo "shadow declare: [$*] count=$#"; }
declare x=1
declare a b c
unset -f declare
- name: "Function shadowing local and readonly receives its arguments"
stdin: |
readonly() { echo "shadow readonly: [$*] count=$#"; }
readonly r=1
unset -f readonly
f() {
local() { echo "shadow local: [$*] count=$#"; }
local l=2
unset -f local
}
f
- name: "Function shadowing declare is reached through command"
known_failure: true # TODO: declare improvements required
stdin: |
declare() { echo "shadow declare: [$*]"; }
command declare cx=1
unset -f declare
#
# Regression coverage: an assignment operand is expanded before the command runs, so the
# command's own temporary environment and redirections cannot affect that expansion.
#
- name: "Temporary environment is not visible to a declare operand"
stdin: |
unset FOO
FOO=fromprefix declare y=$FOO
declare -p y
- name: "Temporary IFS does not split an unquoted compound declare operand"
stdin: |
X="a:b"
IFS=: declare -a arr=($X)
declare -p arr
- name: "Temporary IFS does split a quoted compound declare operand"
known_failure: true # TODO: declare improvements required
stdin: |
X="a,b"
IFS=, declare -a 'arr=(${X})'
declare -p arr
- name: "Redirections do not apply to a declare operand's command substitution"
stdin: |
declare w=$(echo to-stderr >&2; echo val) 2>/dev/null
declare -p w
#
# Regression coverage: an expansion error in an operand is fatal, exactly as it is for an
# ordinary assignment statement.
#
- name: "Unset variable in a declare operand is fatal under set -u"
ignore_stderr: true
stdin: |
( set -u; declare x=$undefined; echo "NOT REACHED" )
echo "status: $?"
- name: "Unset variable in a local operand is fatal under set -u"
ignore_stderr: true
stdin: |
f() { local x=$undefined; echo "NOT REACHED"; }
( set -u; f; echo "NOT REACHED EITHER" )
echo "status: $?"
#
# Regression coverage: `$_` and command tracing are the interpreter's responsibility, so they
# apply to every form of these builtins, not only to the forms that assign.
#
- name: "$_ - declare display and query forms"
stdin: |
value=world
echo seed > /dev/null
declare -p PWD > /dev/null; printf 'declare -p: [%s]\n' "$_"
echo seed > /dev/null
declare > /dev/null; printf 'declare bare: [%s]\n' "$_"
echo seed > /dev/null
f() { :; }
declare -f f > /dev/null; printf 'declare -f: [%s]\n' "$_"
echo seed > /dev/null
declare declared=$value; printf 'declare assign: [%s]\n' "$_"
- name: "$_ - export and readonly display forms"
stdin: |
echo seed > /dev/null
export > /dev/null; printf 'export bare: [%s]\n' "$_"
echo seed > /dev/null
readonly > /dev/null; printf 'readonly bare: [%s]\n' "$_"
echo seed > /dev/null
readonly -p > /dev/null; printf 'readonly -p: [%s]\n' "$_"
#
# Regression coverage: `declare array[index]` versus an assignment whose value merely looks
# like a subscript.
#
- name: "Assignment value ending in a bracket is not a subscripted name"
known_failure: true # TODO: declare improvements required
stdin: |
v='[a]'
declare "x=$v"
declare -p x
- name: "Assignment value containing brackets is preserved"
known_failure: true # TODO: declare improvements required
stdin: |
declare 'a[1]=v[2]'
declare -p a
- name: "Bare subscripted name declares an array"
stdin: |
declare 'arr[5]'
declare -p arr
#
# Regression coverage: `+a`/`+A` report why they refuse to remove an array attribute.
#
- name: "Removing array attribute reports an error"
known_failure: true # TODO: declare improvements required
ignore_stderr: true
stdin: |
declare -a arr=(1 2)
declare +a arr
echo "status: $?"
declare -p arr
- name: "Removing array attribute from a declared but unset array fails"
known_failure: true # TODO: declare improvements required
ignore_stderr: true
stdin: |
declare -a arr
declare +a arr
echo "status: $?"
declare -p arr
- name: "Removing associative array attribute fails"
known_failure: true # TODO: declare improvements required
ignore_stderr: true
stdin: |
declare -A map=([k]=v)
declare +A map
echo "status: $?"
declare -p map
#
# Regression coverage: subscript expansion for operands that arrive already expanded.
#
- name: "Parsed associative subscript is expanded"
stdin: |
declare -A m
key="a b"
declare m[$key]=v
declare -p m
# These two pin the asymmetry in how subscripts are resolved: a `name[sub]=` subscript is
# expanded again by the builtin, while a compound element's key is already final. The key
# deliberately expands to command-substitution syntax, so a second expansion is visible in the
# result and the two cases cannot be collapsed without failing.
- name: "Subscript of a parsed element assignment is expanded a second time"
known_failure: true # TODO: declare improvements required
stdin: |
key='$(echo SECOND)'
declare -A m
declare m[$key]=v
declare -p m
- name: "Compound element key is not expanded a second time"
stdin: |
key='$(echo SECOND)'
declare -A m=([$key]=v)
declare -p m
- name: "Quoted element subscript is expanded exactly once"
known_failure: true # TODO: declare improvements required
stdin: |
key='$(echo SECOND)'
declare -A m
declare 'm[$key]=v'
declare -p m
- name: "Parsed indexed subscript is arithmetically evaluated"
known_failure: true # TODO: declare improvements required
stdin: |
index=3
declare arr[index+1]=v
declare -p arr
- name: "Compound associative key is expanded exactly once"
stdin: |
key="a b"
declare -A m=([$key]=v)
declare -p m
- name: "Compound indexed key is arithmetically evaluated"
known_failure: true # TODO: declare improvements required
stdin: |
declare -a a=([2+2]=v)
declare -p a
- name: "Compound value assigned to an array element targets the whole array"
# A shell drops the subscript and applies the compound assignment to the whole array; brush
# stores the compound text as the element's literal value.
known_failure: true
stdin: |
declare -a n=(1)
declare -a 'n[0]=(9 8)'
echo "status: $?"
declare -p n
- name: "Empty subscript is rejected"
# brush's bare-name array form accepts an empty subscript; a shell rejects it.
known_failure: true
ignore_stderr: true
stdin: |
declare 'b[]'
echo "status: $?"
declare -p b 2>&1
#
# An attribute option given as both `-` and `+`.
#
- name: "An attribute given as both - and + is not set"
stdin: |
declare +x -x zzx=1; declare -p zzx
declare -x +x zzy=1; declare -p zzy
declare +i -i zzi=1+1; declare -p zzi
declare zzo=1; declare +x -x zzo; declare -p zzo
- name: "An attribute given as both - and + is removed"
stdin: |
declare -x zzx=1; declare -x +x zzx; declare -p zzx
declare -i zzi=1; declare +i -i zzi; declare -p zzi
#
# Attribute-filtered display.
#
- name: "Display filtered by readonly and trace attributes"
stdin: |
declare -r zzro=1
declare -t zztr=2
declare -rt zzboth=3
zzplain=4
echo "-r:"; declare -pr | grep zz | sort
echo "-t:"; declare -pt | grep zz | sort
- name: "Display filtered by several attributes lists any that match"
# A shell combines attribute filters as a union: `declare -rt` lists variables that are
# readonly or traced. brush intersects them, listing only variables that are both.
known_failure: true
stdin: |
declare -r zzro=1
declare -t zztr=2
declare -rt zzboth=3
declare -prt | grep zz | sort
- name: "Display is not filtered by a plus attribute option"
stdin: |
declare -x zzex=1
zzplain=2
declare +x | grep zz | sort
- name: "Display is filtered by -X, not +X, when both are given"
stdin: |
declare -x zzex=1
declare -i zzint=2
zzplain=3
declare -p -x +x | grep zz
declare -p +x -x | grep zz
declare -p -i +x | grep zz
- name: "Combining -a and -A is rejected"
# A shell treats `-a` and `-A` together as an invalid option combination. brush accepts it and
# lets `-A` win, which lands on the same variable type but without the diagnostic or status.
known_failure: true
ignore_stderr: true
stdin: |
declare -aA both=(x)
echo "status: $?"
declare -p both 2>&1
- name: "Combining -a and -A with a compound operand still creates the variable"
# bash reports the invalid option combination and still creates the variable from the
# compound operand, as an associative array. brush creates nothing.
known_failure: true
ignore_stderr: true
stdin: |
declare -aA both=(x)
echo "status: $?"
declare -p both 2>/dev/null
echo "declare_status: $?"
- name: "Display diagnostics name the builtin as invoked"
known_failure: true # TODO: declare improvements required
stdin: |
check() {
case $2 in
*"$1") echo "$1: ok";;
*) echo "$1: unexpected: $2";;
esac
}
check "declare: nosuch: not found" "$(declare -p nosuch 2>&1)"
check "typeset: nosuch: not found" "$(typeset -p nosuch 2>&1)"
f() { check "local: nosuch: not found" "$(local -p nosuch 2>&1)"; }
f
- name: "Display quotes an associative key of @"
# bash quotes both `*` and `@` when displaying an associative key; brush quotes only `*`.
known_failure: true
stdin: |
declare -A zzat=(['@']=at ['*']=star [k]=v)
declare -p zzat
- name: "Display filtered by array attributes includes unassigned arrays"
known_failure: true # TODO: declare improvements required
stdin: |
declare -a zzempty
declare -A zzemptyA
declare -a zzfull=(1)
declare -A zzfullA=([k]=v)
echo "-a:"; declare -pa | grep zz | sort
echo "-A:"; declare -pA | grep zz | sort
- name: "Declaration diagnostics name the builtin and quote the operand"
known_failure: true # TODO: declare improvements required
# Covers every diagnostic the declaration builtins emit, so a new one cannot quietly omit the
# builtin's name. The leading "file: line N:" a shell prefixes is stripped.
stdin: |
strip() { sed -E 's/^[^ ]*: line [0-9]+: //'; }
local x=1 2>&1 | strip
v=w; declare -p missing=$v 2>&1 | strip
declare -p nosuchvar 2>&1 | strip
declare -a da=(1); declare +a da 2>&1 | strip
declare 'bad-name=1' 2>&1 | strip
f() { local 'bad-name2=1' 2>&1 | strip; }; f
readonly 'bad-name3=1' 2>&1 | strip
export -f nosuchfn 2>&1 | strip
export 'e[0]=1' 2>&1 | strip
- name: "declare -fx exports a function"
stdin: |
f() { :; }
count_exported() { env | grep -c "^BASH_FUNC_f"; }
count_exported
declare -fx f; echo "rc=$?"
count_exported
declare +x -f f; echo "rc=$?"
count_exported
- name: "declare -f with both -x and +x leaves a function unexported"
stdin: |
f() { :; }
count_exported() { env | grep -c "^BASH_FUNC_f"; }
declare -f -x +x f; echo "rc=$?"
count_exported
declare -fx f; declare -f +x -x f; echo "rc=$?"
count_exported
- name: "declare -F -x lists only exported functions"
known_failure: true # TODO(declare): brush lists every function
stdin: |
f() { :; }
g() { :; }
declare -fx g
declare -F -x
- name: "declare -f with attribute flags doesn't display the function"
stdin: |
f() { echo body; }
echo "[$(declare -ft f)]"; echo "rc=$?"
echo "[$(declare -fx f)]"; echo "rc=$?"
- name: "declare -f with attribute flags on a missing function"
stdin: |
declare -ft nosuchfn 2>&1; echo "rc=$?"
declare -fx nosuchfn 2>&1; echo "rc=$?"
- name: "declare -ft records the trace attribute"
known_failure: true # TODO(declare): brush accepts -t on functions but doesn't track it
stdin: |
f() { :; }
declare -ft f
declare -F
- name: "Declaration builtins quote an invalid operand in full"
stdin: |
strip() { sed -E 's/^[^ ]*: line [0-9]+: //'; }
declare 1x=2 2>&1 | strip
typeset 1x 2>&1 | strip
readonly 'a b=1' 2>&1 | strip
f() { local 1x=2 2>&1 | strip; }; f
- name: "Quoted declaration with = in its subscript"
known_failure: true # TODO(declare): a quoted declaration whose subscript contains `=` isn't assigned
ignore_stderr: true
stdin: |
declare -A m
declare 'm[a=b]=1'
echo "declare: $?"
declare -p m
- name: "Quoted declaration with text after its subscript"
ignore_stderr: true
stdin: |
declare 'x[1]]=2'
echo "declare: $?"
declare 'y[1]z=2'
echo "declare: $?"
declare -p x y
- name: "Declaration expansion depends on the command name as written"
known_failure: true # TODO(declare): declaration expansion is decided by the expanded command name, not the name as written
stdin: |
v="a b"
declare x=$v; declare -p x
"declare" y=$v; declare -p y
\declare z=$v; declare -p z
cmd=declare
$cmd w=$v; declare -p w
- name: "A quoted declaration's subscript is expanded and evaluated"
known_failure: true # TODO(declare): a word operand's subscript is used as written
stdin: |
i=3
declare 'a[$i]=x'
declare -p a
k=foo
declare -A m
declare "m[\$k]=x"
declare -p m
- name: "A command word that expands to nothing doesn't make the next word a declaration utility"
known_failure: true # TODO(declare): brush decides from the first word after expansion
stdin: |
v="a b"
e=
$e declare x=$v
declare -p x
- name: "A function shadowing declare receives a compound assignment's name"
known_failure: true # TODO(declare): bash's parser hands the function `a` for `a=(1 2)`; brush hands it the whole word
stdin: |
declare() { echo "shadow declare: [$*]"; }
declare a=(1 2)
unset -f declare
- name: "Declaration builtins take dash-prefixed operands after -- as names"
stdin: |
strip() { sed -E 's/^[^ ]*: line [0-9]+: //'; }
{ declare -- -x; echo "declare -- -x: $?"; } 2>&1 | strip
{ typeset -- -x; echo "typeset -- -x: $?"; } 2>&1 | strip
{ readonly -- -x; echo "readonly -- -x: $?"; } 2>&1 | strip
f() { { local -- -x; echo "local -- -x: $?"; } 2>&1 | strip; }
f
declare -- y=-z
declare -p y
- name: "Declaration builtins take dash-prefixed words after the first operand as names"
stdin: |
declare a=1 -x 2>/dev/null; echo "declare: $?"
declare -p a
typeset t=1 -r 2>/dev/null; echo "typeset: $?"
declare -p t
readonly c=1 +x 2>/dev/null; echo "readonly: $?"
declare -p c
f() { local x=1 -r 2>/dev/null; echo "local: $?"; x=2; declare -p x; }
f
- name: "the integer attribute evaluates an arithmetic value on assignment"
known_failure: true # TODO: integer-attributed assignments are not evaluated arithmetically
stdin: |
declare -i x=1+1
declare -p x
declare -i y
y=2*3
declare -p y
declare -ai a=(1+1 2*3)
declare -p a
#
# `declare -g` names a global, but a shell resolves a subscripted operand's kind against the
# local that shadows it and then assigns there -- so the global is left untouched.
#
- name: "declare -g with a subscripted operand under a shadowing local"
stdin: |
declare -A g=([x]=1)
f() {
local -a g=(9)
declare -g g[y]=2
declare -p g
}
f
declare -p g
#
# A declaration naming a nameref acts on the variable it references.
#
- name: "declare assigns through a nameref"
known_failure: true # TODO: declaration builtins do not follow namerefs
stdin: |
declare -n nr=tgt
declare nr=hello
declare -p tgt nr
- name: "a subscripted assignment through a nameref targets the referenced array"
known_failure: true # TODO: subscripted assignments do not follow namerefs
stdin: |
declare -A m
declare -n r=m
r[key]=v
declare -p m
declare r[other]=w
declare -p m
#
# Every one of `declare`'s options has a `+X` form, including the four that select a mode rather
# than an attribute. For those four `+X` means "off", which is already the default, so they are
# accepted and (except for `+p`) do nothing: `+f` and `+F` drop the function restriction, `+g`
# drops global creation, and `+p` prints just as `-p` does. brush models those four as plain
# boolean flags with no plus form, so the word is rejected outright and the command exits 2.
# This also takes down any combined form that includes one of them, such as `+ft`.
#
- name: "declare +f drops the function restriction"
known_failure: true # TODO(declare): -f/-F/-g/-p have no `+X` form
stdin: |
strip() { sed -E 's/^[^ ]*: line [0-9]+: //'; }
f() { :; }
{ declare +fp f; echo "status: $?"; } 2>&1 | strip
- name: "declare +F drops names-only display and declares a variable instead"
known_failure: true # TODO(declare): -f/-F/-g/-p have no `+X` form
stdin: |
f() { :; }
declare -F f
declare +F f
echo "status: $?"
declare -p f
- name: "declare +g declares locally inside a function"
known_failure: true # TODO(declare): -f/-F/-g/-p have no `+X` form
stdin: |
g=outer
f() { declare +g g=inner; echo "inside: $g"; }
f
echo "outside: $g"
- name: "declare +p displays just as -p does"
known_failure: true # TODO(declare): -f/-F/-g/-p have no `+X` form
stdin: |
x=1
declare -p x
declare +p x
echo "status: $?"
- name: "declare +ft combines a mode option and an attribute option"
known_failure: true # TODO(declare): -f/-F/-g/-p have no `+X` form
stdin: |
f() { :; }
declare -ft f
declare +ft f
echo "status: $?"
declare -pF f
declare -p f