From acd1cc55bf6b1df62ed39d111576dfb33de4b753 Mon Sep 17 00:00:00 2001 From: Omar Gallo Date: Sun, 28 Dec 2025 09:56:09 +0100 Subject: [PATCH 1/5] test: improve coverage --- src/coverage.sh | 19 ++++++++++++- src/runner.sh | 20 +++++++++++++ tests/acceptance/coverage_hooks_test.sh | 28 +++++++++++++++++++ .../fixtures/test_coverage_hooks.sh | 15 ++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/acceptance/coverage_hooks_test.sh create mode 100644 tests/acceptance/fixtures/test_coverage_hooks.sh diff --git a/src/coverage.sh b/src/coverage.sh index 34f96cd8..0a2286d9 100644 --- a/src/coverage.sh +++ b/src/coverage.sh @@ -82,6 +82,12 @@ function bashunit::coverage::init() { export _BASHUNIT_COVERAGE_TRACKED_FILES export _BASHUNIT_COVERAGE_TRACKED_CACHE_FILE export _BASHUNIT_COVERAGE_TEST_HITS_FILE + + # Ensure we have inclusion paths; if none provided or auto-discovered, + # default to tracking the src/ folder to avoid empty reports + if [[ -z "${BASHUNIT_COVERAGE_PATHS:-}" ]]; then + BASHUNIT_COVERAGE_PATHS="src/" + fi } function bashunit::coverage::enable_trap() { @@ -95,7 +101,18 @@ function bashunit::coverage::enable_trap() { # Set DEBUG trap to record line execution # Use ${VAR:-} to handle unset variables when set -u is active (in subshells) # shellcheck disable=SC2154 - trap 'bashunit::coverage::record_line "${BASH_SOURCE:-}" "${LINENO:-}"' DEBUG + trap ' + # Prefer immediate callee frame; fall back to caller when current file is excluded + local __cov_file="${BASH_SOURCE[0]:-}" + local __cov_line="${LINENO:-}" + if [[ -n "$__cov_file" ]] && ! bashunit::coverage::should_track "$__cov_file"; then + # Try next stack frame if available (e.g., called function from a tracked src file) + if [[ -n "${BASH_SOURCE[1]:-}" ]]; then + __cov_file="${BASH_SOURCE[1]}" + fi + fi + bashunit::coverage::record_line "$__cov_file" "$__cov_line" + ' DEBUG } function bashunit::coverage::disable_trap() { diff --git a/src/runner.sh b/src/runner.sh index f625a796..c6c7d75d 100755 --- a/src/runner.sh +++ b/src/runner.sh @@ -23,6 +23,10 @@ function bashunit::runner::load_test_files() { # Auto-discover coverage paths if not explicitly set if [[ -z "$BASHUNIT_COVERAGE_PATHS" ]]; then BASHUNIT_COVERAGE_PATHS=$(bashunit::coverage::auto_discover_paths "${files[@]}") + # Fallback: if auto-discovery yields no paths, track the src/ folder + if [[ -z "$BASHUNIT_COVERAGE_PATHS" ]]; then + BASHUNIT_COVERAGE_PATHS="src/" + fi fi bashunit::coverage::init fi @@ -850,8 +854,16 @@ function bashunit::runner::execute_file_hook() { trap '_BASHUNIT_HOOK_ERR_STATUS=$?; set +eE; trap - ERR; return $_BASHUNIT_HOOK_ERR_STATUS' ERR { + # Enable coverage trap inside hooks to attribute lines executed during setup/teardown + if bashunit::env::is_coverage_enabled; then + bashunit::coverage::enable_trap + fi "$hook_name" } >"$hook_output_file" 2>&1 + # Disable coverage trap after hook execution + if bashunit::env::is_coverage_enabled; then + bashunit::coverage::disable_trap + fi # Capture exit status from global variable and clean up status=$_BASHUNIT_HOOK_ERR_STATUS @@ -943,8 +955,16 @@ function bashunit::runner::execute_test_hook() { trap '_BASHUNIT_HOOK_ERR_STATUS=$?; set +eE; trap - ERR; return $_BASHUNIT_HOOK_ERR_STATUS' ERR { + # Enable coverage trap inside test-level hooks + if bashunit::env::is_coverage_enabled; then + bashunit::coverage::enable_trap + fi "$hook_name" } >"$hook_output_file" 2>&1 + # Disable coverage trap after hook execution + if bashunit::env::is_coverage_enabled; then + bashunit::coverage::disable_trap + fi # Capture exit status from global variable and clean up status=$_BASHUNIT_HOOK_ERR_STATUS diff --git a/tests/acceptance/coverage_hooks_test.sh b/tests/acceptance/coverage_hooks_test.sh new file mode 100644 index 00000000..554e9e82 --- /dev/null +++ b/tests/acceptance/coverage_hooks_test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +function set_up_before_script() { + TEST_ENV_FILE="tests/acceptance/fixtures/.env.default" + LCOV_FILE="$(bashunit::temp_file "lcov-output")" +} + +function test_coverage_includes_src_hits_from_setup_hook() { + # Enable coverage in-process and exercise code in a hook-like context + BASHUNIT_COVERAGE=true + BASHUNIT_COVERAGE_PATHS="" # force auto-discovery / fallback + bashunit::coverage::init + + # Simulate hook execution with coverage trap enabled + bashunit::coverage::enable_trap + # Call a src function to generate attributable hits + local f + f="$(bashunit::temp_file "cov-hooks")" + [[ -n "${f:-}" ]] && echo "tmp created" > /dev/null + bashunit::coverage::disable_trap + + # Generate LCOV and assert presence of src entries + bashunit::coverage::report_lcov "$LCOV_FILE" + local lcov + lcov="$(cat "$LCOV_FILE" 2>/dev/null)" + assert_contains "SF:$(pwd)/src/" "$lcov" + assert_contains "DA:" "$lcov" +} diff --git a/tests/acceptance/fixtures/test_coverage_hooks.sh b/tests/acceptance/fixtures/test_coverage_hooks.sh new file mode 100644 index 00000000..dc9fb642 --- /dev/null +++ b/tests/acceptance/fixtures/test_coverage_hooks.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# This fixture exercises coverage attribution inside lifecycle hooks. + +function set_up() { + # Invoke src functions to generate attributable coverage hits + local f + f="$(bashunit::temp_file \"cov-hooks\")" + [[ -n "${f:-}" ]] && echo "tmp created" > /dev/null +} + +function test_noop() { + # No-op test; coverage should still attribute lines from set_up + assert_true true +} From 97df5d8436cf24ffe0cca941c24141302c666356 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 28 Dec 2025 23:34:06 +0100 Subject: [PATCH 2/5] fix: correct escaped quotes in coverage hooks fixture --- tests/acceptance/fixtures/test_coverage_hooks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/fixtures/test_coverage_hooks.sh b/tests/acceptance/fixtures/test_coverage_hooks.sh index dc9fb642..ad43e126 100644 --- a/tests/acceptance/fixtures/test_coverage_hooks.sh +++ b/tests/acceptance/fixtures/test_coverage_hooks.sh @@ -5,7 +5,7 @@ function set_up() { # Invoke src functions to generate attributable coverage hits local f - f="$(bashunit::temp_file \"cov-hooks\")" + f="$(bashunit::temp_file "cov-hooks")" [[ -n "${f:-}" ]] && echo "tmp created" > /dev/null } From 78535cfcc28f6a9c25059955879bd416ebab5455 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 28 Dec 2025 23:34:44 +0100 Subject: [PATCH 3/5] refactor(coverage): remove duplicate default path logic from init The src/ fallback is already handled in runner.sh after auto-discovery. Having it in both places is redundant since runner.sh calls init() after setting BASHUNIT_COVERAGE_PATHS. --- src/coverage.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/coverage.sh b/src/coverage.sh index 0a2286d9..1cef5011 100644 --- a/src/coverage.sh +++ b/src/coverage.sh @@ -82,12 +82,6 @@ function bashunit::coverage::init() { export _BASHUNIT_COVERAGE_TRACKED_FILES export _BASHUNIT_COVERAGE_TRACKED_CACHE_FILE export _BASHUNIT_COVERAGE_TEST_HITS_FILE - - # Ensure we have inclusion paths; if none provided or auto-discovered, - # default to tracking the src/ folder to avoid empty reports - if [[ -z "${BASHUNIT_COVERAGE_PATHS:-}" ]]; then - BASHUNIT_COVERAGE_PATHS="src/" - fi } function bashunit::coverage::enable_trap() { From fddd252a6566111312241668074ef5deb28058cd Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 28 Dec 2025 23:35:35 +0100 Subject: [PATCH 4/5] perf(coverage): simplify DEBUG trap to avoid redundant calls The complex trap was: - Redundant: record_line already calls should_track internally - Semantically incorrect: used LINENO from frame 0 with BASH_SOURCE[1] - Adding overhead: extra function call on every line executed Revert to simple, efficient trap. --- src/coverage.sh | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/coverage.sh b/src/coverage.sh index 1cef5011..88bbd023 100644 --- a/src/coverage.sh +++ b/src/coverage.sh @@ -95,18 +95,7 @@ function bashunit::coverage::enable_trap() { # Set DEBUG trap to record line execution # Use ${VAR:-} to handle unset variables when set -u is active (in subshells) # shellcheck disable=SC2154 - trap ' - # Prefer immediate callee frame; fall back to caller when current file is excluded - local __cov_file="${BASH_SOURCE[0]:-}" - local __cov_line="${LINENO:-}" - if [[ -n "$__cov_file" ]] && ! bashunit::coverage::should_track "$__cov_file"; then - # Try next stack frame if available (e.g., called function from a tracked src file) - if [[ -n "${BASH_SOURCE[1]:-}" ]]; then - __cov_file="${BASH_SOURCE[1]}" - fi - fi - bashunit::coverage::record_line "$__cov_file" "$__cov_line" - ' DEBUG + trap 'bashunit::coverage::record_line "${BASH_SOURCE[0]:-}" "${LINENO:-}"' DEBUG } function bashunit::coverage::disable_trap() { From 3d8b8ec2f35976d8e889c2c9fd2bd6eb3432768f Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 28 Dec 2025 23:36:27 +0100 Subject: [PATCH 5/5] test: add shellcheck directive and use explicit coverage paths - Add SC2034 disable for environment variables used by bashunit - Use explicit BASHUNIT_COVERAGE_PATHS="src/" instead of relying on fallback --- tests/acceptance/coverage_hooks_test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/acceptance/coverage_hooks_test.sh b/tests/acceptance/coverage_hooks_test.sh index 554e9e82..ed299336 100644 --- a/tests/acceptance/coverage_hooks_test.sh +++ b/tests/acceptance/coverage_hooks_test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# shellcheck disable=SC2034 function set_up_before_script() { TEST_ENV_FILE="tests/acceptance/fixtures/.env.default" @@ -8,7 +9,7 @@ function set_up_before_script() { function test_coverage_includes_src_hits_from_setup_hook() { # Enable coverage in-process and exercise code in a hook-like context BASHUNIT_COVERAGE=true - BASHUNIT_COVERAGE_PATHS="" # force auto-discovery / fallback + BASHUNIT_COVERAGE_PATHS="src/" bashunit::coverage::init # Simulate hook execution with coverage trap enabled