@@ -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+
118160function 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
564593function 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