Bash · 10146 bytes Raw Blame History
1 #!/bin/sh
2 # =====================================
3 # POSIX Special Parameters Gap Tests
4 # =====================================
5 # Tests for POSIX special parameters and expansion
6 # Split from posix_compliance_gaps.sh for better organization
7
8 # Colors (POSIX-compliant way)
9 RED='\033[0;31m'
10 GREEN='\033[0;32m'
11 YELLOW='\033[1;33m'
12 BLUE='\033[0;34m'
13 NC='\033[0m'
14
15 # Test identification
16 TEST_PREFIX="[gaps-special]"
17 CURRENT_SECTION=""
18 TEST_NUM=0
19
20 PASSED=0
21 FAILED=0
22 SKIPPED=0
23 FAILED_TESTS_LIST=""
24 DEBUG_INFO=""
25
26 # Get script directory (POSIX way)
27 SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
28 SHELL_BIN="${SHELL_BIN:?ERROR: SHELL_BIN must be set}"
29 BASH_REF="${BASH_REF:-bash}"
30
31 # Check if shell binary exists
32 if [ ! -x "$SHELL_BIN" ]; then
33 printf "${RED}ERROR${NC}: shell binary not found at $SHELL_BIN\n"
34 printf "Please set SHELL_BIN or set SHELL_BIN environment variable\n"
35 exit 1
36 fi
37
38 pass() {
39 TEST_NUM=$((TEST_NUM + 1))
40 printf "${GREEN}✓ PASS${NC} ${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}: %s\n" "$1"
41 PASSED=$((PASSED + 1))
42 }
43
44 fail() {
45 TEST_NUM=$((TEST_NUM + 1))
46 TEST_ID="${TEST_PREFIX} ${CURRENT_SECTION}.${TEST_NUM}"
47 printf "${RED}✗ FAIL${NC} ${TEST_ID}: %s\n" "$1"
48 FAILED_TESTS_LIST="${FAILED_TESTS_LIST} ${TEST_ID}: $1\n"
49 if [ -n "$2" ]; then printf " posix: %s\n" "$2"; fi
50 if [ -n "$3" ]; then printf " shell: %s\n" "$3"; fi
51 FAILED=$((FAILED + 1))
52 }
53
54 section() {
55 CURRENT_SECTION=$(echo "$1" | grep -oE '^[0-9]+' || echo "0")
56 TEST_NUM=0
57 printf "\n${BLUE}==========================================\n%s\n==========================================${NC}\n" "$1"
58 }
59
60 normalize_output() { sed -e 's|^[^ ]*/[a-z]*sh[0-9]*: |sh: |' -e 's|^[a-z]*sh[0-9]*: |sh: |' -e 's/line [0-9]*: //'; }
61
62 compare_posix_output() {
63 test_name="$1"; command="$2"
64 posix_out=$("$BASH_REF" -c "$command" 2>&1 | normalize_output)
65 shell_out=$("$SHELL_BIN" -c "$command" 2>&1 | normalize_output)
66 if [ "$posix_out" = "$shell_out" ]; then pass "$test_name"
67 else
68 fail "$test_name" "$posix_out" "$shell_out"
69 # Accumulate debug for CI summary
70 DEBUG_INFO="${DEBUG_INFO}DEBUG [$test_name]: cmd='$command'\n"
71 DEBUG_INFO="${DEBUG_INFO} bash: '${posix_out}' hex=$(printf '%s' "$posix_out" | od -A x -t x1z | head -1)\n"
72 DEBUG_INFO="${DEBUG_INFO} shell: '${shell_out}' hex=$(printf '%s' "$shell_out" | od -A x -t x1z | head -1)\n"
73 fi
74 }
75
76 # ============================================================================
77 # SPECIAL PARAMETERS
78 # ============================================================================
79
80 section "1. POSITIONAL PARAMETERS"
81 compare_posix_output "dollar at" 'set -- a b c; echo "$@"'
82 compare_posix_output "dollar star" 'set -- a b c; echo "$*"'
83 compare_posix_output "dollar hash" 'set -- a b c; echo $#'
84 compare_posix_output "dollar at with IFS" "IFS=:; set -- a b c; echo \"\$*\""
85 compare_posix_output "dollar star vs at unquoted" "set -- a b c; for x in \$*; do echo \$x; done | wc -l"
86 compare_posix_output "dollar at quoted iteration" "set -- 'a b' 'c d'; for x in \"\$@\"; do echo \$x; done | wc -l"
87 compare_posix_output "dollar hash after shift" "set -- a b c; shift; echo \$#"
88
89 section "2. SHELL STATUS PARAMETERS"
90 compare_posix_output "dollar question" 'true; echo $?'
91 compare_posix_output "dollar pid" 'echo $$ | grep -cE "^[0-9]+$"'
92 compare_posix_output "dollar zero" 'echo $0 | grep -c .'
93 compare_posix_output "dollar dash shows options" "echo \$- | grep -c '[a-z]'"
94 compare_posix_output "dollar dollar is numeric" "echo \$\$ | grep -c '^[0-9]*\$'"
95 compare_posix_output "dollar zero is set" "echo \${0:-none} | grep -c '.'"
96
97 # ============================================================================
98 # PARAMETER EXPANSION
99 # ============================================================================
100
101 section "3. DEFAULT VALUES"
102 compare_posix_output "pe default" 'unset x; echo ${x:-default}'
103 compare_posix_output "pe assign" 'unset x; echo ${x:=assigned}; echo $x'
104 compare_posix_output "pe error" '(unset x; echo ${x:?msg}) 2>/dev/null; echo $?'
105 compare_posix_output "pe alt" 'x=val; echo ${x:+alt}'
106 compare_posix_output "default empty" 'x=""; echo ${x:-default}'
107 compare_posix_output "default unset" 'unset x; echo ${x:-default}'
108 compare_posix_output "default set" 'x="value"; echo ${x:-default}'
109
110 section "4. STRING LENGTH"
111 compare_posix_output "pe length" 'x=hello; echo ${#x}'
112 compare_posix_output "length of empty" 'x=""; echo ${#x}'
113 compare_posix_output "length of one" 'x="a"; echo ${#x}'
114 compare_posix_output "length of special" 'x="a b c"; echo ${#x}'
115
116 section "5. PATTERN REMOVAL"
117 compare_posix_output "pe suffix" 'x=file.txt; echo ${x%.txt}'
118 compare_posix_output "pe prefix" 'x=/path/file; echo ${x##*/}'
119 compare_posix_output "suffix remove" 'x="file.txt"; echo ${x%.txt}'
120 compare_posix_output "prefix remove" 'x="prefix_name"; echo ${x#prefix_}'
121 compare_posix_output "longest suffix" 'x="a.b.c"; echo ${x%%.*}'
122 compare_posix_output "longest prefix" 'x="a.b.c"; echo ${x##*.}'
123
124 section "6. NESTED EXPANSION"
125 compare_posix_output "nested default" "unset A; B=inner; echo \${A:-\${B}}"
126 compare_posix_output "nested length" "VAR=hello; echo \${#VAR}"
127 compare_posix_output "nested pattern removal" "VAR=/usr/local/bin; echo \${VAR#\${VAR%/*}}"
128 compare_posix_output "multiple expansions" "A=foo; B=bar; echo \${A}\${B}"
129 compare_posix_output "nested with quotes" "A='a b'; echo \"\${A}\""
130
131 # ============================================================================
132 # IFS SPLITTING
133 # ============================================================================
134
135 section "7. IFS SPLITTING"
136 compare_posix_output "ifs default" 'x="a b c"; set -- $x; echo $#'
137 compare_posix_output "ifs colon" 'IFS=:; x="a:b:c"; set -- $x; echo $#'
138 compare_posix_output "ifs empty" 'IFS=""; x="abc"; set -- $x; echo $#'
139 compare_posix_output "ifs star" 'IFS=:; set -- a b c; echo "$*"'
140
141 # ============================================================================
142 # COMMAND SUBSTITUTION
143 # ============================================================================
144
145 section "8. COMMAND SUBSTITUTION"
146 compare_posix_output "cmdsub basic" 'echo $(echo hello)'
147 compare_posix_output "cmdsub backtick" 'echo `echo hello`'
148 compare_posix_output "cmdsub nested" 'echo $(echo $(echo deep))'
149 compare_posix_output "cmdsub quoted" 'echo "$(echo hello world)"'
150 compare_posix_output "cmdsub multi" 'echo $(echo a; echo b)'
151
152 section "9. COMMAND SUBSTITUTION VARIANTS"
153 compare_posix_output "cmd sub echo" 'echo $(echo hello)'
154 compare_posix_output "cmd sub pwd" 'echo $(pwd | grep -c "/")'
155 compare_posix_output "cmd sub math" 'echo $(($(echo 5) + 3))'
156 compare_posix_output "cmd sub in var" 'x=$(echo test); echo $x'
157 compare_posix_output "backtick equiv" 'echo `echo hello`'
158 compare_posix_output "cmd sub whitespace" 'echo "$(echo " spaces ")"'
159 compare_posix_output "cmd sub multiline" 'echo "$(echo -e "a\nb")" | wc -l'
160 compare_posix_output "cmd sub exit code" '$(exit 0); echo $?'
161 compare_posix_output "cmd sub fail code" '$(exit 1); echo $?'
162
163 # ============================================================================
164 # TILDE EXPANSION
165 # ============================================================================
166
167 section "10. TILDE EXPANSION"
168 compare_posix_output "tilde in assignment" "VAR=~/test; echo \$VAR | grep -c '^/'"
169 compare_posix_output "tilde in middle no expand" "echo a~b | grep -c '~'"
170
171 # ============================================================================
172 # VARIABLE ASSIGNMENT CONTEXTS
173 # ============================================================================
174
175 section "11. VARIABLE ASSIGNMENT"
176 compare_posix_output "simple assign" 'x=5; echo $x'
177 compare_posix_output "multi assign" 'x=1 y=2 z=3; echo $x $y $z'
178 compare_posix_output "assign in subshell" '(x=inner; echo $x); echo ${x:-unset}'
179 compare_posix_output "assign export" 'export X=exported; printenv X 2>/dev/null || echo $X'
180 compare_posix_output "assign readonly" 'readonly X=constant; echo $X'
181 compare_posix_output "assign with cmd" 'X=$(echo value); echo $X'
182 compare_posix_output "assign quoted" 'X="with spaces"; echo "$X"'
183 compare_posix_output "assign empty" 'X=""; echo "[$X]"'
184 compare_posix_output "assign special" 'X="$HOME"; echo ${X:+set}'
185
186 # ============================================================================
187 # PIPELINES
188 # ============================================================================
189
190 section "12. PIPELINES"
191 compare_posix_output "pipe two" 'echo hello | cat'
192 compare_posix_output "pipe three" 'echo hello | cat | cat'
193 compare_posix_output "pipe with grep" 'echo hello | grep -o h'
194 compare_posix_output "pipe word count" 'echo "a b c" | wc -w'
195 compare_posix_output "pipe line count" 'printf "a\nb\nc\n" | wc -l'
196 compare_posix_output "pipe sort" 'printf "c\na\nb\n" | sort | head -1'
197 compare_posix_output "pipe uniq" 'printf "a\na\nb\n" | uniq | wc -l'
198 compare_posix_output "pipe head" 'printf "1\n2\n3\n4\n5\n" | head -2'
199 compare_posix_output "pipe tail" 'printf "1\n2\n3\n4\n5\n" | tail -2'
200 compare_posix_output "pipe tr" 'echo abc | tr a-z A-Z'
201
202 # ============================================================================
203 # COMMAND NAME RESOLUTION
204 # ============================================================================
205
206 section "13. COMMAND NAME RESOLUTION"
207 compare_posix_output "function overrides echo" "echo() { printf 'function\n'; }; echo test | grep -c function"
208 compare_posix_output "command -v finds function" "func() { :; }; command -v func | grep -c func"
209 compare_posix_output "command bypasses function" "echo() { printf 'func\n'; }; command echo test"
210
211 # Summary
212 printf "\n==========================================\n"
213 printf "SPECIAL PARAMETERS GAP TEST RESULTS\n"
214 printf "==========================================\n"
215 printf "${GREEN}Passed:${NC} %d\n" "$PASSED"
216 printf "${RED}Failed:${NC} %d\n" "$FAILED"
217 printf "Total: %d\n" "$((PASSED + FAILED))"
218 if [ "$FAILED" -gt 0 ]; then
219 printf "\n${RED}Failed tests:${NC}\n%b" "$FAILED_TESTS_LIST"
220 if [ -n "$DEBUG_INFO" ]; then
221 printf "\n${YELLOW}Debug info:${NC}\n%b" "$DEBUG_INFO"
222 fi
223 exit 1
224 fi
225 exit 0