Skip to content

Commit a3d8f5c

Browse files
committed
fix(coverage): improve performance and fix duplicate detection in HTML report
- Fix false positives in test-to-line duplicate detection by using newline boundaries instead of substring matching - Guard coverage context exports to only run when coverage is enabled - Remove Google Fonts external dependencies, use system fonts for offline support and faster loading - Remove unused get_line_tests() function
1 parent 0ce9370 commit a3d8f5c

2 files changed

Lines changed: 18 additions & 35 deletions

File tree

src/coverage.sh

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -379,21 +379,6 @@ function bashunit::coverage::get_all_line_hits() {
379379
done
380380
}
381381

382-
# Get the tests that hit a specific line
383-
# Output format: list of "test_file:test_function" (unique, sorted)
384-
function bashunit::coverage::get_line_tests() {
385-
local file="$1"
386-
local lineno="$2"
387-
388-
if [[ ! -f "${_BASHUNIT_COVERAGE_TEST_HITS_FILE:-}" ]]; then
389-
return
390-
fi
391-
392-
# Format in file: source_file:line|test_file:test_function
393-
grep "^${file}:${lineno}|" "$_BASHUNIT_COVERAGE_TEST_HITS_FILE" 2>/dev/null | \
394-
cut -d'|' -f2 | sort -u
395-
}
396-
397382
# Get all test hits for a file in one pass (performance optimization)
398383
# Output format: lineno|test_file:test_function (may have duplicates, one per hit)
399384
function bashunit::coverage::get_all_line_tests() {
@@ -715,9 +700,6 @@ function bashunit::coverage::generate_index_html() {
715700
<meta charset="UTF-8">
716701
<meta name="viewport" content="width=device-width, initial-scale=1.0">
717702
<title>Coverage Report | bashunit</title>
718-
<link rel="preconnect" href="https://fonts.googleapis.com">
719-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
720-
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
721703
<style>
722704
:root {
723705
--primary: #6366f1; --primary-dark: #4f46e5; --primary-light: #818cf8;
@@ -729,7 +711,7 @@ function bashunit::coverage::generate_index_html() {
729711
--border: #e2e8f0;
730712
}
731713
* { margin: 0; padding: 0; box-sizing: border-box; }
732-
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-light); color: var(--text-primary); min-height: 100vh; line-height: 1.6; }
714+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: var(--bg-light); color: var(--text-primary); min-height: 100vh; line-height: 1.6; }
733715
.header { background: var(--bg-card); padding: 0; position: relative; overflow: hidden; border-bottom: 1px solid var(--border); }
734716
.header-content { position: relative; z-index: 1; max-width: 1400px; margin: 0 auto; padding: 40px 30px; }
735717
.header-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
@@ -789,7 +771,7 @@ function bashunit::coverage::generate_index_html() {
789771
.file-info { display: flex; flex-direction: column; gap: 4px; }
790772
.file-name { font-weight: 600; color: var(--text-primary); text-decoration: none; font-size: 1rem; transition: color 0.2s; }
791773
.file-name:hover { color: var(--primary-light); }
792-
.file-path { color: var(--text-muted); font-size: 0.85rem; font-family: 'JetBrains Mono', monospace; }
774+
.file-path { color: var(--text-muted); font-size: 0.85rem; font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; }
793775
.lines-info { text-align: center; }
794776
.lines-covered { font-weight: 700; font-size: 1.1rem; color: var(--text-primary); }
795777
.lines-total { color: var(--text-muted); font-size: 0.85rem; }
@@ -1056,17 +1038,19 @@ function bashunit::coverage::generate_file_html() {
10561038
hits_by_line[_ln]=$_cnt
10571039
done < <(bashunit::coverage::get_all_line_hits "$file")
10581040

1059-
# Pre-load test hits data into associative array (for tooltips)
1060-
# Key: line number, Value: newline-separated list of "test_file:test_function"
1061-
declare -A tests_by_line
1041+
# Pre-load test hits data into indexed array (for tooltips)
1042+
# Index: line number, Value: newline-separated list of "test_file:test_function"
1043+
# Using indexed array for Bash 3.2 compatibility (no associative arrays)
1044+
local -a tests_by_line=()
10621045
local _line_and_test
10631046
while IFS= read -r _line_and_test; do
10641047
[[ -z "$_line_and_test" ]] && continue
10651048
local _tln="${_line_and_test%%|*}"
10661049
local _tinfo="${_line_and_test#*|}"
10671050
if [[ -n "${tests_by_line[$_tln]:-}" ]]; then
10681051
# Append only if not already present (avoid duplicates)
1069-
if [[ "${tests_by_line[$_tln]}" != *"$_tinfo"* ]]; then
1052+
# Use newline boundaries to prevent false positives (e.g., test_foo matching test_foo_bar)
1053+
if [[ $'\n'"${tests_by_line[$_tln]}"$'\n' != *$'\n'"$_tinfo"$'\n'* ]]; then
10701054
tests_by_line[$_tln]="${tests_by_line[$_tln]}"$'\n'"${_tinfo}"
10711055
fi
10721056
else
@@ -1089,9 +1073,6 @@ function bashunit::coverage::generate_file_html() {
10891073
EOF
10901074
echo " <title>$(basename "$display_file") | Coverage Report</title>"
10911075
cat << 'EOF'
1092-
<link rel="preconnect" href="https://fonts.googleapis.com">
1093-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1094-
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
10951076
<style>
10961077
:root {
10971078
--primary: #6366f1; --primary-dark: #4f46e5; --primary-light: #818cf8;
@@ -1103,14 +1084,14 @@ EOF
11031084
--border: #e2e8f0; --line-number-bg: #f8fafc;
11041085
}
11051086
* { margin: 0; padding: 0; box-sizing: border-box; }
1106-
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-light); color: var(--text-primary); min-height: 100vh; line-height: 1.6; }
1087+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: var(--bg-light); color: var(--text-primary); min-height: 100vh; line-height: 1.6; }
11071088
.header { background: var(--bg-card); border-bottom: 1px solid var(--border); padding: 20px 30px; position: sticky; top: 0; z-index: 100; backdrop-filter: blur(10px); box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
11081089
.header-content { max-width: 1600px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px; }
11091090
.nav-section { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
11101091
.back-btn { display: inline-flex; align-items: center; gap: 8px; padding: 12px 24px; background: #475569; border: 2px solid #475569; border-radius: 8px; color: #ffffff; text-decoration: none; font-size: 1rem; font-weight: 600; transition: all 0.2s; box-shadow: 0 2px 4px rgba(71, 85, 105, 0.2); }
11111092
.back-btn:hover { background: #334155; border-color: #334155; box-shadow: 0 4px 12px rgba(51, 65, 85, 0.3); }
11121093
.file-title { display: flex; align-items: center; gap: 12px; }
1113-
.file-name { font-size: 1.3rem; font-weight: 700; font-family: 'JetBrains Mono', monospace; }
1094+
.file-name { font-size: 1.3rem; font-weight: 700; font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; }
11141095
.stats-section { display: flex; align-items: center; gap: 30px; flex-wrap: wrap; }
11151096
.stat-item { display: flex; align-items: center; gap: 10px; }
11161097
.stat-badge { padding: 8px 16px; border-radius: 20px; font-weight: 600; font-size: 0.9rem; }
@@ -1142,11 +1123,11 @@ EOF
11421123
.code-container { max-width: 1600px; margin: 30px auto; padding: 0 30px; }
11431124
.code-wrapper { background: var(--bg-code); border-radius: 16px; overflow: hidden; border: 1px solid var(--border); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
11441125
.code-header { background: var(--line-number-bg); padding: 16px 24px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); flex-wrap: wrap; gap: 12px; }
1145-
.code-path { font-family: 'JetBrains Mono', monospace; font-size: 0.9rem; color: var(--text-secondary); }
1126+
.code-path { font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; font-size: 0.9rem; color: var(--text-secondary); }
11461127
.code-stats { display: flex; gap: 16px; font-size: 0.85rem; }
11471128
.code-stats span { padding: 4px 12px; background: #e5e7eb; border-radius: 4px; color: var(--text-secondary); }
11481129
.code-body { overflow-x: auto; }
1149-
.code-table { width: 100%; border-collapse: collapse; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.6; }
1130+
.code-table { width: 100%; border-collapse: collapse; font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; font-size: 13px; line-height: 1.6; }
11501131
.code-table tr { transition: background 0.15s; }
11511132
.line-num { width: 60px; padding: 2px 16px; text-align: right; color: #9ca3af; background: var(--line-number-bg); border-right: 1px solid var(--border); user-select: none; vertical-align: top; }
11521133
.hits { width: 60px; padding: 2px 12px; text-align: center; color: #9ca3af; background: var(--line-number-bg); border-right: 1px solid var(--border); font-size: 0.85em; vertical-align: top; }
@@ -1159,7 +1140,7 @@ EOF
11591140
.hits-badge:hover .hits-tooltip { display: block; }
11601141
.hits-tooltip-title { font-weight: 600; margin-bottom: 6px; color: #94a3b8; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; }
11611142
.hits-tooltip-list { margin: 0; padding: 0; list-style: none; }
1162-
.hits-tooltip-list li { padding: 3px 0; border-bottom: 1px solid #334155; font-family: 'JetBrains Mono', monospace; }
1143+
.hits-tooltip-list li { padding: 3px 0; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; }
11631144
.hits-tooltip-list li:last-child { border-bottom: none; }
11641145
.hits-tooltip-file { color: #60a5fa; }
11651146
.hits-tooltip-fn { color: #a5b4fc; }

src/runner.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,11 @@ function bashunit::runner::run_test() {
390390
# create temporary files scoped per test run. This prevents
391391
# race conditions when running tests in parallel.
392392
export BASHUNIT_CURRENT_TEST_ID="$(bashunit::helper::generate_id "$fn_name")"
393-
# Export current test file and function for coverage tracking
394-
export _BASHUNIT_COVERAGE_CURRENT_TEST_FILE="$test_file"
395-
export _BASHUNIT_COVERAGE_CURRENT_TEST_FN="$fn_name"
393+
# Export current test file and function for coverage tracking (only when coverage enabled)
394+
if bashunit::env::is_coverage_enabled; then
395+
export _BASHUNIT_COVERAGE_CURRENT_TEST_FILE="$test_file"
396+
export _BASHUNIT_COVERAGE_CURRENT_TEST_FN="$fn_name"
397+
fi
396398

397399
bashunit::state::reset_test_title
398400

0 commit comments

Comments
 (0)