Skip to content

Commit 36a07d4

Browse files
committed
fix(coverage): prevent duplicates in result
1 parent 0f7940e commit 36a07d4

File tree

1 file changed

+109
-146
lines changed

1 file changed

+109
-146
lines changed

src/coverage.sh

Lines changed: 109 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,48 @@ function bashunit::coverage::normalize_path() {
115115
fi
116116
}
117117

118+
# Get deduplicated list of tracked files
119+
function bashunit::coverage::get_tracked_files() {
120+
if [[ ! -f "$_BASHUNIT_COVERAGE_TRACKED_FILES" ]]; then
121+
return
122+
fi
123+
sort -u "$_BASHUNIT_COVERAGE_TRACKED_FILES"
124+
}
125+
126+
# Get coverage class (high/medium/low) based on percentage
127+
function bashunit::coverage::get_coverage_class() {
128+
local pct="$1"
129+
if [[ $pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80} ]]; then
130+
echo "high"
131+
elif [[ $pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50} ]]; then
132+
echo "medium"
133+
else
134+
echo "low"
135+
fi
136+
}
137+
138+
# Calculate percentage from hit and executable counts
139+
function bashunit::coverage::calculate_percentage() {
140+
local hit="$1"
141+
local executable="$2"
142+
if [[ $executable -gt 0 ]]; then
143+
echo $((hit * 100 / executable))
144+
else
145+
echo "0"
146+
fi
147+
}
148+
149+
# Get file coverage stats as "executable:hit:pct:class"
150+
function bashunit::coverage::get_file_stats() {
151+
local file="$1"
152+
local executable hit pct class
153+
executable=$(bashunit::coverage::get_executable_lines "$file")
154+
hit=$(bashunit::coverage::get_hit_lines "$file")
155+
pct=$(bashunit::coverage::calculate_percentage "$hit" "$executable")
156+
class=$(bashunit::coverage::get_coverage_class "$pct")
157+
echo "${executable}:${hit}:${pct}:${class}"
158+
}
159+
118160
function bashunit::coverage::record_line() {
119161
local file="$1"
120162
local lineno="$2"
@@ -534,31 +576,18 @@ function bashunit::coverage::get_percentage() {
534576
local total_executable=0
535577
local total_hit=0
536578

537-
# Check if tracked files exist
538-
if [[ ! -f "$_BASHUNIT_COVERAGE_TRACKED_FILES" ]]; then
539-
echo "0"
540-
return
541-
fi
542-
543579
while IFS= read -r file; do
544580
[[ -z "$file" || ! -f "$file" ]] && continue
545581

546-
local executable
582+
local executable hit
547583
executable=$(bashunit::coverage::get_executable_lines "$file")
548-
local hit
549584
hit=$(bashunit::coverage::get_hit_lines "$file")
550585

551586
((total_executable += executable))
552587
((total_hit += hit))
553-
done < <(sort -u "$_BASHUNIT_COVERAGE_TRACKED_FILES")
588+
done < <(bashunit::coverage::get_tracked_files)
554589

555-
if [[ $total_executable -eq 0 ]]; then
556-
echo "0"
557-
return
558-
fi
559-
560-
# Calculate percentage (integer)
561-
echo $((total_hit * 100 / total_executable))
590+
bashunit::coverage::calculate_percentage "$total_hit" "$total_executable"
562591
}
563592

564593
function bashunit::coverage::report_text() {
@@ -568,76 +597,63 @@ function bashunit::coverage::report_text() {
568597

569598
local total_executable=0
570599
local total_hit=0
600+
local has_files=false
571601

572602
echo ""
573603
echo "Coverage Report"
574604
echo "---------------"
575605

576-
# Check if tracked files exist
577-
if [[ ! -f "$_BASHUNIT_COVERAGE_TRACKED_FILES" ]]; then
578-
echo "---------------"
579-
echo "Total: 0/0 (0%)"
580-
return 0
581-
fi
582-
583606
while IFS= read -r file; do
584607
[[ -z "$file" || ! -f "$file" ]] && continue
608+
has_files=true
585609

586-
local executable
610+
local executable hit pct class
587611
executable=$(bashunit::coverage::get_executable_lines "$file")
588-
local hit
589612
hit=$(bashunit::coverage::get_hit_lines "$file")
613+
pct=$(bashunit::coverage::calculate_percentage "$hit" "$executable")
614+
class=$(bashunit::coverage::get_coverage_class "$pct")
590615

591616
((total_executable += executable))
592617
((total_hit += hit))
593618

594-
# Calculate percentage
595-
local pct=0
596-
if [[ $executable -gt 0 ]]; then
597-
pct=$((hit * 100 / executable))
598-
fi
599-
600-
# Determine color based on thresholds
601-
local color=""
602-
local reset=""
619+
# Determine color based on class
620+
local color="" reset=""
603621
if [[ "${BASHUNIT_NO_COLOR:-false}" != "true" ]]; then
604622
reset=$'\033[0m'
605-
if [[ $pct -lt ${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50} ]]; then
606-
color=$'\033[31m' # Red
607-
elif [[ $pct -lt ${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80} ]]; then
608-
color=$'\033[33m' # Yellow
609-
else
610-
color=$'\033[32m' # Green
611-
fi
623+
case "$class" in
624+
high) color=$'\033[32m' ;; # Green
625+
medium) color=$'\033[33m' ;; # Yellow
626+
low) color=$'\033[31m' ;; # Red
627+
esac
612628
fi
613629

