@@ -173,9 +173,10 @@ function bashunit::coverage::calculate_percentage() {
173173# Get file coverage stats as "executable:hit:pct:class"
174174function bashunit::coverage::get_file_stats() {
175175 local file=" $1 "
176- local executable hit pct class
177- executable=$( bashunit::coverage::get_executable_lines " $file " )
178- hit=$( bashunit::coverage::get_hit_lines " $file " )
176+ local stats executable hit pct class
177+ stats=$( bashunit::coverage::compute_file_coverage " $file " )
178+ executable=" ${stats%%:* } "
179+ hit=" ${stats##*: } "
179180 pct=$( bashunit::coverage::calculate_percentage " $hit " " $executable " )
180181 class=$( bashunit::coverage::get_coverage_class " $pct " )
181182 echo " ${executable} :${hit} :${pct} :${class} "
@@ -517,6 +518,30 @@ function bashunit::coverage::get_line_hits() {
517518 echo " $count "
518519}
519520
521+ # Compute executable + hit counts for a file in a single source-file pass.
522+ # Reuses get_all_line_hits to avoid scanning the coverage data per line.
523+ # Output format: "executable:hit"
524+ function bashunit::coverage::compute_file_coverage() {
525+ local file=" $1 "
526+
527+ local -a hits_by_line=()
528+ local hit_lineno hit_count
529+ while IFS=: read -r hit_lineno hit_count; do
530+ [ -n " $hit_lineno " ] && hits_by_line[hit_lineno]=$hit_count
531+ done < <( bashunit::coverage::get_all_line_hits " $file " )
532+
533+ local executable=0 hit=0 lineno=0 line line_hits
534+ while IFS= read -r line || [ -n " $line " ]; do
535+ lineno=$(( lineno + 1 ))
536+ bashunit::coverage::is_executable_line " $line " " $lineno " || continue
537+ executable=$(( executable + 1 ))
538+ line_hits=${hits_by_line[lineno]:- 0}
539+ [ " $line_hits " -gt 0 ] && hit=$(( hit + 1 ))
540+ done < " $file "
541+
542+ echo " ${executable} :${hit} "
543+ }
544+
520545# Get all line hits for a file in one pass (performance optimization)
521546# Output format: one "lineno:count" per line
522547function bashunit::coverage::get_all_line_hits() {
@@ -571,12 +596,16 @@ function bashunit::coverage::extract_functions() {
571596 if [ " $in_function " -eq 0 ]; then
572597 local fn_name=" "
573598
574- # Match: function name() or function name {
599+ # Match: name() with optional ` function` keyword (parens form)
575600 local _re=' ^[[:space:]]*(function[[:space:]]+)?([a-zA-Z_][a-zA-Z0-9_:]*)[[:space:]]*\(\)[[:space:]]*\{?[[:space:]]*(#.*)?$'
576- fn_name=$( echo " $line " | sed -nE " s/$_re /\2/p" )
577- if [ -z " $fn_name " ]; then
601+ if [[ " $line " =~ $_re ]]; then
602+ fn_name=" ${BASH_REMATCH[2]} "
603+ else
604+ # Match: function name { (keyword form, no parens)
578605 _re=' ^[[:space:]]*(function[[:space:]]+)([a-zA-Z_][a-zA-Z0-9_:]*)[[:space:]]*\{[[:space:]]*(#.*)?$'
579- fn_name=$( echo " $line " | sed -nE " s/$_re /\2/p" )
606+ if [[ " $line " =~ $_re ]]; then
607+ fn_name=" ${BASH_REMATCH[2]} "
608+ fi
580609 fi
581610
582611 if [ -n " $fn_name " ]; then
@@ -588,10 +617,12 @@ function bashunit::coverage::extract_functions() {
588617 # Count opening braces on this line
589618 local open_braces=" ${line// [^\{]/ } "
590619 local close_braces=" ${line// [^\}]/ } "
591- brace_count=$(( brace_count + ${# open_braces} - ${# close_braces} ))
620+ local open_count=${# open_braces}
621+ local close_count=${# close_braces}
622+ brace_count=$(( brace_count + open_count - close_count))
592623
593- # Single-line function
594- if [ " $brace_count " -eq 0 ] && [ " $( echo " $line " | " $GREP " -c ' \{ ' || true ) " - gt 0 ] && [ " $( echo " $line " | " $GREP " -c ' \} ' || true ) " -gt 0 ]; then
624+ # Single-line function: braces balance on same line and both present
625+ if [ " $brace_count " -eq 0 ] && [ " $open_count " - gt 0 ] && [ " $close_count " -gt 0 ]; then
595626 echo " ${current_fn} :${fn_start} :${lineno} "
596627 in_function=0
597628 current_fn=" "
@@ -694,11 +725,14 @@ function bashunit::coverage::report_text() {
694725 { [ -z " $file " ] || [ ! -f " $file " ]; } && continue
695726 has_files=true
696727
697- local executable hit pct class
698- executable=$( bashunit::coverage::get_executable_lines " $file " )
699- hit=$( bashunit::coverage::get_hit_lines " $file " )
700- pct=$( bashunit::coverage::calculate_percentage " $hit " " $executable " )
701- class=$( bashunit::coverage::get_coverage_class " $pct " )
728+ local stats executable hit pct class
729+ stats=$( bashunit::coverage::get_file_stats " $file " )
730+ executable=" ${stats%%:* } "
731+ stats=" ${stats#*: } "
732+ hit=" ${stats%%:* } "
733+ stats=" ${stats#*: } "
734+ pct=" ${stats%%:* } "
735+ class=" ${stats##*: } "
702736
703737 total_executable=$(( total_executable + executable))
704738 total_hit=$(( total_hit + hit))
@@ -772,19 +806,23 @@ function bashunit::coverage::report_lcov() {
772806
773807 echo " SF:$file "
774808
775- local lineno=0
776- local line
809+ local -a hits_by_line=()
810+ local hit_lineno hit_count
811+ while IFS=: read -r hit_lineno hit_count; do
812+ [ -n " $hit_lineno " ] && hits_by_line[hit_lineno]=$hit_count
813+ done < <( bashunit::coverage::get_all_line_hits " $file " )
814+
815+ local lineno=0 executable=0 hit=0 line line_hits
777816 # shellcheck disable=SC2094
778817 while IFS= read -r line || [ -n " $line " ]; do
779- (( ++ lineno))
818+ lineno= $(( lineno + 1 ))
780819 bashunit::coverage::is_executable_line " $line " " $lineno " || continue
781- echo " DA:${lineno} ,$( bashunit::coverage::get_line_hits " $file " " $lineno " ) "
820+ executable=$(( executable + 1 ))
821+ line_hits=${hits_by_line[lineno]:- 0}
822+ [ " $line_hits " -gt 0 ] && hit=$(( hit + 1 ))
823+ echo " DA:${lineno} ,${line_hits} "
782824 done < " $file "
783825
784- local executable hit
785- executable=$( bashunit::coverage::get_executable_lines " $file " )
786- hit=$( bashunit::coverage::get_hit_lines " $file " )
787-
788826 echo " LF:$executable "
789827 echo " LH:$hit "
790828 echo " end_of_record"
@@ -852,10 +890,13 @@ function bashunit::coverage::report_html() {
852890 while IFS= read -r file; do
853891 { [ -z " $file " ] || [ ! -f " $file " ]; } && continue
854892
855- local executable hit pct
856- executable=$( bashunit::coverage::get_executable_lines " $file " )
857- hit=$( bashunit::coverage::get_hit_lines " $file " )
858- pct=$( bashunit::coverage::calculate_percentage " $hit " " $executable " )
893+ local stats executable hit pct
894+ stats=$( bashunit::coverage::get_file_stats " $file " )
895+ executable=" ${stats%%:* } "
896+ stats=" ${stats#*: } "
897+ hit=" ${stats%%:* } "
898+ stats=" ${stats#*: } "
899+ pct=" ${stats%%:* } "
859900
860901 total_executable=$(( total_executable + executable))
861902 total_hit=$(( total_hit + hit))
0 commit comments