Skip to content

Commit 0b892bf

Browse files
authored
Merge pull request #619 from objctp/fix/post-increment-exit-code-bash-set-e
Fix post-increment causing silent exit under set -e
2 parents 73a5a5e + 352e7d8 commit 0b892bf

File tree

7 files changed

+86
-16
lines changed

7 files changed

+86
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Fix spying on `echo` or `printf` causing bashunit to hang due to infinite recursion (#607)
1111
- Fix invalid `.env.example` coverage threshold entry and copy `.env.example` to `.env` in CI test workflows so configuration parse errors are caught during automated test runs
1212
- Fix `clock::now` shell-time parsing when `EPOCHREALTIME` uses a comma decimal separator
13+
- Fix LCOV and HTML coverage reports generating incomplete/empty output due to post-increment operator causing silent exit under `set -e` (#618)
1314

1415
## [0.34.1](https://github.com/TypedDevs/bashunit/compare/0.34.0...0.34.1) - 2026-03-20
1516

src/coverage.sh

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,8 @@ function bashunit::coverage::get_executable_lines() {
465465
local line
466466

467467
while IFS= read -r line || [ -n "$line" ]; do
468-
((lineno++))
469-
bashunit::coverage::is_executable_line "$line" "$lineno" && ((count++))
468+
((++lineno))
469+
bashunit::coverage::is_executable_line "$line" "$lineno" && ((++count))
470470
done <"$file"
471471

472472
echo "$count"
@@ -498,7 +498,7 @@ function bashunit::coverage::get_hit_lines() {
498498
local line_content
499499
line_content=$(sed -n "${line_num}p" "$file" 2>/dev/null) || continue
500500
if bashunit::coverage::is_executable_line "$line_content" "$line_num"; then
501-
((count++))
501+
((++count))
502502
fi
503503
done
504504

@@ -565,7 +565,7 @@ function bashunit::coverage::extract_functions() {
565565
local line
566566

567567
while IFS= read -r line || [ -n "$line" ]; do
568-
((lineno++))
568+
((++lineno))
569569

570570
# Check for function definition patterns
571571
# Pattern 1: function name() { or function name {
@@ -644,10 +644,10 @@ function bashunit::coverage::get_function_coverage() {
644644
line_content=$(sed -n "${lineno}p" "$file" 2>/dev/null) || continue
645645

646646
if bashunit::coverage::is_executable_line "$line_content" "$lineno"; then
647-
((executable++))
647+
((++executable))
648648
local line_hits=${_hits_ref[$lineno]:-0}
649649
if [ "$line_hits" -gt 0 ]; then
650-
((hit++))
650+
((++hit))
651651
fi
652652
fi
653653
done
@@ -778,7 +778,7 @@ function bashunit::coverage::report_lcov() {
778778
local line
779779
# shellcheck disable=SC2094
780780
while IFS= read -r line || [ -n "$line" ]; do
781-
((lineno++))
781+
((++lineno))
782782
bashunit::coverage::is_executable_line "$line" "$lineno" || continue
783783
echo "DA:${lineno},$(bashunit::coverage::get_line_hits "$file" "$lineno")"
784784
done <"$file"
@@ -1543,10 +1543,10 @@ EOF
15431543
local ln_content
15441544
ln_content=$(sed -n "${ln}p" "$file" 2>/dev/null) || continue
15451545
if bashunit::coverage::is_executable_line "$ln_content" "$ln"; then
1546-
((fn_executable++))
1546+
((++fn_executable))
15471547
local ln_hits=${hits_by_line[$ln]:-0}
15481548
if [ "$ln_hits" -gt 0 ]; then
1549-
((fn_hit++))
1549+
((++fn_hit))
15501550
fi
15511551
fi
15521552
done
@@ -1597,7 +1597,7 @@ EOF
15971597
local lineno=0
15981598
local line
15991599
while IFS= read -r line || [ -n "$line" ]; do
1600-
((lineno++))
1600+
((++lineno))
16011601

16021602
local escaped_line
16031603
escaped_line=$(bashunit::coverage::html_escape "$line")

src/learn.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ function bashunit::learn::show_progress() {
127127
for i in $(seq 1 $total_lessons); do
128128
if bashunit::learn::is_completed "lesson_$i"; then
129129
echo " ${_BASHUNIT_COLOR_PASSED}${_BASHUNIT_COLOR_DEFAULT} Lesson $i completed"
130-
((completed++)) || true
130+
((++completed)) || true
131131
else
132132
echo " ${_BASHUNIT_COLOR_INCOMPLETE}${_BASHUNIT_COLOR_DEFAULT} Lesson $i"
133133
fi

src/str.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,17 @@ function bashunit::str::rpad() {
4040
if [ "$original_char" = $'\x1b' ]; then
4141
while [ "${left_text:$j:1}" != "m" ] && [ $j -lt ${#left_text} ]; do
4242
result_left_text="$result_left_text${left_text:$j:1}"
43-
((j++))
43+
((++j))
4444
done
4545
result_left_text="$result_left_text${left_text:$j:1}" # Append the final 'm'
46-
((j++))
46+
((++j))
4747
elif [ "$char" = "$original_char" ]; then
4848
# Match the actual character
4949
result_left_text="$result_left_text$char"
50-
((i++))
51-
((j++))
50+
((++i))
51+
((++j))
5252
else
53-
((j++))
53+
((++j))
5454
fi
5555
done
5656

tests/unit/coverage_core_test.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,29 @@ EOF
174174
rm -f "$temp_file"
175175
}
176176

177+
function test_coverage_get_executable_lines_does_not_exit_under_set_e() {
178+
local temp_file
179+
temp_file=$(mktemp)
180+
181+
cat >"$temp_file" <<'EOF'
182+
#!/usr/bin/env bash
183+
echo "line 1"
184+
echo "line 2"
185+
EOF
186+
187+
# ((var++)) when var=0 evaluates to 0 (falsy) causing exit code 1;
188+
# under set -e this silently terminates the function (#618)
189+
local result
190+
result=$(
191+
set -e
192+
bashunit::coverage::get_executable_lines "$temp_file"
193+
)
194+
195+
assert_equals "2" "$result"
196+
197+
rm -f "$temp_file"
198+
}
199+
177200
function test_coverage_record_line_writes_to_file() {
178201
BASHUNIT_COVERAGE="true"
179202
BASHUNIT_COVERAGE_PATHS="/"

tests/unit/coverage_reporting_test.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,40 @@ EOF
146146
rm -f "$temp_file" "$report_file"
147147
}
148148

149+
function test_coverage_report_lcov_completes_under_set_e() {
150+
BASHUNIT_COVERAGE="true"
151+
bashunit::coverage::init
152+
153+
local temp_file
154+
temp_file=$(mktemp)
155+
cat >"$temp_file" <<'EOF'
156+
#!/usr/bin/env bash
157+
echo "line 1"
158+
echo "line 2"
159+
EOF
160+
161+
echo "$temp_file" >"$_BASHUNIT_COVERAGE_TRACKED_FILES"
162+
163+
local report_file
164+
report_file=$(mktemp)
165+
166+
# ((lineno++)) when lineno=0 returns exit code 1 under set -e
167+
# causing incomplete LCOV output (#618)
168+
(
169+
set -e
170+
bashunit::coverage::report_lcov "$report_file"
171+
)
172+
173+
local content
174+
content=$(cat "$report_file")
175+
176+
assert_contains "end_of_record" "$content"
177+
assert_contains "DA:2," "$content"
178+
assert_contains "DA:3," "$content"
179+
180+
rm -f "$temp_file" "$report_file"
181+
}
182+
149183
function test_coverage_report_text_shows_no_files_message() {
150184
BASHUNIT_COVERAGE="true"
151185
bashunit::coverage::init

tests/unit/str_test.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ function test_rpad_custom_width_padding_text_too_long_and_special_chars() {
5151
"$actual"
5252
}
5353

54+
function test_rpad_does_not_exit_under_set_e() {
55+
# ((i++)) when i=0 evaluates to 0 (falsy) causing exit code 1;
56+
# under set -e this silently terminates the function (#618)
57+
local actual
58+
actual=$(
59+
set -e
60+
bashunit::str::rpad "input" "1" 20
61+
)
62+
63+
assert_same "input 1" "$actual"
64+
}
65+
5466
function test_rpad_width_smaller_than_right_word() {
5567
local actual=$(bashunit::str::rpad "foo" "verylongword" 5)
5668

0 commit comments

Comments
 (0)