614630
# Display relative path
615-
local display_file
616-
display_file="${file#"$(pwd)"/}"
617-
631+
local display_file="${file#"$(pwd)"/}"
618632
printf "%s%-40s %3d/%3d lines (%3d%%)%s\n" \
619633
"$color" "$display_file" "$hit" "$executable" "$pct" "$reset"
620-
done < <(sort -u "$_BASHUNIT_COVERAGE_TRACKED_FILES")
634+
done < <(bashunit::coverage::get_tracked_files)
635+
636+
if [[ "$has_files" != "true" ]]; then
637+
echo "---------------"
638+
echo "Total: 0/0 (0%)"
639+
return 0
640+
fi
621641

622642
echo "---------------"
623643

624644
# Total
625-
local total_pct=0
626-
if [[ $total_executable -gt 0 ]]; then
627-
total_pct=$((total_hit * 100 / total_executable))
628-
fi
645+
local total_pct total_class
646+
total_pct=$(bashunit::coverage::calculate_percentage "$total_hit" "$total_executable")
647+
total_class=$(bashunit::coverage::get_coverage_class "$total_pct")
629648

630-
local color=""
631-
local reset=""
649+
local color="" reset=""
632650
if [[ "${BASHUNIT_NO_COLOR:-false}" != "true" ]]; then
633651
reset=$'\033[0m'
634-
if [[ $total_pct -lt ${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50} ]]; then
635-
color=$'\033[31m'
636-
elif [[ $total_pct -lt ${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80} ]]; then
637-
color=$'\033[33m'
638-
else
639-
color=$'\033[32m'
640-
fi
652+
case "$total_class" in
653+
high) color=$'\033[32m' ;;
654+
medium) color=$'\033[33m' ;;
655+
low) color=$'\033[31m' ;;
656+
esac
641657
fi
642658

