Skip to content

Commit d68762c

Browse files
committed
fix: make array iteration safe for strict mode (set -u)
When bashunit runs with BASHUNIT_STRICT_MODE=true (set -u), iterating over arrays could trigger "unbound variable" errors. This fix implements safe array iteration patterns: - For value iteration: Use "${array[@]+"${array[@]}"}" pattern which expands to nothing when array is empty, preventing unbound errors - For indexed iteration: Check array size before loop with [ "${#array[@]}" -gt 0 ] to avoid iterating over empty arrays - Add default values (:-) when accessing array elements to handle unset elements gracefully Changes affect: - src/benchmark.sh: Safe iteration over benchmark result arrays - src/env.sh: Safe iteration over environment variable keys - src/helpers.sh: Safe iteration over function names - src/main.sh: Safe iteration over command-line arguments - src/parallel.sh: Safe iteration over result files - src/reports.sh: Safe iteration over test results - src/runner.sh: Safe iteration over test files and functions - src/test_doubles.sh: Safe iteration over mocked functions All tests passing locally (738 passed, 3 skipped, 7 incomplete). Fixes strict mode compatibility without breaking existing functionality.
1 parent d7cd950 commit d68762c

8 files changed

Lines changed: 45 additions & 33 deletions

File tree

src/benchmark.sh

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function bashunit::benchmark::run_function() {
8787

8888
local sum=0
8989
local d
90-
for d in "${durations[@]}"; do
90+
for d in "${durations[@]+"${durations[@]}"}"; do
9191
sum=$(bashunit::math::calculate "$sum + $d")
9292
done
9393
local avg=$(bashunit::math::calculate "$sum / ${#durations[@]}")
@@ -113,7 +113,7 @@ function bashunit::benchmark::print_results() {
113113

114114
local has_threshold=false
115115
local val
116-
for val in "${_BASHUNIT_BENCH_MAX_MILLIS[@]}"; do
116+
for val in "${_BASHUNIT_BENCH_MAX_MILLIS[@]+"${_BASHUNIT_BENCH_MAX_MILLIS[@]}"}"; do
117117
if [[ -n "$val" ]]; then
118118
has_threshold=true
119119
break
@@ -126,13 +126,17 @@ function bashunit::benchmark::print_results() {
126126
printf '%-40s %6s %6s %10s\n' "Name" "Revs" "Its" "Avg(ms)"
127127
fi
128128

129-
local i=0
129+
if [ "${#_BASHUNIT_BENCH_NAMES[@]}" -eq 0 ]; then
130+
return
131+
fi
132+
133+
local i
130134
for i in "${!_BASHUNIT_BENCH_NAMES[@]}"; do
131-
local name="${_BASHUNIT_BENCH_NAMES[$i]}"
132-
local revs="${_BASHUNIT_BENCH_REVS[$i]}"
133-
local its="${_BASHUNIT_BENCH_ITS[$i]}"
134-
local avg="${_BASHUNIT_BENCH_AVERAGES[$i]}"
135-
local max_ms="${_BASHUNIT_BENCH_MAX_MILLIS[$i]}"
135+
local name="${_BASHUNIT_BENCH_NAMES[$i]:-}"
136+
local revs="${_BASHUNIT_BENCH_REVS[$i]:-}"
137+
local its="${_BASHUNIT_BENCH_ITS[$i]:-}"
138+
local avg="${_BASHUNIT_BENCH_AVERAGES[$i]:-}"
139+
local max_ms="${_BASHUNIT_BENCH_MAX_MILLIS[$i]:-}"
136140

137141
if [[ -z "$max_ms" ]]; then
138142
printf '%-40s %6s %6s %10s\n' "$name" "$revs" "$its" "$avg"

src/env.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,13 @@ function bashunit::env::print_verbose() {
245245
local max_length=0
246246

247247
local key=""
248-
for key in "${keys[@]}"; do
248+
for key in "${keys[@]+"${keys[@]}"}"; do
249249
if (( ${#key} > max_length )); then
250250
max_length=${#key}
251251
fi
252252
done
253253

254-
for key in "${keys[@]}"; do
254+
for key in "${keys[@]+"${keys[@]}"}"; do
255255
bashunit::internal_log "$key=${!key}"
256256
printf "%s:%*s%s\n" "$key" $((max_length - ${#key} + 1)) "" "${!key}"
257257
done

src/helpers.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ function bashunit::helper::find_total_tests() {
361361
local -a provider_data=()
362362
local provider_data_count=0
363363
local fn_name line
364-
for fn_name in "${functions_to_run[@]}"; do
364+
for fn_name in "${functions_to_run[@]+"${functions_to_run[@]}"}"; do
365365
provider_data=()
366366
provider_data_count=0
367367
while IFS=" " read -r line; do

src/main.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ function bashunit::main::cmd_test() {
180180
else
181181
# Test mode: process file paths and extract inline filters
182182
local arg=""
183-
for arg in "${raw_args[@]}"; do
183+
for arg in "${raw_args[@]+"${raw_args[@]}"}"; do
184184
local parsed_path parsed_filter
185185
{
186186
read -r parsed_path
@@ -321,7 +321,7 @@ function bashunit::main::cmd_bench() {
321321
# Expand positional arguments
322322
if [[ "$raw_args_count" -gt 0 ]]; then
323323
local arg file
324-
for arg in "${raw_args[@]}"; do
324+
for arg in "${raw_args[@]+"${raw_args[@]}"}"; do
325325
while IFS= read -r file; do
326326
args[args_count]="$file"; args_count=$((args_count + 1))
327327
done < <(bashunit::helper::find_files_recursive "$arg" '*[bB]ench.sh')

src/parallel.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function bashunit::parallel::aggregate_test_results() {
2525
fi
2626

2727
local result_file=""
28-
for result_file in "${result_files[@]}"; do
28+
for result_file in "${result_files[@]+"${result_files[@]}"}"; do
2929
local result_line
3030
result_line=$(tail -n 1 < "$result_file")
3131

src/reports.sh

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ function bashunit::reports::generate_junit_xml() {
6464

6565
local i=0
6666
for i in "${!_BASHUNIT_REPORTS_TEST_NAMES[@]}"; do
67-
local file="${_BASHUNIT_REPORTS_TEST_FILES[$i]}"
68-
local name="${_BASHUNIT_REPORTS_TEST_NAMES[$i]}"
69-
local assertions="${_BASHUNIT_REPORTS_TEST_ASSERTIONS[$i]}"
70-
local status="${_BASHUNIT_REPORTS_TEST_STATUSES[$i]}"
71-
local test_time="${_BASHUNIT_REPORTS_TEST_DURATIONS[$i]}"
67+
local file="${_BASHUNIT_REPORTS_TEST_FILES[$i]:-}"
68+
local name="${_BASHUNIT_REPORTS_TEST_NAMES[$i]:-}"
69+
local assertions="${_BASHUNIT_REPORTS_TEST_ASSERTIONS[$i]:-}"
70+
local status="${_BASHUNIT_REPORTS_TEST_STATUSES[$i]:-}"
71+
local test_time="${_BASHUNIT_REPORTS_TEST_DURATIONS[$i]:-}"
7272

7373
echo " <testcase file=\"$file\""
7474
echo " name=\"$name\""
@@ -100,10 +100,10 @@ function bashunit::reports::generate_report_html() {
100100
: > "$temp_file" # Clear temp file if it exists
101101
local i=0
102102
for i in "${!_BASHUNIT_REPORTS_TEST_NAMES[@]}"; do
103-
local file="${_BASHUNIT_REPORTS_TEST_FILES[$i]}"
104-
local name="${_BASHUNIT_REPORTS_TEST_NAMES[$i]}"
105-
local status="${_BASHUNIT_REPORTS_TEST_STATUSES[$i]}"
106-
local test_time="${_BASHUNIT_REPORTS_TEST_DURATIONS[$i]}"
103+
local file="${_BASHUNIT_REPORTS_TEST_FILES[$i]:-}"
104+
local name="${_BASHUNIT_REPORTS_TEST_NAMES[$i]:-}"
105+
local status="${_BASHUNIT_REPORTS_TEST_STATUSES[$i]:-}"
106+
local test_time="${_BASHUNIT_REPORTS_TEST_DURATIONS[$i]:-}"
107107
local test_case="$file|$name|$status|$test_time"
108108

109109
echo "$test_case" >> "$temp_file"

src/runner.sh

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function bashunit::runner::load_test_files() {
3030
fi
3131

3232
local test_file=""
33-
for test_file in "${files[@]}"; do
33+
for test_file in "${files[@]+"${files[@]}"}"; do
3434
if [[ ! -f $test_file ]]; then
3535
continue
3636
fi
@@ -103,7 +103,7 @@ function bashunit::runner::load_test_files() {
103103
disown "$spinner_pid" && kill "$spinner_pid" &>/dev/null
104104
printf "\r \r" # Clear the spinner output
105105
local script_id=""
106-
for script_id in "${scripts_ids[@]}"; do
106+
for script_id in "${scripts_ids[@]+"${scripts_ids[@]}"}"; do
107107
export BASHUNIT_CURRENT_SCRIPT_ID="${script_id}"
108108
bashunit::cleanup_script_temp_files
109109
done
@@ -232,7 +232,7 @@ function bashunit::runner::parse_data_provider_args() {
232232
unset 'args[$last_idx]'
233233
fi
234234
# Print args and return early
235-
for arg in "${args[@]}"; do
235+
for arg in "${args[@]+"${args[@]}"}"; do
236236
encoded_arg="$(bashunit::helper::encode_base64 "${arg}")"
237237
printf '%s\n' "$encoded_arg"
238238
done
@@ -338,7 +338,7 @@ function bashunit::runner::call_test_functions() {
338338
local -a parsed_data=()
339339
local parsed_data_count=0
340340

341-
for fn_name in "${functions_to_run[@]}"; do
341+
for fn_name in "${functions_to_run[@]+"${functions_to_run[@]}"}"; do
342342
if bashunit::parallel::is_enabled && bashunit::parallel::must_stop_on_failure; then
343343
break
344344
fi
@@ -361,7 +361,7 @@ function bashunit::runner::call_test_functions() {
361361

362362
# Execute the test function for each line of data
363363
local data=""
364-
for data in "${provider_data[@]}"; do
364+
for data in "${provider_data[@]+"${provider_data[@]}"}"; do
365365
parsed_data=()
366366
parsed_data_count=0
367367
local line=""
@@ -403,7 +403,7 @@ function bashunit::runner::call_bench_functions() {
403403
fi
404404

405405
local fn_name=""
406-
for fn_name in "${functions_to_run[@]}"; do
406+
for fn_name in "${functions_to_run[@]+"${functions_to_run[@]}"}"; do
407407
read -r revs its max_ms <<< "$(bashunit::benchmark::parse_annotations "$fn_name" "$script")"
408408
bashunit::benchmark::run_function "$fn_name" "$revs" "$its" "$max_ms"
409409
unset -v fn_name
@@ -1067,9 +1067,13 @@ function bashunit::runner::record_test_hook_failure() {
10671067
}
10681068

10691069
function bashunit::runner::clear_mocks() {
1070-
local i=0
1070+
if [ "${#_BASHUNIT_MOCKED_FUNCTIONS[@]}" -eq 0 ]; then
1071+
return
1072+
fi
1073+
1074+
local i
10711075
for i in "${!_BASHUNIT_MOCKED_FUNCTIONS[@]}"; do
1072-
bashunit::unmock "${_BASHUNIT_MOCKED_FUNCTIONS[$i]}"
1076+
bashunit::unmock "${_BASHUNIT_MOCKED_FUNCTIONS[$i]:-}"
10731077
done
10741078
}
10751079

src/test_doubles.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ declare -a _BASHUNIT_MOCKED_FUNCTIONS=()
55
function bashunit::unmock() {
66
local command=$1
77

8-
local i=0
8+
if [ "${#_BASHUNIT_MOCKED_FUNCTIONS[@]}" -eq 0 ]; then
9+
return
10+
fi
11+
12+
local i
913
for i in "${!_BASHUNIT_MOCKED_FUNCTIONS[@]}"; do
10-
if [[ "${_BASHUNIT_MOCKED_FUNCTIONS[$i]}" == "$command" ]]; then
14+
if [[ "${_BASHUNIT_MOCKED_FUNCTIONS[$i]:-}" == "$command" ]]; then
1115
unset "_BASHUNIT_MOCKED_FUNCTIONS[$i]"
1216
unset -f "$command"
1317
local variable

0 commit comments

Comments
 (0)