diff --git a/src/coverage.sh b/src/coverage.sh index 34f96cd8..88bbd023 100644 --- a/src/coverage.sh +++ b/src/coverage.sh @@ -95,7 +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 'bashunit::coverage::record_line "${BASH_SOURCE:-}" "${LINENO:-}"' DEBUG + trap 'bashunit::coverage::record_line "${BASH_SOURCE[0]:-}" "${LINENO:-}"' 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..ed299336 --- /dev/null +++ b/tests/acceptance/coverage_hooks_test.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 + +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="src/" + 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..ad43e126 --- /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 +}