643659
printf "%sTotal: %d/%d (%d%%)%s\n" \
@@ -658,15 +674,7 @@ function bashunit::coverage::report_lcov() {
658674
fi
659675

660676
# Create output directory if needed
661-
local output_dir
662-
output_dir=$(dirname "$output_file")
663-
mkdir -p "$output_dir"
664-
665-
# Check if tracked files exist - if not, write empty LCOV file
666-
if [[ ! -f "$_BASHUNIT_COVERAGE_TRACKED_FILES" ]]; then
667-
echo "TN:" > "$output_file"
668-
return 0
669-
fi
677+
mkdir -p "$(dirname "$output_file")"
670678

671679
# Generate LCOV format
672680
{
@@ -681,26 +689,18 @@ function bashunit::coverage::report_lcov() {
681689
# shellcheck disable=SC2094
682690
while IFS= read -r line || [[ -n "$line" ]]; do
683691
((lineno++))
684-
685-
# Skip non-executable lines (use shared helper)
686692
bashunit::coverage::is_executable_line "$line" "$lineno" || continue
687-
688-
# Get hit count for this line
689-
local hits
690-
hits=$(bashunit::coverage::get_line_hits "$file" "$lineno")
691-
692-
echo "DA:${lineno},${hits}"
693+
echo "DA:${lineno},$(bashunit::coverage::get_line_hits "$file" "$lineno")"
693694
done < "$file"
694695

695-
local executable
696+
local executable hit
696697
executable=$(bashunit::coverage::get_executable_lines "$file")
697-
local hit
698698
hit=$(bashunit::coverage::get_hit_lines "$file")
699699

700700
echo "LF:$executable"
701701
echo "LH:$hit"
702702
echo "end_of_record"
703-
done < <(sort -u "$_BASHUNIT_COVERAGE_TRACKED_FILES")
703+
done < <(bashunit::coverage::get_tracked_files)
704704
} > "$output_file"
705705
}
706706

@@ -752,11 +752,6 @@ function bashunit::coverage::report_html() {
752752
return 0
753753
fi
754754

755-
# Check if tracked files exist
756-
if [[ ! -f "$_BASHUNIT_COVERAGE_TRACKED_FILES" ]]; then
757-
return 0
758-
fi
759-
760755
# Create output directory structure
761756
mkdir -p "$output_dir/files"
762757

@@ -768,19 +763,14 @@ function bashunit::coverage::report_html() {
768763
while IFS= read -r file; do
769764
[[ -z "$file" || ! -f "$file" ]] && continue
770765

771-
local executable
766+
local executable hit pct
772767
executable=$(bashunit::coverage::get_executable_lines "$file")
773-
local hit
774768
hit=$(bashunit::coverage::get_hit_lines "$file")
769+
pct=$(bashunit::coverage::calculate_percentage "$hit" "$executable")
775770

776771
((total_executable += executable))
777772
((total_hit += hit))
778773

779-
local pct=0
780-
if [[ $executable -gt 0 ]]; then
781-
pct=$((hit * 100 / executable))
782-
fi
783-
784774
local display_file="${file#"$(pwd)"/}"
785775
local safe_filename
786776
safe_filename=$(bashunit::coverage::path_to_filename "$file")
@@ -789,13 +779,11 @@ function bashunit::coverage::report_html() {
789779

790780
# Generate individual file HTML
791781
bashunit::coverage::generate_file_html "$file" "$output_dir/files/${safe_filename}.html"
792-
done < <(sort -u "$_BASHUNIT_COVERAGE_TRACKED_FILES")
782+
done < <(bashunit::coverage::get_tracked_files)
793783

794784
# Calculate total percentage
795-
local total_pct=0
796-
if [[ $total_executable -gt 0 ]]; then
797-
total_pct=$((total_hit * 100 / total_executable))
798-
fi
785+
local total_pct
786+
total_pct=$(bashunit::coverage::calculate_percentage "$total_hit" "$total_executable")
799787

800788
# Get test results
801789
local tests_passed=$(bashunit::state::get_tests_passed)
@@ -831,23 +819,19 @@ function bashunit::coverage::generate_index_html() {
831819
local gauge_offset=$((440 - (440 * total_pct / 100)))
832820

833821
# Determine coverage level and colors for gauge
834-
local gauge_color_start gauge_color_end gauge_text_gradient
835-
if [[ $total_pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80} ]]; then
836-
# High coverage - green
837-
gauge_color_start="#10b981"
838-
gauge_color_end="#34d399"
839-
gauge_text_gradient="linear-gradient(135deg, #10b981 0%, #34d399 100%)"
840-
elif [[ $total_pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50} ]]; then
841-
# Medium coverage - yellow/orange
842-
gauge_color_start="#f59e0b"
843-
gauge_color_end="#fbbf24"
844-
gauge_text_gradient="linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)"
845-
else
846-
# Low coverage - red
847-
gauge_color_start="#ef4444"
848-
gauge_color_end="#f87171"
849-
gauge_text_gradient="linear-gradient(135deg, #ef4444 0%, #f87171 100%)"
850-
fi
822+
local total_class gauge_color_start gauge_color_end gauge_text_gradient
823+
total_class=$(bashunit::coverage::get_coverage_class "$total_pct")
824+
case "$total_class" in
825+
high)
826+
gauge_color_start="#10b981"; gauge_color_end="#34d399"
827+
gauge_text_gradient="linear-gradient(135deg, #10b981 0%, #34d399 100%)" ;;
828+
medium)
829+
gauge_color_start="#f59e0b"; gauge_color_end="#fbbf24"
830+
gauge_text_gradient="linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)" ;;
831+
low)
832+
gauge_color_start="#ef4444"; gauge_color_end="#f87171"
833+
gauge_text_gradient="linear-gradient(135deg, #ef4444 0%, #f87171 100%)" ;;
834+
esac
851835

