Merge nucleic/sleek-thistle-egret-fyej into dev

This commit is contained in:
2026-07-18 05:19:31 -07:00
commit b6be87b72d
677 changed files with 102939 additions and 0 deletions
@@ -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: $?"