diff --git a/CHANGELOG.md b/CHANGELOG.md index a47b02f1..cdac3a6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Performance - Faster runtime-error detection: single `case` glob instead of 23-iteration loop in `detect_runtime_error` (#668) +- Hot-path coverage flag now cached in `_BASHUNIT_COVERAGE_ON`, removing a function dispatch per call (#664) ## [0.36.0](https://github.com/TypedDevs/bashunit/compare/0.35.0...0.36.0) - 2026-05-07 diff --git a/src/runner.sh b/src/runner.sh index be5cdcbf..c3a76ed1 100755 --- a/src/runner.sh +++ b/src/runner.sh @@ -12,6 +12,17 @@ function bashunit::runner::restore_workdir() { cd "$BASHUNIT_WORKING_DIR" 2>/dev/null || true } +# Caches BASHUNIT_COVERAGE into _BASHUNIT_COVERAGE_ON ("1"|"0") so hot-path checks +# avoid a function dispatch per call. Call once after arg parsing; tests that +# toggle BASHUNIT_COVERAGE mid-run must call this again to refresh. +function bashunit::runner::sync_coverage_flag() { + if [ "${BASHUNIT_COVERAGE-}" = "true" ]; then + _BASHUNIT_COVERAGE_ON=1 + else + _BASHUNIT_COVERAGE_ON=0 + fi +} + function bashunit::runner::source_login_shell_profiles() { # shellcheck disable=SC1091 [ -f /etc/profile ] && source /etc/profile 2>/dev/null || true @@ -27,7 +38,7 @@ function bashunit::runner::export_test_identity() { local test_file=$1 local fn_name=$2 export BASHUNIT_CURRENT_TEST_ID="$(bashunit::helper::generate_id "$fn_name")" - if bashunit::env::is_coverage_enabled; then + if [ "${_BASHUNIT_COVERAGE_ON:-0}" = 1 ]; then export _BASHUNIT_COVERAGE_CURRENT_TEST_FILE="$test_file" export _BASHUNIT_COVERAGE_CURRENT_TEST_FN="$fn_name" fi @@ -167,8 +178,10 @@ function bashunit::runner::load_test_files() { local -a scripts_ids=() local scripts_ids_count=0 + bashunit::runner::sync_coverage_flag + # Initialize coverage tracking if enabled - if bashunit::env::is_coverage_enabled; then + if [ "$_BASHUNIT_COVERAGE_ON" = 1 ]; then # Auto-discover coverage paths if not explicitly set if [ -z "$BASHUNIT_COVERAGE_PATHS" ]; then BASHUNIT_COVERAGE_PATHS=$(bashunit::coverage::auto_discover_paths "${files[@]}") @@ -737,7 +750,7 @@ function bashunit::runner::run_test() { fi # Enable coverage tracking early to include set_up/tear_down hooks - if bashunit::env::is_coverage_enabled; then + if [ "${_BASHUNIT_COVERAGE_ON:-0}" = 1 ]; then bashunit::coverage::enable_trap fi @@ -941,7 +954,7 @@ function bashunit::runner::cleanup_on_exit() { local exit_code="$2" # Disable coverage trap before cleanup to avoid interference - if bashunit::env::is_coverage_enabled; then + if [ "${_BASHUNIT_COVERAGE_ON:-0}" = 1 ]; then bashunit::coverage::disable_trap fi @@ -1381,7 +1394,7 @@ function bashunit::runner::run_set_up_before_script() { start_time=$(bashunit::clock::now) # Enable coverage trap to attribute lines executed during set_up_before_script - if bashunit::env::is_coverage_enabled; then + if [ "${_BASHUNIT_COVERAGE_ON:-0}" = 1 ]; then bashunit::coverage::enable_trap fi @@ -1390,7 +1403,7 @@ function bashunit::runner::run_set_up_before_script() { local status=$? # Disable coverage trap after hook execution - if bashunit::env::is_coverage_enabled; then + if [ "${_BASHUNIT_COVERAGE_ON:-0}" = 1 ]; then bashunit::coverage::disable_trap fi @@ -1520,7 +1533,7 @@ function bashunit::runner::run_tear_down_after_script() { start_time=$(bashunit::clock::now) # Enable coverage trap to attribute lines executed during tear_down_after_script - if bashunit::env::is_coverage_enabled; then + if [ "${_BASHUNIT_COVERAGE_ON:-0}" = 1 ]; then bashunit::coverage::enable_trap fi @@ -1529,7 +1542,7 @@ function bashunit::runner::run_tear_down_after_script() { local status=$? # Disable coverage trap after hook execution - if bashunit::env::is_coverage_enabled; then + if [ "${_BASHUNIT_COVERAGE_ON:-0}" = 1 ]; then bashunit::coverage::disable_trap fi diff --git a/tests/unit/runner_test.sh b/tests/unit/runner_test.sh index eff156b0..0e8745d9 100644 --- a/tests/unit/runner_test.sh +++ b/tests/unit/runner_test.sh @@ -48,6 +48,33 @@ function test_extract_assertion_runtime_output_keeps_user_output_that_looks_like assert_same "✗ Failed: emitted by the code under test" "$actual" } +function test_sync_coverage_flag_sets_one_when_enabled() { + local _orig="${BASHUNIT_COVERAGE-}" + BASHUNIT_COVERAGE="true" + bashunit::runner::sync_coverage_flag + assert_same "1" "$_BASHUNIT_COVERAGE_ON" + BASHUNIT_COVERAGE="$_orig" + bashunit::runner::sync_coverage_flag +} + +function test_sync_coverage_flag_sets_zero_when_disabled() { + local _orig="${BASHUNIT_COVERAGE-}" + BASHUNIT_COVERAGE="false" + bashunit::runner::sync_coverage_flag + assert_same "0" "$_BASHUNIT_COVERAGE_ON" + BASHUNIT_COVERAGE="$_orig" + bashunit::runner::sync_coverage_flag +} + +function test_sync_coverage_flag_sets_zero_when_unset() { + local _orig="${BASHUNIT_COVERAGE-}" + unset BASHUNIT_COVERAGE + bashunit::runner::sync_coverage_flag + assert_same "0" "$_BASHUNIT_COVERAGE_ON" + BASHUNIT_COVERAGE="$_orig" + bashunit::runner::sync_coverage_flag +} + function test_detect_runtime_error_returns_empty_when_input_is_empty() { local actual actual="$(bashunit::runner::detect_runtime_error "")"