852836
{
853837
cat << 'EOF'
@@ -1117,12 +1101,8 @@ EOF
11171101
for data in ${file_data[@]+"${file_data[@]}"}; do
11181102
IFS='|' read -r display_file hit executable pct safe_filename <<< "$data"
11191103

1120-
local class="low"
1121-
if [[ $pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80} ]]; then
1122-
class="high"
1123-
elif [[ $pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50} ]]; then
1124-
class="medium"
1125-
fi
1104+
local class
1105+
class=$(bashunit::coverage::get_coverage_class "$pct")
11261106

11271107
echo " <tr onclick=\"window.location='files/${safe_filename}.html'\">"
11281108
echo " <td>"
@@ -1174,24 +1154,13 @@ function bashunit::coverage::generate_file_html() {
11741154
local output_file="$2"
11751155

11761156
local display_file="${file#"$(pwd)"/}"
1177-
local executable
1157+
local executable hit pct class
11781158
executable=$(bashunit::coverage::get_executable_lines "$file")
1179-
local hit
11801159
hit=$(bashunit::coverage::get_hit_lines "$file")
1160+
pct=$(bashunit::coverage::calculate_percentage "$hit" "$executable")
1161+
class=$(bashunit::coverage::get_coverage_class "$pct")
11811162
local uncovered=$((executable - hit))
11821163

1183-
local pct=0
1184-
if [[ $executable -gt 0 ]]; then
1185-
pct=$((hit * 100 / executable))
1186-
fi
1187-
1188-
local class="low"
1189-
if [[ $pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80} ]]; then
1190-
class="high"
1191-
elif [[ $pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50} ]]; then
1192-
class="medium"
1193-
fi
1194-
11951164
# Pre-load all line hits into indexed array (performance optimization)
11961165
local -a hits_by_line=()
11971166
local _ln _cnt
@@ -1472,20 +1441,14 @@ EOF
14721441
fi
14731442
done
14741443

1475-
local fn_pct=0
1476-
if [[ $fn_executable -gt 0 ]]; then
1477-
fn_pct=$((fn_hit * 100 / fn_executable))
1478-
fi
1479-
1480-
local fn_class="low"
1481-
local row_class="fn-uncovered"
1482-
if [[ $fn_pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80} ]]; then
1483-
fn_class="high"
1484-
row_class="fn-covered"
1485-
elif [[ $fn_pct -ge ${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50} ]]; then
1486-
fn_class="medium"
1487-
row_class="fn-partial"
1488-
fi
1444+
local fn_pct fn_class row_class
1445+
fn_pct=$(bashunit::coverage::calculate_percentage "$fn_hit" "$fn_executable")
1446+
fn_class=$(bashunit::coverage::get_coverage_class "$fn_pct")
1447+
case "$fn_class" in
1448+
high) row_class="fn-covered" ;;
1449+
medium) row_class="fn-partial" ;;
1450+
low) row_class="fn-uncovered" ;;
1451+
esac
14891452

14901453
echo " <tr class=\"$row_class\">"
14911454
echo " <td><a href=\"#line-${fn_start}\" class=\"fn-name\">${fn_name}</a></td>"

0 commit comments

Comments
 (0)