#!/usr/bin/env bash
# check_os.sh

# shellcheck disable=SC2034
_BASHUNIT_OS="Unknown"
_BASHUNIT_DISTRO="Unknown"

function bashunit::check_os::init() {
  _BASHUNIT_UNAME="$(uname)"
  if bashunit::check_os::is_linux; then
    _BASHUNIT_OS="Linux"
    if bashunit::check_os::is_ubuntu; then
      _BASHUNIT_DISTRO="Ubuntu"
    elif bashunit::check_os::is_alpine; then
      _BASHUNIT_DISTRO="Alpine"
    elif bashunit::check_os::is_nixos; then
      _BASHUNIT_DISTRO="NixOS"
    else
      _BASHUNIT_DISTRO="Other"
    fi
  elif bashunit::check_os::is_macos; then
    _BASHUNIT_OS="OSX"
  elif bashunit::check_os::is_windows; then
    _BASHUNIT_OS="Windows"
  else
    _BASHUNIT_OS="Unknown"
    _BASHUNIT_DISTRO="Unknown"
  fi
}

function bashunit::check_os::is_ubuntu() {
  command -v apt >/dev/null 2>&1
}

function bashunit::check_os::is_alpine() {
  command -v apk >/dev/null 2>&1
}

function bashunit::check_os::is_nixos() {
  [ -f /etc/NIXOS ] && return 0
  grep -q '^ID=nixos' /etc/os-release 2>/dev/null
}

_BASHUNIT_UNAME="$(uname)"

function bashunit::check_os::is_linux() {
  [ "$_BASHUNIT_UNAME" = "Linux" ]
}

function bashunit::check_os::is_macos() {
  [ "$_BASHUNIT_UNAME" = "Darwin" ]
}

function bashunit::check_os::is_windows() {
  case "$_BASHUNIT_UNAME" in
  *MINGW* | *MSYS* | *CYGWIN*)
    return 0
    ;;
  *)
    return 1
    ;;
  esac
}

function bashunit::check_os::is_busybox() {

  case "$_BASHUNIT_DISTRO" in

  "Alpine")
    return 0
    ;;
  *)
    return 1
    ;;
  esac
}

bashunit::check_os::init

export _BASHUNIT_OS
export _BASHUNIT_DISTRO
export -f bashunit::check_os::is_alpine
export -f bashunit::check_os::is_busybox
export -f bashunit::check_os::is_ubuntu
export -f bashunit::check_os::is_nixos

# str.sh

# Strip ANSI escape codes and control characters
function bashunit::str::strip_ansi() {
  local input="$1"
  echo -e "$input" | sed -E 's/\x1B\[[0-9;]*[mK]//g; s/[[:cntrl:]]//g'
}

function bashunit::str::rpad() {
  local left_text="$1"
  local right_word="$2"
  local width_padding="${3:-$TERMINAL_WIDTH}"
  # Subtract 1 more to account for the extra space
  local padding=$((width_padding - ${#right_word} - 1))
  if ((padding < 0)); then
    padding=0
  fi

  # Remove ANSI escape sequences (non-visible characters) for length calculation
  # shellcheck disable=SC2155
  local clean_left_text=$(bashunit::str::strip_ansi "$left_text")

  local is_truncated=false
  # If the visible left text exceeds the padding, truncate it and add "..."
  if [ ${#clean_left_text} -gt $padding ]; then
    local truncation_length=$((padding < 3 ? 0 : padding - 3))
    clean_left_text="${clean_left_text:0:$truncation_length}"
    is_truncated=true
  fi

  # Rebuild the text with ANSI codes intact, preserving the truncation
  local result_left_text=""
  local i=0
  local j=0
  while [ $i -lt ${#clean_left_text} ] && [ $j -lt ${#left_text} ]; do
    local char="${clean_left_text:$i:1}"
    local original_char="${left_text:$j:1}"

    # If the current character is part of an ANSI sequence, skip it and copy it
    if [ "$original_char" = $'\x1b' ]; then
      while [ "${left_text:$j:1}" != "m" ] && [ $j -lt ${#left_text} ]; do
        result_left_text="$result_left_text${left_text:$j:1}"
        ((j++))
      done
      result_left_text="$result_left_text${left_text:$j:1}" # Append the final 'm'
      ((j++))
    elif [ "$char" = "$original_char" ]; then
      # Match the actual character
      result_left_text="$result_left_text$char"
      ((i++))
      ((j++))
    else
      ((j++))
    fi
  done

  local remaining_space
  if $is_truncated; then
    result_left_text="$result_left_text..."
    # 1: due to a blank space
    # 3: due to the appended ...
    remaining_space=$((width_padding - ${#clean_left_text} - ${#right_word} - 1 - 3))
  else
    # Copy any remaining characters after the truncation point
    result_left_text="$result_left_text${left_text:$j}"
    remaining_space=$((width_padding - ${#clean_left_text} - ${#right_word} - 1))
  fi

  # Ensure the right word is placed exactly at the far right of the screen
  # filling the remaining space with padding
  if [ $remaining_space -lt 0 ]; then
    remaining_space=0
  fi

  printf "%s%${remaining_space}s %s\n" "$result_left_text" "" "$right_word"
}

# globals.sh
set -euo pipefail

# This file provides a set of global functions to developers.

function bashunit::current_dir() {
  dirname "${BASH_SOURCE[1]}"
}

function bashunit::current_filename() {
  basename "${BASH_SOURCE[1]}"
}

function bashunit::caller_filename() {
  dirname "${BASH_SOURCE[2]}"
}

function bashunit::caller_line() {
  echo "${BASH_LINENO[1]}"
}

function bashunit::current_timestamp() {
  date +"%Y-%m-%d %H:%M:%S"
}

function bashunit::is_command_available() {
  command -v "$1" >/dev/null 2>&1
}

function bashunit::random_str() {
  local length=${1:-6}
  local chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
  local str=''
  local i
  for ((i = 0; i < length; i++)); do
    str="$str${chars:RANDOM%${#chars}:1}"
  done
  echo "$str"
}

function bashunit::temp_file() {
  local prefix=${1:-bashunit}
  local test_prefix=""
  if [ -n "${BASHUNIT_CURRENT_TEST_ID:-}" ]; then
    # We're inside a test function - use test ID
    test_prefix="${BASHUNIT_CURRENT_TEST_ID}_"
  elif [ -n "${BASHUNIT_CURRENT_SCRIPT_ID:-}" ]; then
    # We're at script level (e.g., in set_up_before_script) - use script ID
    test_prefix="${BASHUNIT_CURRENT_SCRIPT_ID}_"
  fi
  "$MKTEMP" "$BASHUNIT_TEMP_DIR/${test_prefix}${prefix}.XXXXXXX"
}

function bashunit::temp_dir() {
  local prefix=${1:-bashunit}
  local test_prefix=""
  if [ -n "${BASHUNIT_CURRENT_TEST_ID:-}" ]; then
    # We're inside a test function - use test ID
    test_prefix="${BASHUNIT_CURRENT_TEST_ID}_"
  elif [ -n "${BASHUNIT_CURRENT_SCRIPT_ID:-}" ]; then
    # We're at script level (e.g., in set_up_before_script) - use script ID
    test_prefix="${BASHUNIT_CURRENT_SCRIPT_ID}_"
  fi
  "$MKTEMP" -d "$BASHUNIT_TEMP_DIR/${test_prefix}${prefix}.XXXXXXX"
}

function bashunit::cleanup_testcase_temp_files() {
  bashunit::internal_log "cleanup_testcase_temp_files"
  if [ -n "${BASHUNIT_CURRENT_TEST_ID:-}" ]; then
    rm -rf "$BASHUNIT_TEMP_DIR/${BASHUNIT_CURRENT_TEST_ID}"_*
  fi
}

function bashunit::cleanup_script_temp_files() {
  bashunit::internal_log "cleanup_script_temp_files"
  if [ -n "${BASHUNIT_CURRENT_SCRIPT_ID:-}" ]; then
    rm -rf "$BASHUNIT_TEMP_DIR/${BASHUNIT_CURRENT_SCRIPT_ID}"_*
  fi
}

# shellcheck disable=SC2145
function bashunit::log() {
  if ! bashunit::env::is_dev_mode_enabled; then
    return
  fi

  local level="$1"
  shift

  case "$level" in
  info | INFO) level="INFO" ;;
  debug | DEBUG) level="DEBUG" ;;
  warning | WARNING) level="WARNING" ;;
  critical | CRITICAL) level="CRITICAL" ;;
  error | ERROR) level="ERROR" ;;
  *)
    set -- "$level $@"
    level="INFO"
    ;;
  esac

  echo "$(bashunit::current_timestamp) [$level]: $* #${BASH_SOURCE[1]}:${BASH_LINENO[0]}" >>"$BASHUNIT_DEV_LOG"
}

function bashunit::internal_log() {
  if ! bashunit::env::is_dev_mode_enabled || ! bashunit::env::is_internal_log_enabled; then
    return
  fi

  echo "$(bashunit::current_timestamp) [INTERNAL]: $* #${BASH_SOURCE[1]}:${BASH_LINENO[0]}" >>"$BASHUNIT_DEV_LOG"
}

function bashunit::print_line() {
  local length="${1:-70}" # Default to 70 if not passed
  local char="${2:--}"    # Default to '-' if not passed
  printf '%*s\n' "$length" '' | tr ' ' "$char"
}

function bashunit::data_set() {
  local arg
  local first=true

  for arg in "$@"; do
    if [ "$first" = true ]; then
      # Bash 3.0 compatible: printf '%q' "" produces nothing in Bash 3.0
      if [ -z "$arg" ]; then
        printf "''"
      else
        printf '%q' "$arg"
      fi
      first=false
    else
      if [ -z "$arg" ]; then
        printf " ''"
      else
        printf ' %q' "$arg"
      fi
    fi
  done
  # Sentinel empty string at end
  printf " ''\n"
}

# dependencies.sh
set -euo pipefail

function bashunit::dependencies::has_perl() {
  command -v perl >/dev/null 2>&1
}

function bashunit::dependencies::has_powershell() {
  command -v powershell >/dev/null 2>&1
}

function bashunit::dependencies::has_adjtimex() {
  command -v adjtimex >/dev/null 2>&1
}

function bashunit::dependencies::has_bc() {
  command -v bc >/dev/null 2>&1
}

function bashunit::dependencies::has_awk() {
  command -v awk >/dev/null 2>&1
}

function bashunit::dependencies::has_git() {
  command -v git >/dev/null 2>&1
}

function bashunit::dependencies::has_curl() {
  command -v curl >/dev/null 2>&1
}

function bashunit::dependencies::has_wget() {
  command -v wget >/dev/null 2>&1
}

function bashunit::dependencies::has_python() {
  command -v python >/dev/null 2>&1
}

function bashunit::dependencies::has_node() {
  command -v node >/dev/null 2>&1
}

# io.sh

function bashunit::io::download_to() {
  local url="$1"
  local output="$2"
  if bashunit::dependencies::has_curl; then
    curl -L -J -o "$output" "$url" 2>/dev/null
  elif bashunit::dependencies::has_wget; then
    wget -q -O "$output" "$url" 2>/dev/null
  else
    return 1
  fi
}

# math.sh

function bashunit::math::calculate() {
  local expr="$*"

  if bashunit::dependencies::has_bc; then
    echo "$expr" | bc
    return
  fi

  case "$expr" in
  *.*)
    if bashunit::dependencies::has_awk; then
      awk "BEGIN { print ($expr) }"
      return
    fi
    # Downgrade to integer math by stripping decimals
    expr=$(echo "$expr" | sed -E 's/([0-9]+)\.[0-9]+/\1/g')
    ;;
  esac

  # Remove leading zeros from integers
  expr=$(echo "$expr" | sed -E 's/\b0*([1-9][0-9]*)/\1/g')

  local result=$((expr))
  echo "$result"
}

# parallel.sh

function bashunit::parallel::aggregate_test_results() {
  local temp_dir_parallel_test_suite=$1
  local IFS=$' \t\n'

  bashunit::internal_log "aggregate_test_results" "dir:$temp_dir_parallel_test_suite"

  local total_failed=0
  local total_passed=0
  local total_skipped=0
  local total_incomplete=0
  local total_snapshot=0

  local script_dir=""
  for script_dir in "$temp_dir_parallel_test_suite"/*; do
    shopt -s nullglob
    # Bash 3.0 compatible: separate declaration and assignment for arrays
    local result_files
    result_files=("$script_dir"/*.result)
    shopt -u nullglob

    if [ ${#result_files[@]} -eq 0 ]; then
      printf "%sNo tests found%s" "$_BASHUNIT_COLOR_SKIPPED" "$_BASHUNIT_COLOR_DEFAULT"
      continue
    fi

    local result_file=""
    for result_file in "${result_files[@]+"${result_files[@]}"}"; do
      local result_line
      result_line=$(<"$result_file")
      result_line="${result_line##*$'\n'}"

      local failed="${result_line##*##ASSERTIONS_FAILED=}"
      failed="${failed%%##*}"
      failed=${failed:-0}

      local passed="${result_line##*##ASSERTIONS_PASSED=}"
      passed="${passed%%##*}"
      passed=${passed:-0}

      local skipped="${result_line##*##ASSERTIONS_SKIPPED=}"
      skipped="${skipped%%##*}"
      skipped=${skipped:-0}

      local incomplete="${result_line##*##ASSERTIONS_INCOMPLETE=}"
      incomplete="${incomplete%%##*}"
      incomplete=${incomplete:-0}

      local snapshot="${result_line##*##ASSERTIONS_SNAPSHOT=}"
      snapshot="${snapshot%%##*}"
      snapshot=${snapshot:-0}

      local exit_code="${result_line##*##TEST_EXIT_CODE=}"
      exit_code="${exit_code%%##*}"
      exit_code=${exit_code:-0}

      # Add to the total counts
      total_failed=$((total_failed + failed))
      total_passed=$((total_passed + passed))
      total_skipped=$((total_skipped + skipped))
      total_incomplete=$((total_incomplete + incomplete))
      total_snapshot=$((total_snapshot + snapshot))

      if [ "${failed:-0}" -gt 0 ]; then
        bashunit::state::add_tests_failed
        continue
      fi

      if [ "${exit_code:-0}" -ne 0 ]; then
        bashunit::state::add_tests_failed
        continue
      fi

      if [ "${snapshot:-0}" -gt 0 ]; then
        bashunit::state::add_tests_snapshot
        continue
      fi

      if [ "${incomplete:-0}" -gt 0 ]; then
        bashunit::state::add_tests_incomplete
        continue
      fi

      if [ "${skipped:-0}" -gt 0 ]; then
        bashunit::state::add_tests_skipped
        continue
      fi

      # Check for risky test (zero assertions, no error)
      local total_for_test=$((failed + passed + skipped + incomplete + snapshot))
      if [ "$total_for_test" -eq 0 ] && [ "${exit_code:-0}" -eq 0 ]; then
        bashunit::state::add_tests_risky
        continue
      fi

      bashunit::state::add_tests_passed
    done
  done

  export _BASHUNIT_ASSERTIONS_FAILED=$total_failed
  export _BASHUNIT_ASSERTIONS_PASSED=$total_passed
  export _BASHUNIT_ASSERTIONS_SKIPPED=$total_skipped
  export _BASHUNIT_ASSERTIONS_INCOMPLETE=$total_incomplete
  export _BASHUNIT_ASSERTIONS_SNAPSHOT=$total_snapshot

  bashunit::internal_log "aggregate_totals" \
    "failed:$total_failed" \
    "passed:$total_passed" \
    "skipped:$total_skipped" \
    "incomplete:$total_incomplete" \
    "snapshot:$total_snapshot"
}

function bashunit::parallel::mark_stop_on_failure() {
  touch "$TEMP_FILE_PARALLEL_STOP_ON_FAILURE"
}

function bashunit::parallel::must_stop_on_failure() {
  [ -f "$TEMP_FILE_PARALLEL_STOP_ON_FAILURE" ]
}

function bashunit::parallel::cleanup() {
  # shellcheck disable=SC2153
  rm -rf "$TEMP_DIR_PARALLEL_TEST_SUITE"
}

function bashunit::parallel::init() {
  bashunit::parallel::cleanup
  mkdir -p "$TEMP_DIR_PARALLEL_TEST_SUITE"
}

function bashunit::parallel::is_enabled() {
  bashunit::internal_log "bashunit::parallel::is_enabled" \
    "requested:$BASHUNIT_PARALLEL_RUN" "os:${_BASHUNIT_OS:-Unknown}"

  if bashunit::env::is_parallel_run_enabled &&
    (bashunit::check_os::is_macos || bashunit::check_os::is_ubuntu || bashunit::check_os::is_windows); then
    return 0
  fi
  return 1
}

# env.sh

# shellcheck disable=SC2034

# Load .env file (skip if --skip-env-file is used to keep shell environment intact)
if [ "${BASHUNIT_SKIP_ENV_FILE:-false}" != "true" ]; then
  set -o allexport
  # shellcheck source=/dev/null
  [ -f ".env" ] && source .env
  set +o allexport
fi

_BASHUNIT_DEFAULT_DEFAULT_PATH="tests"
_BASHUNIT_DEFAULT_BOOTSTRAP="tests/bootstrap.sh"
_BASHUNIT_DEFAULT_DEV_LOG=""
_BASHUNIT_DEFAULT_LOG_JUNIT=""
_BASHUNIT_DEFAULT_REPORT_HTML=""

# Coverage defaults (following kcov, bashcov, SimpleCov conventions)
_BASHUNIT_DEFAULT_COVERAGE="false"
_BASHUNIT_DEFAULT_COVERAGE_PATHS=""
_BASHUNIT_DEFAULT_COVERAGE_EXCLUDE="tests/*,vendor/*,*_test.sh,*Test.sh"
_BASHUNIT_DEFAULT_COVERAGE_REPORT="coverage/lcov.info"
_BASHUNIT_DEFAULT_COVERAGE_REPORT_HTML=""
_BASHUNIT_DEFAULT_COVERAGE_MIN=""
_BASHUNIT_DEFAULT_COVERAGE_THRESHOLD_LOW="50"
_BASHUNIT_DEFAULT_COVERAGE_THRESHOLD_HIGH="80"

: "${BASHUNIT_DEFAULT_PATH:=${DEFAULT_PATH:=$_BASHUNIT_DEFAULT_DEFAULT_PATH}}"
: "${BASHUNIT_DEV_LOG:=${DEV_LOG:=$_BASHUNIT_DEFAULT_DEV_LOG}}"
: "${BASHUNIT_BOOTSTRAP:=${BOOTSTRAP:=$_BASHUNIT_DEFAULT_BOOTSTRAP}}"
: "${BASHUNIT_BOOTSTRAP_ARGS:=${BOOTSTRAP_ARGS:=}}"
: "${BASHUNIT_LOG_JUNIT:=${LOG_JUNIT:=$_BASHUNIT_DEFAULT_LOG_JUNIT}}"
: "${BASHUNIT_REPORT_HTML:=${REPORT_HTML:=$_BASHUNIT_DEFAULT_REPORT_HTML}}"

# Coverage
: "${BASHUNIT_COVERAGE:=${COVERAGE:=$_BASHUNIT_DEFAULT_COVERAGE}}"
: "${BASHUNIT_COVERAGE_PATHS:=${COVERAGE_PATHS:=$_BASHUNIT_DEFAULT_COVERAGE_PATHS}}"
: "${BASHUNIT_COVERAGE_EXCLUDE:=${COVERAGE_EXCLUDE:=$_BASHUNIT_DEFAULT_COVERAGE_EXCLUDE}}"
: "${BASHUNIT_COVERAGE_REPORT:=${COVERAGE_REPORT:=$_BASHUNIT_DEFAULT_COVERAGE_REPORT}}"
: "${BASHUNIT_COVERAGE_REPORT_HTML:=${COVERAGE_REPORT_HTML:=$_BASHUNIT_DEFAULT_COVERAGE_REPORT_HTML}}"
: "${BASHUNIT_COVERAGE_MIN:=${COVERAGE_MIN:=$_BASHUNIT_DEFAULT_COVERAGE_MIN}}"
: "${BASHUNIT_COVERAGE_THRESHOLD_LOW:=${COVERAGE_THRESHOLD_LOW:=$_BASHUNIT_DEFAULT_COVERAGE_THRESHOLD_LOW}}"
: "${BASHUNIT_COVERAGE_THRESHOLD_HIGH:=${COVERAGE_THRESHOLD_HIGH:=$_BASHUNIT_DEFAULT_COVERAGE_THRESHOLD_HIGH}}"

# Booleans
_BASHUNIT_DEFAULT_PARALLEL_RUN="false"
_BASHUNIT_DEFAULT_SHOW_HEADER="true"
_BASHUNIT_DEFAULT_HEADER_ASCII_ART="false"
_BASHUNIT_DEFAULT_SIMPLE_OUTPUT="false"
_BASHUNIT_DEFAULT_STOP_ON_FAILURE="false"
_BASHUNIT_DEFAULT_SHOW_EXECUTION_TIME="true"
_BASHUNIT_DEFAULT_VERBOSE="false"
_BASHUNIT_DEFAULT_BENCH_MODE="false"
_BASHUNIT_DEFAULT_NO_OUTPUT="false"
_BASHUNIT_DEFAULT_INTERNAL_LOG="false"
_BASHUNIT_DEFAULT_SHOW_SKIPPED="false"
_BASHUNIT_DEFAULT_SHOW_INCOMPLETE="false"
_BASHUNIT_DEFAULT_STRICT_MODE="false"
_BASHUNIT_DEFAULT_STOP_ON_ASSERTION_FAILURE="true"
_BASHUNIT_DEFAULT_SKIP_ENV_FILE="false"
_BASHUNIT_DEFAULT_LOGIN_SHELL="false"
_BASHUNIT_DEFAULT_FAILURES_ONLY="false"
_BASHUNIT_DEFAULT_NO_COLOR="false"
_BASHUNIT_DEFAULT_SHOW_OUTPUT_ON_FAILURE="true"
_BASHUNIT_DEFAULT_NO_PROGRESS="false"
_BASHUNIT_DEFAULT_OUTPUT_FORMAT=""

: "${BASHUNIT_PARALLEL_RUN:=${PARALLEL_RUN:=$_BASHUNIT_DEFAULT_PARALLEL_RUN}}"
: "${BASHUNIT_PARALLEL_JOBS:=0}"
: "${BASHUNIT_SHOW_HEADER:=${SHOW_HEADER:=$_BASHUNIT_DEFAULT_SHOW_HEADER}}"
: "${BASHUNIT_HEADER_ASCII_ART:=${HEADER_ASCII_ART:=$_BASHUNIT_DEFAULT_HEADER_ASCII_ART}}"
: "${BASHUNIT_SIMPLE_OUTPUT:=${SIMPLE_OUTPUT:=$_BASHUNIT_DEFAULT_SIMPLE_OUTPUT}}"
: "${BASHUNIT_STOP_ON_FAILURE:=${STOP_ON_FAILURE:=$_BASHUNIT_DEFAULT_STOP_ON_FAILURE}}"
: "${BASHUNIT_SHOW_EXECUTION_TIME:=${SHOW_EXECUTION_TIME:=$_BASHUNIT_DEFAULT_SHOW_EXECUTION_TIME}}"
: "${BASHUNIT_VERBOSE:=${VERBOSE:=$_BASHUNIT_DEFAULT_VERBOSE}}"
: "${BASHUNIT_BENCH_MODE:=${BENCH_MODE:=$_BASHUNIT_DEFAULT_BENCH_MODE}}"
: "${BASHUNIT_NO_OUTPUT:=${NO_OUTPUT:=$_BASHUNIT_DEFAULT_NO_OUTPUT}}"
: "${BASHUNIT_INTERNAL_LOG:=${INTERNAL_LOG:=$_BASHUNIT_DEFAULT_INTERNAL_LOG}}"
: "${BASHUNIT_SHOW_SKIPPED:=${SHOW_SKIPPED:=$_BASHUNIT_DEFAULT_SHOW_SKIPPED}}"
: "${BASHUNIT_SHOW_INCOMPLETE:=${SHOW_INCOMPLETE:=$_BASHUNIT_DEFAULT_SHOW_INCOMPLETE}}"
: "${BASHUNIT_STRICT_MODE:=${STRICT_MODE:=$_BASHUNIT_DEFAULT_STRICT_MODE}}"
: "${BASHUNIT_STOP_ON_ASSERTION_FAILURE:=${STOP_ON_ASSERTION_FAILURE:=$_BASHUNIT_DEFAULT_STOP_ON_ASSERTION_FAILURE}}"
: "${BASHUNIT_SKIP_ENV_FILE:=${SKIP_ENV_FILE:=$_BASHUNIT_DEFAULT_SKIP_ENV_FILE}}"
: "${BASHUNIT_LOGIN_SHELL:=${LOGIN_SHELL:=$_BASHUNIT_DEFAULT_LOGIN_SHELL}}"
: "${BASHUNIT_FAILURES_ONLY:=${FAILURES_ONLY:=$_BASHUNIT_DEFAULT_FAILURES_ONLY}}"
: "${BASHUNIT_SHOW_OUTPUT_ON_FAILURE:=${SHOW_OUTPUT_ON_FAILURE:=$_BASHUNIT_DEFAULT_SHOW_OUTPUT_ON_FAILURE}}"
: "${BASHUNIT_NO_PROGRESS:=${NO_PROGRESS:=$_BASHUNIT_DEFAULT_NO_PROGRESS}}"
: "${BASHUNIT_OUTPUT_FORMAT:=${OUTPUT_FORMAT:=$_BASHUNIT_DEFAULT_OUTPUT_FORMAT}}"
# Support NO_COLOR standard (https://no-color.org)
if [ -n "${NO_COLOR:-}" ]; then
  BASHUNIT_NO_COLOR="true"
else
  : "${BASHUNIT_NO_COLOR:=$_BASHUNIT_DEFAULT_NO_COLOR}"
fi

function bashunit::env::is_parallel_run_enabled() {
  [ "$BASHUNIT_PARALLEL_RUN" = "true" ]
}

function bashunit::env::is_show_header_enabled() {
  [ "$BASHUNIT_SHOW_HEADER" = "true" ]
}

function bashunit::env::is_header_ascii_art_enabled() {
  [ "$BASHUNIT_HEADER_ASCII_ART" = "true" ]
}

function bashunit::env::is_simple_output_enabled() {
  [ "$BASHUNIT_SIMPLE_OUTPUT" = "true" ]
}

function bashunit::env::is_stop_on_failure_enabled() {
  [ "$BASHUNIT_STOP_ON_FAILURE" = "true" ]
}

function bashunit::env::is_show_execution_time_enabled() {
  [ "$BASHUNIT_SHOW_EXECUTION_TIME" = "true" ]
}

function bashunit::env::is_dev_mode_enabled() {
  [ -n "$BASHUNIT_DEV_LOG" ]
}

function bashunit::env::is_internal_log_enabled() {
  [ "$BASHUNIT_INTERNAL_LOG" = "true" ]
}

function bashunit::env::is_verbose_enabled() {
  [ "$BASHUNIT_VERBOSE" = "true" ]
}

function bashunit::env::is_bench_mode_enabled() {
  [ "$BASHUNIT_BENCH_MODE" = "true" ]
}

function bashunit::env::is_no_output_enabled() {
  [ "$BASHUNIT_NO_OUTPUT" = "true" ]
}

function bashunit::env::is_show_skipped_enabled() {
  [ "$BASHUNIT_SHOW_SKIPPED" = "true" ]
}

function bashunit::env::is_show_incomplete_enabled() {
  [ "$BASHUNIT_SHOW_INCOMPLETE" = "true" ]
}

function bashunit::env::is_strict_mode_enabled() {
  [ "$BASHUNIT_STRICT_MODE" = "true" ]
}

function bashunit::env::is_stop_on_assertion_failure_enabled() {
  [ "$BASHUNIT_STOP_ON_ASSERTION_FAILURE" = "true" ]
}

function bashunit::env::is_skip_env_file_enabled() {
  [ "$BASHUNIT_SKIP_ENV_FILE" = "true" ]
}

function bashunit::env::is_login_shell_enabled() {
  [ "$BASHUNIT_LOGIN_SHELL" = "true" ]
}

function bashunit::env::is_failures_only_enabled() {
  [ "$BASHUNIT_FAILURES_ONLY" = "true" ]
}

function bashunit::env::is_show_output_on_failure_enabled() {
  [ "$BASHUNIT_SHOW_OUTPUT_ON_FAILURE" = "true" ]
}

function bashunit::env::is_no_progress_enabled() {
  [ "$BASHUNIT_NO_PROGRESS" = "true" ]
}

function bashunit::env::is_no_color_enabled() {
  [ "$BASHUNIT_NO_COLOR" = "true" ]
}

function bashunit::env::is_coverage_enabled() {
  [ "$BASHUNIT_COVERAGE" = "true" ]
}

function bashunit::env::is_tap_output_enabled() {
  [ "$BASHUNIT_OUTPUT_FORMAT" = "tap" ]
}

function bashunit::env::active_internet_connection() {
  if [ "${BASHUNIT_NO_NETWORK:-}" = "true" ]; then
    return 1
  fi

  if command -v curl >/dev/null 2>&1; then
    curl -sfI https://github.com >/dev/null 2>&1 && return 0
  elif command -v wget >/dev/null 2>&1; then
    wget -q --spider https://github.com && return 0
  fi

  if ping -c 1 -W 3 google.com &>/dev/null; then
    return 0
  fi

  return 1
}

function bashunit::env::find_terminal_width() {
  local cols=""

  if [ -z "$cols" ] && command -v tput >/dev/null; then
    cols=$(tput cols 2>/dev/null)
  fi

  if [ -z "$cols" ] && command -v stty >/dev/null; then
    cols=$(stty size 2>/dev/null | cut -d' ' -f2)
  fi

  # Directly echo the value with fallback
  echo "${cols:-100}"
}

function bashunit::env::print_verbose() {
  bashunit::internal_log "Printing verbose environment variables"
  local IFS=$' \t\n'
  # Bash 3.0 compatible: separate declaration and assignment for arrays
  local keys
  keys=(
    "BASHUNIT_DEFAULT_PATH"
    "BASHUNIT_DEV_LOG"
    "BASHUNIT_BOOTSTRAP"
    "BASHUNIT_BOOTSTRAP_ARGS"
    "BASHUNIT_LOG_JUNIT"
    "BASHUNIT_REPORT_HTML"
    "BASHUNIT_PARALLEL_RUN"
    "BASHUNIT_SHOW_HEADER"
    "BASHUNIT_HEADER_ASCII_ART"
    "BASHUNIT_SIMPLE_OUTPUT"
    "BASHUNIT_STOP_ON_FAILURE"
    "BASHUNIT_SHOW_EXECUTION_TIME"
    "BASHUNIT_VERBOSE"
    "BASHUNIT_STRICT_MODE"
    "BASHUNIT_STOP_ON_ASSERTION_FAILURE"
    "BASHUNIT_SKIP_ENV_FILE"
    "BASHUNIT_LOGIN_SHELL"
    "BASHUNIT_COVERAGE"
    "BASHUNIT_COVERAGE_PATHS"
    "BASHUNIT_COVERAGE_EXCLUDE"
    "BASHUNIT_COVERAGE_REPORT"
    "BASHUNIT_COVERAGE_REPORT_HTML"
    "BASHUNIT_COVERAGE_MIN"
  )

  local max_length=0

  local key
  for key in "${keys[@]+"${keys[@]}"}"; do
    if ((${#key} > max_length)); then
      max_length=${#key}
    fi
  done

  for key in "${keys[@]+"${keys[@]}"}"; do
    bashunit::internal_log "$key=${!key}"
    printf "%s:%*s%s\n" "$key" $((max_length - ${#key} + 1)) "" "${!key}"
  done
}

EXIT_CODE_STOP_ON_FAILURE=4
# Use a unique directory per run to avoid conflicts when bashunit is invoked
# recursively or multiple instances are executed in parallel.
TEMP_DIR_PARALLEL_TEST_SUITE="${TMPDIR:-/tmp}/bashunit/parallel/${_BASHUNIT_OS:-Unknown}/$(bashunit::random_str 8)"
TEMP_FILE_PARALLEL_STOP_ON_FAILURE="$TEMP_DIR_PARALLEL_TEST_SUITE/.stop-on-failure"
TERMINAL_WIDTH="$(bashunit::env::find_terminal_width)"
CAT="$(command -v cat)"
GREP="$(command -v grep)"
MKTEMP="$(command -v mktemp)"
FAILURES_OUTPUT_PATH=$("$MKTEMP")
SKIPPED_OUTPUT_PATH=$("$MKTEMP")
INCOMPLETE_OUTPUT_PATH=$("$MKTEMP")
RISKY_OUTPUT_PATH=$("$MKTEMP")

# Initialize temp directory once at startup for performance
BASHUNIT_TEMP_DIR="${TMPDIR:-/tmp}/bashunit/tmp"
mkdir -p "$BASHUNIT_TEMP_DIR" 2>/dev/null || true

if bashunit::env::is_dev_mode_enabled; then
  bashunit::internal_log "info" "Dev log enabled" "file:$BASHUNIT_DEV_LOG"
fi

# coverage.sh
# shellcheck disable=SC2094

# Coverage data storage
# Use :- to preserve inherited values from parent bashunit processes
_BASHUNIT_COVERAGE_DATA_FILE="${_BASHUNIT_COVERAGE_DATA_FILE:-}"
_BASHUNIT_COVERAGE_TRACKED_FILES="${_BASHUNIT_COVERAGE_TRACKED_FILES:-}"

# Simple file-based cache for tracked files (Bash 3.0 compatible)
# The tracked cache file stores files that have already been processed
_BASHUNIT_COVERAGE_TRACKED_CACHE_FILE="${_BASHUNIT_COVERAGE_TRACKED_CACHE_FILE:-}"

# File to store which tests hit each line (for detailed coverage tooltips)
_BASHUNIT_COVERAGE_TEST_HITS_FILE="${_BASHUNIT_COVERAGE_TEST_HITS_FILE:-}"

# In-memory buffer for coverage data (reduces file I/O)
_BASHUNIT_COVERAGE_BUFFER=""
_BASHUNIT_COVERAGE_BUFFER_COUNT=0
_BASHUNIT_COVERAGE_BUFFER_LIMIT=100
_BASHUNIT_COVERAGE_HITS_BUFFER=""

# In-memory caches for hot-path lookups (avoids grep + subshells)
_BASHUNIT_COVERAGE_TRACK_CACHE=""
_BASHUNIT_COVERAGE_PATH_CACHE=""
_BASHUNIT_COVERAGE_IS_PARALLEL=""

# Auto-discover coverage paths from test file names
# When no explicit coverage paths are set, find source files matching test file base names
# Example: tests/unit/assert_test.sh -> finds src/assert.sh, src/assert_*.sh
function bashunit::coverage::auto_discover_paths() {
  local project_root
  project_root="$(pwd)"
  local -a discovered_paths=()
  local discovered_paths_count=0
  local test_file

  for test_file in "$@"; do
    # Extract base name: tests/unit/assert_test.sh -> assert_test.sh
    local file_basename
    file_basename=$(basename "$test_file")

    # Remove test suffixes to get source name: assert_test.sh -> assert
    local source_name="${file_basename%_test.sh}"
    [ "$source_name" = "$file_basename" ] && source_name="${file_basename%Test.sh}"
    [ "$source_name" = "$file_basename" ] && continue # Not a test file pattern

    # Find matching source files recursively
    local found_file
    while IFS= read -r -d '' found_file; do
      # Skip test files and vendor directories
      case "$found_file" in
      *test* | *Test* | *vendor* | *node_modules*) continue ;;
      esac
      discovered_paths[discovered_paths_count]="$found_file"
      discovered_paths_count=$((discovered_paths_count + 1))
    done < <(find "$project_root" -name "${source_name}*.sh" -type f -print0 2>/dev/null)
  done

  # Return unique paths, comma-separated
  if [ "$discovered_paths_count" -gt 0 ]; then
    printf '%s\n' "${discovered_paths[@]}" | sort -u | tr '\n' ',' | sed 's/,$//'
  fi
}

function bashunit::coverage::init() {
  if ! bashunit::env::is_coverage_enabled; then
    return 0
  fi

  # Skip coverage init if we're a subprocess of another coverage-enabled bashunit
  # This prevents nested bashunit calls (e.g., in acceptance tests) from
  # interfering with the parent's coverage tracking
  if [ -n "${_BASHUNIT_COVERAGE_DATA_FILE:-}" ]; then
    export BASHUNIT_COVERAGE=false
    return 0
  fi

  # Create coverage data directory with unique name
  # Use $$ (PID) + $RANDOM to avoid conflicts when tests call coverage::init
  local coverage_dir
  coverage_dir="${BASHUNIT_TEMP_DIR:-/tmp}/bashunit-coverage-$$-$RANDOM"
  mkdir -p "$coverage_dir"

  _BASHUNIT_COVERAGE_DATA_FILE="${coverage_dir}/hits.dat"
  _BASHUNIT_COVERAGE_TRACKED_FILES="${coverage_dir}/files.dat"
  _BASHUNIT_COVERAGE_TRACKED_CACHE_FILE="${coverage_dir}/cache.dat"
  _BASHUNIT_COVERAGE_TEST_HITS_FILE="${coverage_dir}/test_hits.dat"

  # Initialize empty files
  : >"$_BASHUNIT_COVERAGE_DATA_FILE"
  : >"$_BASHUNIT_COVERAGE_TRACKED_FILES"
  : >"$_BASHUNIT_COVERAGE_TRACKED_CACHE_FILE"
  : >"$_BASHUNIT_COVERAGE_TEST_HITS_FILE"

  # Reset in-memory caches and buffers
  _BASHUNIT_COVERAGE_BUFFER=""
  _BASHUNIT_COVERAGE_BUFFER_COUNT=0
  _BASHUNIT_COVERAGE_HITS_BUFFER=""
  _BASHUNIT_COVERAGE_TRACK_CACHE=""
  _BASHUNIT_COVERAGE_PATH_CACHE=""
  _BASHUNIT_COVERAGE_IS_PARALLEL=""

  export _BASHUNIT_COVERAGE_DATA_FILE
  export _BASHUNIT_COVERAGE_TRACKED_FILES
  export _BASHUNIT_COVERAGE_TRACKED_CACHE_FILE
  export _BASHUNIT_COVERAGE_TEST_HITS_FILE
}

function bashunit::coverage::enable_trap() {
  if ! bashunit::env::is_coverage_enabled; then
    return 0
  fi

  # Enable trap inheritance into functions
  set -T

  # 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[0]:-}" "${LINENO:-}"' DEBUG
}

function bashunit::coverage::disable_trap() {
  trap - DEBUG
  set +T
  # Flush any remaining buffered coverage data
  bashunit::coverage::flush_buffer
}

# Normalize file path to absolute
function bashunit::coverage::normalize_path() {
  local file="$1"

  # Normalize path to absolute
  if [ -f "$file" ]; then
    echo "$(cd "$(dirname "$file")" && pwd)/$(basename "$file")"
  else
    echo "$file"
  fi
}

# Get deduplicated list of tracked files
function bashunit::coverage::get_tracked_files() {
  if [ ! -f "$_BASHUNIT_COVERAGE_TRACKED_FILES" ]; then
    return
  fi
  sort -u "$_BASHUNIT_COVERAGE_TRACKED_FILES"
}

# Get coverage class (high/medium/low) based on percentage
function bashunit::coverage::get_coverage_class() {
  local pct="$1"
  if [ "$pct" -ge "${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80}" ]; then
    echo "high"
  elif [ "$pct" -ge "${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50}" ]; then
    echo "medium"
  else
    echo "low"
  fi
}

# Calculate percentage from hit and executable counts
function bashunit::coverage::calculate_percentage() {
  local hit="$1"
  local executable="$2"
  if [ "$executable" -gt 0 ]; then
    echo $((hit * 100 / executable))
  else
    echo "0"
  fi
}

# Get file coverage stats as "executable:hit:pct:class"
function bashunit::coverage::get_file_stats() {
  local file="$1"
  local executable hit pct class
  executable=$(bashunit::coverage::get_executable_lines "$file")
  hit=$(bashunit::coverage::get_hit_lines "$file")
  pct=$(bashunit::coverage::calculate_percentage "$hit" "$executable")
  class=$(bashunit::coverage::get_coverage_class "$pct")
  echo "${executable}:${hit}:${pct}:${class}"
}

function bashunit::coverage::record_line() {
  local file="$1"
  local lineno="$2"

  # Skip if no file or line
  { [ -z "$file" ] || [ -z "$lineno" ]; } && return 0

  # Skip if coverage data file doesn't exist (trap inherited by child process)
  [ -z "$_BASHUNIT_COVERAGE_DATA_FILE" ] && return 0

  # Fast in-memory should_track cache (avoids grep + file I/O per line)
  case "$_BASHUNIT_COVERAGE_TRACK_CACHE" in
  *"|${file}:0|"*) return 0 ;;
  *"|${file}:1|"*) ;;
  *)
    # Not cached yet — run full check and cache result
    if bashunit::coverage::should_track "$file"; then
      _BASHUNIT_COVERAGE_TRACK_CACHE="${_BASHUNIT_COVERAGE_TRACK_CACHE}|${file}:1|"
    else
      _BASHUNIT_COVERAGE_TRACK_CACHE="${_BASHUNIT_COVERAGE_TRACK_CACHE}|${file}:0|"
      return 0
    fi
    ;;
  esac

  # Fast in-memory path normalization cache (avoids cd + pwd subshell per line)
  local normalized_file=""
  case "$_BASHUNIT_COVERAGE_PATH_CACHE" in
  *"|${file}="*)
    # Extract cached value
    normalized_file="${_BASHUNIT_COVERAGE_PATH_CACHE#*"|${file}="}"
    normalized_file="${normalized_file%%"|"*}"
    ;;
  *)
    normalized_file=$(bashunit::coverage::normalize_path "$file")
    _BASHUNIT_COVERAGE_PATH_CACHE="${_BASHUNIT_COVERAGE_PATH_CACHE}|${file}=${normalized_file}|"
    ;;
  esac

  # Buffer the coverage data in memory
  _BASHUNIT_COVERAGE_BUFFER="${_BASHUNIT_COVERAGE_BUFFER}${normalized_file}:${lineno}
"
  # Also buffer test hit data if in a test context
  if [ -n "${_BASHUNIT_COVERAGE_CURRENT_TEST_FILE:-}" ] &&
    [ -n "${_BASHUNIT_COVERAGE_CURRENT_TEST_FN:-}" ]; then
    _BASHUNIT_COVERAGE_HITS_BUFFER="${_BASHUNIT_COVERAGE_HITS_BUFFER}${normalized_file}:${lineno}|${_BASHUNIT_COVERAGE_CURRENT_TEST_FILE}:${_BASHUNIT_COVERAGE_CURRENT_TEST_FN}
"
  fi

  _BASHUNIT_COVERAGE_BUFFER_COUNT=$((_BASHUNIT_COVERAGE_BUFFER_COUNT + 1))

  # Flush buffer to disk when threshold is reached
  if [ "$_BASHUNIT_COVERAGE_BUFFER_COUNT" -ge \
    "$_BASHUNIT_COVERAGE_BUFFER_LIMIT" ]; then
    bashunit::coverage::flush_buffer
  fi
}

function bashunit::coverage::flush_buffer() {
  [ -z "$_BASHUNIT_COVERAGE_BUFFER" ] && return 0

  # Determine output files (parallel-safe)
  local data_file="$_BASHUNIT_COVERAGE_DATA_FILE"
  local test_hits_file="$_BASHUNIT_COVERAGE_TEST_HITS_FILE"

  # Cache the parallel check to avoid function calls
  if [ -z "$_BASHUNIT_COVERAGE_IS_PARALLEL" ]; then
    if bashunit::parallel::is_enabled; then
      _BASHUNIT_COVERAGE_IS_PARALLEL="yes"
    else
      _BASHUNIT_COVERAGE_IS_PARALLEL="no"
    fi
  fi

  if [ "$_BASHUNIT_COVERAGE_IS_PARALLEL" = "yes" ]; then
    data_file="${_BASHUNIT_COVERAGE_DATA_FILE}.$$"
    test_hits_file="${_BASHUNIT_COVERAGE_TEST_HITS_FILE}.$$"
  fi

  # Write buffered data in a single I/O operation
  printf '%s' "$_BASHUNIT_COVERAGE_BUFFER" >>"$data_file"

  if [ -n "$_BASHUNIT_COVERAGE_HITS_BUFFER" ]; then
    printf '%s' "$_BASHUNIT_COVERAGE_HITS_BUFFER" >>"$test_hits_file"
  fi

  # Reset buffer
  _BASHUNIT_COVERAGE_BUFFER=""
  _BASHUNIT_COVERAGE_HITS_BUFFER=""
  _BASHUNIT_COVERAGE_BUFFER_COUNT=0
}

function bashunit::coverage::should_track() {
  local file="$1"

  # Skip empty paths
  [ -z "$file" ] && return 1

  # Skip if tracked files list doesn't exist (trap inherited by child process)
  [ -z "$_BASHUNIT_COVERAGE_TRACKED_FILES" ] && return 1

  # Check file-based cache for previous decision (Bash 3.0 compatible)
  # Cache format: "file:0" for excluded, "file:1" for tracked
  # In parallel mode, use per-process cache to avoid race conditions
  local cache_file="$_BASHUNIT_COVERAGE_TRACKED_CACHE_FILE"
  if bashunit::parallel::is_enabled && [ -n "$cache_file" ]; then
    cache_file="${cache_file}.$$"
    # Initialize per-process cache if needed
    [ ! -f "$cache_file" ] && [ -d "$(dirname "$cache_file")" ] && : >"$cache_file"
  fi
  if [ -n "$cache_file" ] && [ -f "$cache_file" ]; then
    local cached_decision
    # Use || true to prevent exit in strict mode when grep finds no match
    cached_decision=$(grep "^${file}:" "$cache_file" 2>/dev/null | head -1) || true
    if [ -n "$cached_decision" ]; then
      [ "${cached_decision##*:}" = "1" ] && return 0 || return 1
    fi
  fi

  # Normalize path
  local normalized_file
  normalized_file=$(bashunit::coverage::normalize_path "$file")

  # Check exclusion patterns
  # Save and restore IFS to avoid corrupting caller's environment
  local old_ifs="$IFS"
  IFS=','
  local pattern
  for pattern in $BASHUNIT_COVERAGE_EXCLUDE; do
    # shellcheck disable=SC2254
    case "$normalized_file" in
    *$pattern*)
      IFS="$old_ifs"
      # Cache exclusion decision (use per-process cache in parallel mode)
      { [ -n "$cache_file" ] && [ -f "$cache_file" ]; } && echo "${file}:0" >>"$cache_file"
      return 1
      ;;
    esac
  done

  # Check inclusion paths
  local matched=false
  local path
  for path in $BASHUNIT_COVERAGE_PATHS; do
    # Resolve relative paths
    local resolved_path
    case "$path" in
    /*)
      resolved_path="$path"
      ;;
    *)
      resolved_path="$(pwd)/$path"
      ;;
    esac

    case "$normalized_file" in
    "$resolved_path"*)
      matched=true
      break
      ;;
    esac
  done
  IFS="$old_ifs"

  if [ "$matched" = "false" ]; then
    # Cache exclusion decision (use per-process cache in parallel mode)
    { [ -n "$cache_file" ] && [ -f "$cache_file" ]; } && echo "${file}:0" >>"$cache_file"
    return 1
  fi

  # Cache tracking decision (use per-process cache in parallel mode)
  { [ -n "$cache_file" ] && [ -f "$cache_file" ]; } && echo "${file}:1" >>"$cache_file"

  # Track this file for later reporting
  # In parallel mode, use a per-process file to avoid race conditions
  local tracked_file="$_BASHUNIT_COVERAGE_TRACKED_FILES"
  if bashunit::parallel::is_enabled; then
    tracked_file="${_BASHUNIT_COVERAGE_TRACKED_FILES}.$$"
  fi

  # Only write if parent directory exists
  if [ -d "$(dirname "$tracked_file")" ]; then
    # Check if not already written to avoid duplicates
    if ! grep -q "^${normalized_file}$" "$tracked_file" 2>/dev/null; then
      echo "$normalized_file" >>"$tracked_file"
    fi
  fi

  return 0
}

function bashunit::coverage::aggregate_parallel() {
  # Aggregate per-process coverage files created during parallel execution
  local base_file="$_BASHUNIT_COVERAGE_DATA_FILE"
  local tracked_base="$_BASHUNIT_COVERAGE_TRACKED_FILES"
  local test_hits_base="$_BASHUNIT_COVERAGE_TEST_HITS_FILE"

  # Find and merge all per-process coverage data files
  # Use nullglob to handle case when no files match
  local pid_files pid_file
  pid_files=$(ls -1 "${base_file}."* 2>/dev/null) || true
  if [ -n "$pid_files" ]; then
    while IFS= read -r pid_file; do
      [ -f "$pid_file" ] || continue
      cat "$pid_file" >>"$base_file"
      rm -f "$pid_file"
    done <<<"$pid_files"
  fi

  # Find and merge all per-process tracked files lists
  pid_files=$(ls -1 "${tracked_base}."* 2>/dev/null) || true
  if [ -n "$pid_files" ]; then
    while IFS= read -r pid_file; do
      [ -f "$pid_file" ] || continue
      cat "$pid_file" >>"$tracked_base"
      rm -f "$pid_file"
    done <<<"$pid_files"
  fi

  # Find and merge all per-process test hits files
  if [ -n "$test_hits_base" ]; then
    pid_files=$(ls -1 "${test_hits_base}."* 2>/dev/null) || true
    if [ -n "$pid_files" ]; then
      while IFS= read -r pid_file; do
        [ -f "$pid_file" ] || continue
        cat "$pid_file" >>"$test_hits_base"
        rm -f "$pid_file"
      done <<<"$pid_files"
    fi
  fi

  # Deduplicate tracked files
  if [ -f "$tracked_base" ]; then
    sort -u "$tracked_base" -o "$tracked_base"
  fi
}

# Pre-compiled regex pattern for function declarations (performance optimization)
# Matches: function foo() { OR foo() { OR function foo() OR foo()
# Does NOT match single-line functions with body: function foo() { echo "hi"; }
_BASHUNIT_COVERAGE_FUNC_PATTERN='^[[:space:]]*(function[[:space:]]+)?'
_BASHUNIT_COVERAGE_FUNC_PATTERN="${_BASHUNIT_COVERAGE_FUNC_PATTERN}"'[a-zA-Z_][a-zA-Z0-9_:]*[[:space:]]*\(\)[[:space:]]*\{?[[:space:]]*$'

# Check if a line is executable (used by get_executable_lines and report_lcov)
# Arguments: line content, line number
# Returns: 0 if executable, 1 if not
function bashunit::coverage::is_executable_line() {
  local line="$1"
  local lineno="$2"

  # Unused but kept for API compatibility
  : "$lineno"

  # Skip empty lines (line with only whitespace)
  [ -z "${line// /}" ] && return 1

  # Skip comment-only lines (including shebang)
  [ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*#' || true)" -gt 0 ] && return 1

  # Skip function declaration lines (but not single-line functions with body)
  [ "$(echo "$line" | "$GREP" -cE "$_BASHUNIT_COVERAGE_FUNC_PATTERN" || true)" -gt 0 ] && return 1

  # Skip lines with only braces
  [ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*[\{\}][[:space:]]*$' || true)" -gt 0 ] && return 1

  # Skip control flow keywords (then, else, fi, do, done, esac, in, ;;, ;&, ;;&)
  [ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*(then|else|fi|do|done|esac|in|;;|;;&|;&)[[:space:]]*(#.*)?$' || true)" -gt 0 ] && return 1

  # Skip case patterns like "--option)" or "*)"
  [ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*[^\)]+\)[[:space:]]*$' || true)" -gt 0 ] && return 1

  # Skip standalone ) for arrays/subshells
  [ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*\)[[:space:]]*(#.*)?$' || true)" -gt 0 ] && return 1

  return 0
}

function bashunit::coverage::get_executable_lines() {
  local file="$1"
  local count=0
  local lineno=0
  local line

  while IFS= read -r line || [ -n "$line" ]; do
    ((lineno++))
    bashunit::coverage::is_executable_line "$line" "$lineno" && ((count++))
  done <"$file"

  echo "$count"
}

function bashunit::coverage::get_hit_lines() {
  local file="$1"

  if [ ! -f "$_BASHUNIT_COVERAGE_DATA_FILE" ]; then
    echo "0"
    return
  fi

  # Get unique hit line numbers
  local hit_lines
  hit_lines=$( (grep "^${file}:" "$_BASHUNIT_COVERAGE_DATA_FILE" 2>/dev/null || true) |
    cut -d: -f2 | sort -u)

  if [ -z "$hit_lines" ]; then
    echo "0"
    return
  fi

  # Only count hits that correspond to executable lines
  # This prevents >100% coverage when DEBUG trap fires on non-executable lines
  local count=0
  local line_num
  for line_num in $hit_lines; do
    local line_content
    line_content=$(sed -n "${line_num}p" "$file" 2>/dev/null) || continue
    if bashunit::coverage::is_executable_line "$line_content" "$line_num"; then
      ((count++))
    fi
  done

  echo "$count"
}

function bashunit::coverage::get_line_hits() {
  local file="$1"
  local lineno="$2"

  if [ ! -f "$_BASHUNIT_COVERAGE_DATA_FILE" ]; then
    echo "0"
    return
  fi

  local count
  count=$("$GREP" -c "^${file}:${lineno}$" "$_BASHUNIT_COVERAGE_DATA_FILE" 2>/dev/null) || count=0
  echo "$count"
}

# Get all line hits for a file in one pass (performance optimization)
# Output format: one "lineno:count" per line
function bashunit::coverage::get_all_line_hits() {
  local file="$1"

  if [ ! -f "$_BASHUNIT_COVERAGE_DATA_FILE" ]; then
    return
  fi

  # Extract all lines for this file, count occurrences of each line number
  local count lineno
  grep "^${file}:" "$_BASHUNIT_COVERAGE_DATA_FILE" 2>/dev/null |
    cut -d: -f2 | sort | uniq -c |
    while read -r count lineno; do
      echo "${lineno}:${count}"
    done
}

# Get all test hits for a file in one pass (performance optimization)
# Output format: lineno|test_file:test_function (may have duplicates, one per hit)
function bashunit::coverage::get_all_line_tests() {
  local file="$1"

  if [ ! -f "${_BASHUNIT_COVERAGE_TEST_HITS_FILE:-}" ]; then
    return
  fi

  # Format in file: source_file:line|test_file:test_function
  # Output: lineno|test_file:test_function
  grep "^${file}:" "$_BASHUNIT_COVERAGE_TEST_HITS_FILE" 2>/dev/null |
    sed "s|^${file}:||" | sort -u
}

# Extract function definitions from a bash file
# Output format: function_name:start_line:end_line (one per function)
function bashunit::coverage::extract_functions() {
  local file="$1"

  local lineno=0
  local in_function=0
  local brace_count=0
  local current_fn=""
  local fn_start=0
  local line

  while IFS= read -r line || [ -n "$line" ]; do
    ((lineno++))

    # Check for function definition patterns
    # Pattern 1: function name() { or function name {
    # Pattern 2: name() { or name () {
    if [ "$in_function" -eq 0 ]; then
      local fn_name=""

      # Match: function name() or function name {
      local _re='^[[:space:]]*(function[[:space:]]+)?([a-zA-Z_][a-zA-Z0-9_:]*)[[:space:]]*\(\)[[:space:]]*\{?[[:space:]]*(#.*)?$'
      fn_name=$(echo "$line" | sed -nE "s/$_re/\2/p")
      if [ -z "$fn_name" ]; then
        _re='^[[:space:]]*(function[[:space:]]+)([a-zA-Z_][a-zA-Z0-9_:]*)[[:space:]]*\{[[:space:]]*(#.*)?$'
        fn_name=$(echo "$line" | sed -nE "s/$_re/\2/p")
      fi

      if [ -n "$fn_name" ]; then
        in_function=1
        current_fn="$fn_name"
        fn_start=$lineno
        brace_count=0

        # Count opening braces on this line
        local open_braces="${line//[^\{]/}"
        local close_braces="${line//[^\}]/}"
        brace_count=$((brace_count + ${#open_braces} - ${#close_braces}))

        # Single-line function
        if [ "$brace_count" -eq 0 ] && [ "$(echo "$line" | "$GREP" -c '\{' || true)" -gt 0 ] && [ "$(echo "$line" | "$GREP" -c '\}' || true)" -gt 0 ]; then
          echo "${current_fn}:${fn_start}:${lineno}"
          in_function=0
          current_fn=""
        fi
        continue
      fi
    fi

    # Track braces inside function
    if [ "$in_function" -eq 1 ]; then
      local open_braces="${line//[^\{]/}"
      local close_braces="${line//[^\}]/}"
      brace_count=$((brace_count + ${#open_braces} - ${#close_braces}))

      # Function ended
      if [ "$brace_count" -le 0 ]; then
        echo "${current_fn}|${fn_start}|${lineno}"
        in_function=0
        current_fn=""
        brace_count=0
      fi
    fi
  done <"$file"

  # Handle unclosed function (shouldn't happen in valid code)
  if [ "$in_function" -eq 1 ] && [ -n "$current_fn" ]; then
    echo "${current_fn}|${fn_start}|${lineno}"
  fi
}

# Calculate coverage for a specific function in a file
# Returns: hit_lines:executable_lines:percentage
function bashunit::coverage::get_function_coverage() {
  local file="$1"
  local fn_start="$2"
  local fn_end="$3"
  shift 3

  # Accept hits_by_line array as nameref (Bash 4.3+) or fall back to counting
  local -n _hits_ref=$1 2>/dev/null || true

  local executable=0
  local hit=0
  local lineno=0

  for ((lineno = fn_start; lineno <= fn_end; lineno++)); do
    local line_content
    line_content=$(sed -n "${lineno}p" "$file" 2>/dev/null) || continue

    if bashunit::coverage::is_executable_line "$line_content" "$lineno"; then
      ((executable++))
      local line_hits=${_hits_ref[$lineno]:-0}
      if [ "$line_hits" -gt 0 ]; then
        ((hit++))
      fi
    fi
  done

  local pct=0
  if [ "$executable" -gt 0 ]; then
    pct=$((hit * 100 / executable))
  fi

  echo "${hit}:${executable}:${pct}"
}

function bashunit::coverage::get_percentage() {
  local total_executable=0
  local total_hit=0

  while IFS= read -r file; do
    { [ -z "$file" ] || [ ! -f "$file" ]; } && continue

    local executable hit
    executable=$(bashunit::coverage::get_executable_lines "$file")
    hit=$(bashunit::coverage::get_hit_lines "$file")

    total_executable=$((total_executable + executable))
    total_hit=$((total_hit + hit))
  done < <(bashunit::coverage::get_tracked_files)

  bashunit::coverage::calculate_percentage "$total_hit" "$total_executable"
}

function bashunit::coverage::report_text() {
  if ! bashunit::env::is_coverage_enabled; then
    return 0
  fi

  local total_executable=0
  local total_hit=0
  local has_files=false

  echo ""
  echo "Coverage Report"
  echo "---------------"

  local file
  while IFS= read -r file; do
    { [ -z "$file" ] || [ ! -f "$file" ]; } && continue
    has_files=true

    local executable hit pct class
    executable=$(bashunit::coverage::get_executable_lines "$file")
    hit=$(bashunit::coverage::get_hit_lines "$file")
    pct=$(bashunit::coverage::calculate_percentage "$hit" "$executable")
    class=$(bashunit::coverage::get_coverage_class "$pct")

    total_executable=$((total_executable + executable))
    total_hit=$((total_hit + hit))

    # Determine color based on class
    local color="" reset=""
    if [ "${BASHUNIT_NO_COLOR:-false}" != "true" ]; then
      reset=$'\033[0m'
      case "$class" in
      high) color=$'\033[32m' ;;   # Green
      medium) color=$'\033[33m' ;; # Yellow
      low) color=$'\033[31m' ;;    # Red
      esac
    fi

    # Display relative path
    local display_file="${file#"$(pwd)"/}"
    printf "%s%-40s %3d/%3d lines (%3d%%)%s\n" \
      "$color" "$display_file" "$hit" "$executable" "$pct" "$reset"
  done < <(bashunit::coverage::get_tracked_files)

  if [ "$has_files" != "true" ]; then
    echo "---------------"
    echo "Total: 0/0 (0%)"
    return 0
  fi

  echo "---------------"

  # Total
  local total_pct total_class
  total_pct=$(bashunit::coverage::calculate_percentage "$total_hit" "$total_executable")
  total_class=$(bashunit::coverage::get_coverage_class "$total_pct")

  local color="" reset=""
  if [ "${BASHUNIT_NO_COLOR:-false}" != "true" ]; then
    reset=$'\033[0m'
    case "$total_class" in
    high) color=$'\033[32m' ;;
    medium) color=$'\033[33m' ;;
    low) color=$'\033[31m' ;;
    esac
  fi

  printf "%sTotal: %d/%d (%d%%)%s\n" \
    "$color" "$total_hit" "$total_executable" "$total_pct" "$reset"

  # Show report location if generated
  if [ -n "$BASHUNIT_COVERAGE_REPORT" ]; then
    echo ""
    echo "Coverage report written to: $BASHUNIT_COVERAGE_REPORT"
  fi
}

function bashunit::coverage::report_lcov() {
  local output_file="${1:-$BASHUNIT_COVERAGE_REPORT}"

  if [ -z "$output_file" ]; then
    return 0
  fi

  # Create output directory if needed
  mkdir -p "$(dirname "$output_file")"

  # Generate LCOV format
  {
    echo "TN:"

    while IFS= read -r file; do
      { [ -z "$file" ] || [ ! -f "$file" ]; } && continue

      echo "SF:$file"

      local lineno=0
      local line
      # shellcheck disable=SC2094
      while IFS= read -r line || [ -n "$line" ]; do
        ((lineno++))
        bashunit::coverage::is_executable_line "$line" "$lineno" || continue
        echo "DA:${lineno},$(bashunit::coverage::get_line_hits "$file" "$lineno")"
      done <"$file"

      local executable hit
      executable=$(bashunit::coverage::get_executable_lines "$file")
      hit=$(bashunit::coverage::get_hit_lines "$file")

      echo "LF:$executable"
      echo "LH:$hit"
      echo "end_of_record"
    done < <(bashunit::coverage::get_tracked_files)
  } >"$output_file"
}

function bashunit::coverage::check_threshold() {
  if [ -z "$BASHUNIT_COVERAGE_MIN" ]; then
    return 0
  fi

  local pct
  pct=$(bashunit::coverage::get_percentage)

  if [ "$pct" -lt "$BASHUNIT_COVERAGE_MIN" ]; then
    local color=""
    local reset=""
    if [ "${BASHUNIT_NO_COLOR:-false}" != "true" ]; then
      color=$'\033[31m'
      reset=$'\033[0m'
    fi
    printf "%sCoverage %d%% is below minimum %d%%%s\n" \
      "$color" "$pct" "$BASHUNIT_COVERAGE_MIN" "$reset"
    return 1
  fi

  return 0
}

# Escape HTML special characters
# Uses sed for cross-version bash compatibility (bash 3.2 vs 4.4+ handle & differently in replacement strings)
function bashunit::coverage::html_escape() {
  local text="$1"
  printf "%s" "$text" | sed "s/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g"
}

# Convert file path to safe filename for HTML
function bashunit::coverage::path_to_filename() {
  local file="$1"
  local display_file="${file#"$(pwd)"/}"
  # Replace / with _ and . with _
  local safe_name="${display_file//\//_}"
  echo "${safe_name//./_}"
}

function bashunit::coverage::report_html() {
  local output_dir="${1:-coverage/html}"

  if [ -z "$output_dir" ]; then
    return 0
  fi

  # Create output directory structure
  mkdir -p "$output_dir/files"

  # Collect file data for index
  local IFS=$' \t\n'
  local total_executable=0
  local total_hit=0
  local -a file_data=()
  local file_data_count=0
  local file=""

  while IFS= read -r file; do
    { [ -z "$file" ] || [ ! -f "$file" ]; } && continue

    local executable hit pct
    executable=$(bashunit::coverage::get_executable_lines "$file")
    hit=$(bashunit::coverage::get_hit_lines "$file")
    pct=$(bashunit::coverage::calculate_percentage "$hit" "$executable")

    total_executable=$((total_executable + executable))
    total_hit=$((total_hit + hit))

    local display_file="${file#"$(pwd)"/}"
    local safe_filename
    safe_filename=$(bashunit::coverage::path_to_filename "$file")

    file_data[file_data_count]="$display_file|$hit|$executable|$pct|$safe_filename"
    file_data_count=$((file_data_count + 1))

    # Generate individual file HTML
    bashunit::coverage::generate_file_html "$file" "$output_dir/files/${safe_filename}.html"
  done < <(bashunit::coverage::get_tracked_files)

  # Calculate total percentage
  local total_pct
  total_pct=$(bashunit::coverage::calculate_percentage "$total_hit" "$total_executable")

  # Get test results
  local tests_passed tests_failed tests_total
  tests_passed=$(bashunit::state::get_tests_passed)
  tests_failed=$(bashunit::state::get_tests_failed)
  tests_total=$((tests_passed + tests_failed))

  # Generate index.html
  bashunit::coverage::generate_index_html \
    "$output_dir/index.html" "$total_hit" "$total_executable" "$total_pct" \
    "$tests_total" "$tests_passed" "$tests_failed" ${file_data[@]+"${file_data[@]}"}

  echo "Coverage HTML report written to: $output_dir/index.html"
}

function bashunit::coverage::generate_index_html() {
  # Set normal IFS for array operations throughout the function (Bash 3.0/4.3 compatible)
  local IFS=$' \t\n'
  local output_file="$1"
  local total_hit="$2"
  local total_executable="$3"
  local total_pct="$4"
  local tests_total="$5"
  local tests_passed="$6"
  local tests_failed="$7"
  shift 7
  # Handle array passed as arguments - Bash 3.0 compatible
  local -a file_data=()
  local file_count=0
  if [ $# -gt 0 ]; then
    file_data=("$@")
    file_count=$#
  fi

  # Calculate uncovered lines and file count
  local total_uncovered=$((total_executable - total_hit))

  # Calculate gauge stroke offset (440 is full circle circumference)
  local gauge_offset=$((440 - (440 * total_pct / 100)))

  # Determine coverage level and colors for gauge
  local total_class gauge_color_start gauge_color_end gauge_text_gradient
  total_class=$(bashunit::coverage::get_coverage_class "$total_pct")
  case "$total_class" in
  high)
    gauge_color_start="#10b981"
    gauge_color_end="#34d399"
    gauge_text_gradient="linear-gradient(135deg, #10b981 0%, #34d399 100%)"
    ;;
  medium)
    gauge_color_start="#f59e0b"
    gauge_color_end="#fbbf24"
    gauge_text_gradient="linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)"
    ;;
  low)
    gauge_color_start="#ef4444"
    gauge_color_end="#f87171"
    gauge_text_gradient="linear-gradient(135deg, #ef4444 0%, #f87171 100%)"
    ;;
  esac

  {
    cat <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Coverage Report | bashunit</title>
  <style>
    :root {
      --primary: #6366f1; --primary-dark: #4f46e5; --primary-light: #818cf8;
      --success: #10b981; --success-light: #34d399;
      --warning: #f59e0b; --warning-light: #fbbf24;
      --danger: #ef4444; --danger-light: #f87171;
      --bg-light: #ffffff; --bg-card: #f8fafc; --bg-hover: #f1f5f9;
      --text-primary: #0f172a; --text-secondary: #475569; --text-muted: #94a3b8;
      --border: #e2e8f0;
    }
    * { margin: 0; padding: 0; box-sizing: border-box; }
    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; }
    .header { background: var(--bg-card); padding: 0; position: relative; overflow: hidden; border-bottom: 1px solid var(--border); }
    .header-content { position: relative; z-index: 1; max-width: 1400px; margin: 0 auto; padding: 40px 30px; }
    .header-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
    .logo { display: flex; align-items: center; gap: 12px; }
    .logo img { width: 40px; height: 40px; }
    .logo-text { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.5px; color: var(--text-primary); }
    .logo-text span { opacity: 0.6; font-weight: 400; }
    .header-badge { background: var(--bg-hover); padding: 8px 16px; border-radius: 20px; font-size: 0.85rem; font-weight: 500; color: var(--text-secondary); }
    .header-title { font-size: 2.5rem; font-weight: 800; margin-bottom: 8px; letter-spacing: -1px; color: var(--text-primary); }
    .header-subtitle { font-size: 1.1rem; opacity: 0.7; color: var(--text-secondary); }
    .main { max-width: 1400px; margin: 0 auto; padding: 40px 30px; }
    .gauge-section { background: var(--bg-card); border-radius: 20px; padding: 40px; margin-bottom: 30px; border: 1px solid var(--border); display: flex; align-items: center; gap: 60px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
    .gauge-container { position: relative; width: 200px; height: 200px; flex-shrink: 0; }
    .gauge-bg { fill: none; stroke: #e5e7eb; stroke-width: 20; }
    .gauge-fill { fill: none; stroke: url(#gaugeGradient); stroke-width: 20; stroke-linecap: round; transform: rotate(-90deg); transform-origin: center; animation: gaugeAnimation 1.5s ease-out forwards; }
    @keyframes gaugeAnimation { from { stroke-dashoffset: 440; } }
    @keyframes fadeInUp { from { opacity: 0; } to { opacity: 1 } }
    .gauge-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; width: 100%; }
EOF
    echo "    .gauge-percent { font-size: 3.5rem; font-weight: 800; background: ${gauge_text_gradient}; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; line-height: 1; margin: 0; display: block; }"
    cat <<'EOF'
    .gauge-label { color: var(--text-secondary); font-size: 0.9rem; text-transform: uppercase; letter-spacing: 2px; margin: 0; display: block; }
    .gauge-info { flex: 1; }
    .gauge-title { font-size: 1.8rem; font-weight: 700; margin-bottom: 12px; }
    .gauge-description { color: var(--text-secondary); font-size: 1.05rem; margin-bottom: 24px; line-height: 1.7; }
    .breakdown-item { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
    .breakdown-dot { width: 12px; height: 12px; border-radius: 50%; }
    .breakdown-dot.total { background: #94a3b8; }
    .breakdown-dot.covered { background: var(--success); }
    .breakdown-dot.uncovered { background: var(--danger); }
    .breakdown-dot.files { background: var(--warning); }
    .breakdown-dot.tests { background: #a78bfa; }
    .breakdown-dot.tests-passed { background: var(--success); }
    .breakdown-dot.tests-failed { background: var(--danger); }
    .breakdown-label { color: var(--text-secondary); font-size: 0.9rem; }
    .breakdown-value { font-weight: 600; color: var(--text-primary); }
    .compact-metrics { display: flex; flex-direction: column; gap: 10px; }
    .metrics-group { background: var(--bg-hover); padding: 12px 16px; border-radius: 8px; border-left: 3px solid var(--primary); }
    .metrics-group.coverage-group { border-left-color: var(--success); }
    .metrics-group.test-group { border-left-color: #a78bfa; }
    .metrics-group-title { font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }
    .metrics-inline { display: flex; gap: 16px; flex-wrap: wrap; align-items: center; font-size: 0.9rem; }
    .metrics-inline .breakdown-item { margin: 0; }
    .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 16px; }
    .section-title { font-size: 1.5rem; font-weight: 700; display: flex; align-items: center; gap: 12px; }
    .legend { display: flex; gap: 20px; background: #f1f5f9; padding: 12px 20px; border-radius: 10px; }
    .legend-item { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; color: var(--text-secondary); pointer-events: none; }
    .legend-color { width: 16px; height: 16px; border-radius: 4px; }
    .legend-color.high { background: var(--success); }
    .legend-color.medium { background: var(--warning); }
    .legend-color.low { background: var(--danger); }
    .files-table { background: var(--bg-card); border-radius: 16px; overflow: hidden; border: 1px solid var(--border); box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
    .files-table table { width: 100%; border-collapse: collapse; }
    .files-table th { background: #f8fafc; padding: 16px 24px; text-align: left; font-weight: 600; color: var(--text-secondary); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 1px; border-bottom: 1px solid var(--border); }
    .files-table td { padding: 20px 24px; border-bottom: 1px solid var(--border); vertical-align: middle; }
    .files-table tr:last-child td { border-bottom: none; }
    .files-table tbody tr { transition: all 0.2s ease; animation: fadeInUp 0.5s ease-out forwards; opacity: 0; cursor: pointer; }
    .files-table tbody tr:hover { background: var(--bg-hover); }
    .file-info { display: flex; flex-direction: column; gap: 4px; }
    .file-name { font-weight: 600; color: var(--text-primary); text-decoration: none; font-size: 1rem; transition: color 0.2s; }
    .file-name:hover { color: var(--primary-light); }
    .file-path { color: var(--text-muted); font-size: 0.85rem; font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; }
    .lines-info { text-align: center; }
    .lines-covered { font-weight: 700; font-size: 1.1rem; color: var(--text-primary); }
    .lines-total { color: var(--text-muted); font-size: 0.85rem; }
    .coverage-cell { width: 200px; }
    .coverage-bar-container { display: flex; align-items: center; gap: 16px; }
    .coverage-bar { flex: 1; height: 10px; background: var(--bg-hover); border-radius: 5px; overflow: hidden; }
    .coverage-bar-fill { height: 100%; border-radius: 5px; transition: width 1s ease-out; }
    .coverage-bar-fill.high { background: linear-gradient(90deg, var(--success) 0%, var(--success-light) 100%); }
    .coverage-bar-fill.medium { background: linear-gradient(90deg, var(--warning) 0%, var(--warning-light) 100%); }
    .coverage-bar-fill.low { background: linear-gradient(90deg, var(--danger) 0%, var(--danger-light) 100%); }
    .coverage-percent { font-weight: 700; font-size: 1rem; min-width: 50px; text-align: right; }
    .coverage-percent.high { color: var(--success); }
    .coverage-percent.medium { color: var(--warning); }
    .coverage-percent.low { color: var(--danger); }
    .view-btn { display: inline-flex; align-items: center; gap: 8px; padding: 10px 20px; background: var(--bg-hover); border: 1px solid var(--border); border-radius: 8px; color: var(--text-primary); text-decoration: none; font-size: 0.9rem; font-weight: 500; transition: all 0.2s; }
    .view-btn:hover { background: var(--primary); border-color: var(--primary); }
    .footer { max-width: 1400px; margin: 0 auto; padding: 40px 30px; text-align: center; border-top: 1px solid var(--border); }
    .footer-content { display: flex; justify-content: center; align-items: center; gap: 10px; flex-wrap: wrap; }
    .footer-text { color: var(--text-muted); font-size: 0.9rem; }
    .footer-link { color: var(--primary-light); text-decoration: none; font-weight: 500; transition: color 0.2s; }
    .footer-link:hover { color: var(--primary); }
    .footer-divider { width: 4px; height: 4px; background: var(--text-muted); border-radius: 50%; }
    @media (max-width: 768px) {
      .header-content { padding: 30px 20px; } .header-title { font-size: 1.8rem; }
      .main { padding: 30px 20px; }
      .gauge-section { flex-direction: column; padding: 30px; gap: 30px; }
      .gauge-container { width: 160px; height: 160px; }
      .gauge-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; }
      .gauge-percent { font-size: 2.5rem; line-height: 1; margin: 0; }
      .gauge-label { font-size: 0.75rem; letter-spacing: 1.5px; margin: 0; }
      .metrics-inline { flex-direction: column; gap: 10px; align-items: flex-start; }
      .files-table th, .files-table td { padding: 15px; }
      .coverage-cell { width: auto; }
      .coverage-bar-container { flex-direction: column; align-items: flex-start; gap: 8px; }
      .coverage-bar { width: 100%; }
    }
  </style>
</head>
<body>
  <header class="header">
    <div class="header-content">
      <div class="header-top">
        <div class="logo">
          <img src="https://bashunit.typeddevs.com/logo.svg" alt="bashunit">
          <div class="logo-text">bashunit <span>coverage</span></div>
        </div>
EOF
    echo "        <div class=\"header-badge\">v${BASHUNIT_VERSION:-0.0.0}</div>"
    cat <<'EOF'
      </div>
      <h1 class="header-title">Code Coverage Report</h1>
      <p class="header-subtitle">Comprehensive line-by-line coverage analysis for your bash scripts</p>
    </div>
  </header>
  <main class="main">
    <section class="gauge-section">
      <div class="gauge-container">
        <svg viewBox="0 0 160 160" width="200" height="200">
          <defs>
            <linearGradient id="gaugeGradient" x1="0%" y1="0%" x2="100%" y2="0%">
EOF
    echo "              <stop offset=\"0%\" style=\"stop-color:${gauge_color_start}\"/>"
    echo "              <stop offset=\"100%\" style=\"stop-color:${gauge_color_end}\"/>"
    cat <<'EOF'
            </linearGradient>
          </defs>
          <circle class="gauge-bg" cx="80" cy="80" r="70"/>
EOF
    echo "          <circle class=\"gauge-fill\" cx=\"80\" cy=\"80\" r=\"70\" stroke-dasharray=\"440\" stroke-dashoffset=\"${gauge_offset}\"/>"
    cat <<'EOF'
        </svg>
        <div class="gauge-text">
EOF
    echo "          <div class=\"gauge-percent\">${total_pct}%</div>"
    cat <<'EOF'
          <div class="gauge-label">Coverage</div>
        </div>
      </div>
      <div class="gauge-info">
        <h2 class="gauge-title">Overall Code Coverage</h2>
EOF
    echo "        <p class=\"gauge-description\"><strong>${total_hit} of ${total_executable}</strong> executable lines covered across <strong>${file_count} files</strong>.</p>"
    cat <<'EOF'

        <div class="compact-metrics">
          <div class="metrics-group coverage-group">
            <div class="metrics-group-title">Coverage Metrics</div>
            <div class="metrics-inline">
              <div class="breakdown-item">
                <span class="breakdown-dot total"></span>
                <span class="breakdown-label">Total:</span>
EOF
    echo "                <span class=\"breakdown-value\">${total_executable} lines</span>"
    cat <<'EOF'
              </div>
              <div class="breakdown-item">
                <span class="breakdown-dot covered"></span>
                <span class="breakdown-label">Covered:</span>
EOF
    echo "                <span class=\"breakdown-value\">${total_hit} lines</span>"
    cat <<'EOF'
              </div>
              <div class="breakdown-item">
                <span class="breakdown-dot uncovered"></span>
                <span class="breakdown-label">Uncovered:</span>
EOF
    echo "                <span class=\"breakdown-value\">${total_uncovered} lines</span>"
    cat <<'EOF'
              </div>
            </div>
          </div>
          <div class="metrics-group test-group">
            <div class="metrics-group-title">Test Results</div>
            <div class="metrics-inline">
              <div class="breakdown-item">
                <span class="breakdown-dot files"></span>
                <span class="breakdown-label">Files:</span>
EOF
    echo "                <span class=\"breakdown-value\">${file_count}</span>"
    cat <<'EOF'
              </div>
              <div class="breakdown-item">
                <span class="breakdown-dot tests"></span>
                <span class="breakdown-label">Tests:</span>
EOF
    echo "                <span class=\"breakdown-value\">${tests_total} total</span>"
    cat <<'EOF'
              </div>
              <div class="breakdown-item">
                <span class="breakdown-dot tests-passed"></span>
                <span class="breakdown-label">Passed:</span>
EOF
    echo "                <span class=\"breakdown-value\">${tests_passed}</span>"
    cat <<'EOF'
              </div>
              <div class="breakdown-item">
                <span class="breakdown-dot tests-failed"></span>
                <span class="breakdown-label">Failed:</span>
EOF
    echo "                <span class=\"breakdown-value\">${tests_failed}</span>"
    cat <<'EOF'
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
    <section>
      <div class="section-header">
        <h2 class="section-title">File Coverage Details</h2>
        <div class="legend">
          <div class="legend-item">
            <span class="legend-color high"></span>
EOF
    echo "            <span>≥${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80}% High</span>"
    cat <<'EOF'
          </div>
          <div class="legend-item">
            <span class="legend-color medium"></span>
EOF
    echo "            <span>${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50}-${BASHUNIT_COVERAGE_THRESHOLD_HIGH:-80}% Medium</span>"
    cat <<'EOF'
          </div>
          <div class="legend-item">
            <span class="legend-color low"></span>
EOF
    echo "            <span>&lt;${BASHUNIT_COVERAGE_THRESHOLD_LOW:-50}% Low</span>"
    cat <<'EOF'
          </div>
        </div>
      </div>
      <div class="files-table">
        <table>
          <thead>
            <tr>
              <th>File</th>
              <th style="text-align: center;">Lines</th>
              <th>Coverage</th>
            </tr>
          </thead>
          <tbody>
EOF

    local data display_file hit executable pct safe_filename
    for data in ${file_data[@]+"${file_data[@]}"}; do
      IFS='|' read -r display_file hit executable pct safe_filename <<<"$data"

      local class
      class=$(bashunit::coverage::get_coverage_class "$pct")

      echo "            <tr onclick=\"window.location='files/${safe_filename}.html'\">"
      echo "              <td>"
      echo "                <div class=\"file-info\">"
      echo "                  <a href=\"files/${safe_filename}.html\" class=\"file-name\">$(basename "$display_file")</a>"
      echo "                  <div class=\"file-path\">./${display_file}</div>"
      echo "                </div>"
      echo "              </td>"
      echo "              <td>"
      echo "                <div class=\"lines-info\">"
      echo "                  <div class=\"lines-covered\">${hit}</div>"
      echo "                  <div class=\"lines-total\">of ${executable} lines</div>"
      echo "                </div>"
      echo "              </td>"
      echo "              <td class=\"coverage-cell\">"
      echo "                <div class=\"coverage-bar-container\">"
      echo "                  <div class=\"coverage-bar\">"
      echo "                    <div class=\"coverage-bar-fill $class\" style=\"width: ${pct}%;\"></div>"
      echo "                  </div>"
      echo "                  <span class=\"coverage-percent $class\">${pct}%</span>"
      echo "                </div>"
      echo "              </td>"
      echo "            </tr>"
    done

    cat <<'EOF'
          </tbody>
        </table>
      </div>
    </section>
  </main>
  <footer class="footer">
    <div class="footer-content">
      <span class="footer-text">Generated by</span>
      <a href="https://bashunit.typeddevs.com" class="footer-link" target="_blank">bashunit</a>
      <span class="footer-divider"></span>
      <span class="footer-text">Documentation at</span>
      <a href="https://bashunit.typeddevs.com/coverage" class="footer-link" target="_blank">bashunit.typeddevs.com/coverage</a>
    </div>
  </footer>
</body>
</html>
EOF
  } >"$output_file"
}

function bashunit::coverage::generate_file_html() {
  local file="$1"
  local output_file="$2"

  local display_file="${file#"$(pwd)"/}"
  local executable hit pct class
  executable=$(bashunit::coverage::get_executable_lines "$file")
  hit=$(bashunit::coverage::get_hit_lines "$file")
  pct=$(bashunit::coverage::calculate_percentage "$hit" "$executable")
  class=$(bashunit::coverage::get_coverage_class "$pct")
  local uncovered=$((executable - hit))

  # Pre-load all line hits into indexed array (performance optimization)
  local -a hits_by_line=()
  local _ln _cnt
  while IFS=: read -r _ln _cnt; do
    hits_by_line[_ln]=$_cnt
  done < <(bashunit::coverage::get_all_line_hits "$file")

  # Pre-load test hits data into indexed array (for tooltips)
  # Index: line number, Value: newline-separated list of "test_file:test_function"
  # Using indexed array for Bash 3.0 compatibility (no associative arrays)
  local -a tests_by_line=()
  local _line_and_test
  while IFS= read -r _line_and_test; do
    [ -z "$_line_and_test" ] && continue
    local _tln="${_line_and_test%%|*}"
    local _tinfo="${_line_and_test#*|}"
    if [ -n "${tests_by_line[_tln]:-}" ]; then
      # Append only if not already present (avoid duplicates)
      # Use newline boundaries to prevent false positives (e.g., test_foo matching test_foo_bar)
      case $'\n'"${tests_by_line[_tln]}"$'\n' in
      *$'\n'"$_tinfo"$'\n'*)
        # already present, skip
        ;;
      *)
        tests_by_line[_tln]="${tests_by_line[_tln]}"$'\n'"${_tinfo}"
        ;;
      esac
    else
      tests_by_line[_tln]="$_tinfo"
    fi
  done < <(bashunit::coverage::get_all_line_tests "$file")

  # Count total lines and functions
  local total_lines
  total_lines=$(wc -l <"$file" | tr -d ' ')
  local non_executable=$((total_lines - executable))

  {
    cat <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
EOF
    echo "  <title>$(basename "$display_file") | Coverage Report</title>"
    cat <<'EOF'
  <style>
    :root {
      --primary: #6366f1; --primary-dark: #4f46e5; --primary-light: #818cf8;
      --success: #10b981; --success-bg: rgba(16, 185, 129, 0.1); --success-border: rgba(16, 185, 129, 0.2);
      --warning: #f59e0b;
      --danger: #ef4444; --danger-bg: rgba(239, 68, 68, 0.1); --danger-border: rgba(239, 68, 68, 0.2);
      --bg-light: #ffffff; --bg-card: #f8fafc; --bg-hover: #e1e5ea; --bg-code: #f6f8fa;
      --text-primary: #0f172a; --text-secondary: #475569; --text-muted: #94a3b8;
      --border: #e2e8f0; --line-number-bg: #f8fafc;
    }
    * { margin: 0; padding: 0; box-sizing: border-box; }
    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; }
    .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); }
    .header-content { max-width: 1600px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px; }
    .nav-section { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
    .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); }
    .back-btn:hover { background: #334155; border-color: #334155; box-shadow: 0 4px 12px rgba(51, 65, 85, 0.3); }
    .file-title { display: flex; align-items: center; gap: 12px; }
    .file-name { font-size: 1.3rem; font-weight: 700; font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; }
    .stats-section { display: flex; align-items: center; gap: 30px; flex-wrap: wrap; }
    .stat-item { display: flex; align-items: center; gap: 10px; }
    .stat-badge { padding: 8px 16px; border-radius: 20px; font-weight: 600; font-size: 0.9rem; }
    .stat-badge.coverage.high { background: linear-gradient(135deg, var(--success) 0%, #34d399 100%); color: #000; }
    .stat-badge.coverage.medium { background: linear-gradient(135deg, var(--warning) 0%, #fbbf24 100%); color: #000; }
    .stat-badge.coverage.low { background: linear-gradient(135deg, var(--danger) 0%, #f87171 100%); color: #fff; }
    .stat-badge.lines { background: var(--bg-hover); color: var(--text-primary); }
    .stat-label { color: var(--text-secondary); font-size: 0.85rem; }
    .summary-bar { background: var(--bg-card); border-bottom: 1px solid var(--border); padding: 20px 30px; }
    .summary-content { max-width: 1600px; margin: 0 auto; display: flex; align-items: center; gap: 40px; flex-wrap: wrap; }
    .progress-section { flex: 1; min-width: 300px; }
    .progress-header { display: flex; justify-content: space-between; margin-bottom: 8px; }
    .progress-label { color: var(--text-secondary); font-size: 0.9rem; }
    .progress-percent { font-weight: 700; }
    .progress-percent.high { color: var(--success); }
    .progress-percent.medium { color: var(--warning); }
    .progress-percent.low { color: var(--danger); }
    .progress-bar { height: 12px; background: var(--bg-hover); border-radius: 6px; overflow: hidden; }
    .progress-fill { height: 100%; border-radius: 6px; transition: width 1s ease-out; }
    .progress-fill.high { background: linear-gradient(90deg, #059669 0%, #10b981 100%); }
    .progress-fill.medium { background: linear-gradient(90deg, #d97706 0%, #f59e0b 100%); }
    .progress-fill.low { background: linear-gradient(90deg, #dc2626 0%, #ef4444 100%); }
    .legend { display: flex; gap: 24px; flex-wrap: wrap; }
    .legend-item { display: flex; align-items: center; gap: 8px; font-size: 0.9rem; color: var(--text-secondary); pointer-events: none; }
    .legend-color { width: 16px; height: 16px; border-radius: 4px; }
    .legend-color.covered { background: var(--success); }
    .legend-color.uncovered { background: var(--danger); }
    .legend-color.neutral { background: var(--text-muted); }
    .code-container { max-width: 1600px; margin: 30px auto; padding: 0 30px; }
    .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); }
    .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; }
    .code-path { font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; font-size: 0.9rem; color: var(--text-secondary); }
    .code-stats { display: flex; gap: 16px; font-size: 0.85rem; }
    .code-stats span { padding: 4px 12px; background: #e5e7eb; border-radius: 4px; color: var(--text-secondary); }
    .code-body { overflow-x: auto; }
    .code-table { width: 100%; border-collapse: collapse; font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; font-size: 13px; line-height: 1.6; }
    .code-table tr { transition: background 0.15s; }
    .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; }
    .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; }
    .hits-badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 0.8em; font-weight: 600; position: relative; }
    .hits-badge.has-tooltip { cursor: help; }
    .covered .hits-badge { background: var(--success-bg); color: var(--success); }
    .uncovered .hits-badge { background: var(--danger-bg); color: var(--danger); }
    .hits-tooltip { display: none; position: absolute; left: 100%; top: 50%; transform: translateY(-50%); margin-left: 12px; padding: 10px 14px; background: #1e293b; color: #f1f5f9; border-radius: 8px; font-size: 11px; font-weight: 400; white-space: normal; z-index: 100; box-shadow: 0 4px 12px rgba(0,0,0,0.15); min-width: 200px; max-width: 500px; width: max-content; }
    .hits-tooltip::after { content: ''; position: absolute; right: 100%; top: 50%; transform: translateY(-50%); border: 6px solid transparent; border-right-color: #1e293b; }
    .hits-badge:hover .hits-tooltip { display: block; }
    .hits-tooltip-title { font-weight: 600; margin-bottom: 6px; color: #94a3b8; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; }
    .hits-tooltip-list { margin: 0; padding: 0; list-style: none; }
    .hits-tooltip-list li { padding: 3px 0; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; }
    .hits-tooltip-list li:last-child { border-bottom: none; }
    .hits-tooltip-file { color: #60a5fa; }
    .hits-tooltip-fn { color: #a5b4fc; }
    .code { padding: 2px 20px; white-space: pre; vertical-align: top; }
    .covered { background: #d1fae5; }
    .covered .line-num, .covered .hits { background: #a7f3d0; border-color: var(--success-border); }
    .covered:hover { background: #ecfdf5; }
    .covered:hover .line-num, .covered:hover .hits { background: #d1fae5; }
    .uncovered { background: #fee2e2; }
    .uncovered .line-num, .uncovered .hits { background: #fecaca; border-color: var(--danger-border); }
    .uncovered:hover { background: #fef2f2; }
    .uncovered:hover .line-num, .uncovered:hover .hits { background: #fee2e2; }
    .function-summary { max-width: 1600px; margin: 30px auto; padding: 0 30px; }
    .function-table { background: var(--bg-card); border-radius: 16px; overflow: hidden; border: 1px solid var(--border); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); width: 100%; border-collapse: collapse; }
    .function-table th { background: #f1f5f9; padding: 14px 20px; text-align: left; font-weight: 600; color: var(--text-secondary); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 2px solid var(--border); }
    .function-table th:first-child { width: 40%; }
    .function-table th:nth-child(2), .function-table th:nth-child(3) { text-align: center; }
    .function-table td { padding: 12px 20px; border-bottom: 1px solid var(--border); vertical-align: middle; }
    .function-table tr:last-child td { border-bottom: none; }
    .function-table tbody tr { transition: background 0.15s; }
    .function-table tbody tr:hover { background: var(--bg-hover); }
    .function-table tbody tr.fn-covered { background: #f0fdf4; }
    .function-table tbody tr.fn-covered:hover { background: #dcfce7; }
    .function-table tbody tr.fn-partial { background: #fffbeb; }
    .function-table tbody tr.fn-partial:hover { background: #fef3c7; }
    .function-table tbody tr.fn-uncovered { background: #fef2f2; }
    .function-table tbody tr.fn-uncovered:hover { background: #fee2e2; }
    .fn-name { font-weight: 600; color: var(--primary); cursor: pointer; text-decoration: none; font-family: 'SF Mono', 'Consolas', 'Liberation Mono', Menlo, monospace; font-size: 0.95rem; }
    .fn-name:hover { color: var(--primary-dark); text-decoration: underline; }
    .fn-lines { text-align: center; color: var(--text-secondary); font-size: 0.9rem; }
    .fn-coverage-cell { text-align: center; }
    .fn-coverage-bar { display: flex; align-items: center; gap: 12px; justify-content: center; }
    .fn-progress { width: 100px; height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden; }
    .fn-progress-fill { height: 100%; border-radius: 4px; }
    .fn-progress-fill.high { background: var(--success); }
    .fn-progress-fill.medium { background: var(--warning); }
    .fn-progress-fill.low { background: var(--danger); }
    .fn-pct { font-weight: 600; font-size: 0.9rem; min-width: 50px; text-align: right; }
    .fn-pct.high { color: var(--success); }
    .fn-pct.medium { color: var(--warning); }
    .fn-pct.low { color: var(--danger); }
    .line-anchor { scroll-margin-top: 200px; }
    .line-anchor:target { animation: highlightFade 4s ease-out forwards; }
    .line-anchor:target .line-num, .line-anchor:target .hits { animation: highlightFade 4s ease-out forwards; }
    @keyframes highlightFade {
      0% { background: #93c5fd; }
      70% { background: #dbeafe; }
      100% { background: transparent; }
    }
    .footer { max-width: 1600px; margin: 0 auto; padding: 40px 30px; text-align: center; }
    .footer-text { color: var(--text-muted); font-size: 0.9rem; }
    .footer-link { color: var(--primary-light); text-decoration: none; font-weight: 500; }
    .footer-link:hover { color: var(--primary); }
    @media (max-width: 768px) {
      .header { padding: 15px 20px; } .header-content { gap: 15px; }
      .stats-section { gap: 15px; } .summary-bar { padding: 15px 20px; }
      .summary-content { gap: 20px; } .code-container { padding: 0 15px; margin: 20px auto; }
      .code-header { padding: 12px 16px; } .line-num, .hits { padding: 2px 8px; }
      .code { padding: 2px 12px; }
    }
  </style>
</head>
<body>
  <header class="header">
    <div class="header-content">
      <div class="nav-section">
        <a href="../index.html" class="back-btn">← Back to Overview</a>
        <div class="file-title">
EOF
    echo "          <span class=\"file-name\">$(basename "$display_file")</span>"
    cat <<'EOF'
        </div>
      </div>
      <div class="stats-section">
        <div class="stat-item">
EOF
    echo "          <span class=\"stat-badge coverage $class\">${pct}%</span>"
    cat <<'EOF'
          <span class="stat-label">Coverage</span>
        </div>
        <div class="stat-item">
EOF
    echo "          <span class=\"stat-badge lines\">${hit}/${executable}</span>"
    cat <<'EOF'
          <span class="stat-label">Lines</span>
        </div>
      </div>
    </div>
  </header>
  <div class="summary-bar">
    <div class="summary-content">
      <div class="progress-section">
        <div class="progress-header">
          <span class="progress-label">Line Coverage Progress</span>
EOF
    echo "          <span class=\"progress-percent $class\">${pct}%</span>"
    cat <<'EOF'
        </div>
        <div class="progress-bar">
EOF
    echo "          <div class=\"progress-fill $class\" style=\"width: ${pct}%;\"></div>"
    cat <<'EOF'
        </div>
      </div>
      <div class="legend">
        <div class="legend-item">
          <span class="legend-color covered"></span>
EOF
    echo "          <span>${hit} lines covered</span>"
    cat <<'EOF'
        </div>
        <div class="legend-item">
          <span class="legend-color uncovered"></span>
EOF
    echo "          <span>${uncovered} lines uncovered</span>"
    cat <<'EOF'
        </div>
        <div class="legend-item">
          <span class="legend-color neutral"></span>
EOF
    echo "          <span>${non_executable} non-executable</span>"
    cat <<'EOF'
        </div>
      </div>
    </div>
  </div>
EOF

    # Extract functions and generate summary table
    local functions_data
    functions_data=$(bashunit::coverage::extract_functions "$file")

    if [ -n "$functions_data" ]; then
      cat <<'EOF'
  <div class="function-summary">
    <table class="function-table">
      <thead>
        <tr>
          <th>Function</th>
          <th>Lines</th>
          <th>Coverage</th>
        </tr>
      </thead>
      <tbody>
EOF
      local fn_entry
      while IFS= read -r fn_entry; do
        [ -z "$fn_entry" ] && continue
        local fn_name fn_start fn_end
        fn_name="${fn_entry%%|*}"
        local rest="${fn_entry#*|}"
        fn_start="${rest%%|*}"
        fn_end="${rest#*|}"

        # Calculate function coverage using pre-loaded hits data
        local fn_executable=0
        local fn_hit=0
        local ln
        for ((ln = fn_start; ln <= fn_end; ln++)); do
          local ln_content
          ln_content=$(sed -n "${ln}p" "$file" 2>/dev/null) || continue
          if bashunit::coverage::is_executable_line "$ln_content" "$ln"; then
            ((fn_executable++))
            local ln_hits=${hits_by_line[$ln]:-0}
            if [ "$ln_hits" -gt 0 ]; then
              ((fn_hit++))
            fi
          fi
        done

        local fn_pct fn_class row_class
        fn_pct=$(bashunit::coverage::calculate_percentage "$fn_hit" "$fn_executable")
        fn_class=$(bashunit::coverage::get_coverage_class "$fn_pct")
        case "$fn_class" in
        high) row_class="fn-covered" ;;
        medium) row_class="fn-partial" ;;
        low) row_class="fn-uncovered" ;;
        esac

        echo "        <tr class=\"$row_class\">"
        echo "          <td><a href=\"#line-${fn_start}\" class=\"fn-name\">${fn_name}</a></td>"
        echo "          <td class=\"fn-lines\">${fn_hit} / ${fn_executable}</td>"
        echo "          <td class=\"fn-coverage-cell\">"
        echo "            <div class=\"fn-coverage-bar\">"
        echo "              <div class=\"fn-progress\"><div class=\"fn-progress-fill ${fn_class}\" style=\"width: ${fn_pct}%;\"></div></div>"
        echo "              <span class=\"fn-pct ${fn_class}\">${fn_pct}%</span>"
        echo "            </div>"
        echo "          </td>"
        echo "        </tr>"
      done <<<"$functions_data"

      cat <<'EOF'
      </tbody>
    </table>
  </div>
EOF
    fi

    cat <<'EOF'
  <div class="code-container">
    <div class="code-wrapper">
      <div class="code-header">
EOF
    echo "        <span class=\"code-path\">./${display_file}</span>"
    echo "        <div class=\"code-stats\">"
    echo "          <span>${total_lines} total lines</span>"
    echo "        </div>"
    cat <<'EOF'
      </div>
      <div class="code-body">
        <table class="code-table">
EOF

    local lineno=0
    local line
    while IFS= read -r line || [ -n "$line" ]; do
      ((lineno++))

      local escaped_line
      escaped_line=$(bashunit::coverage::html_escape "$line")

      local row_class=""
      local hits_display=""

      if bashunit::coverage::is_executable_line "$line" "$lineno"; then
        # O(1) lookup from pre-loaded array
        local hits=${hits_by_line[$lineno]:-0}

        if [ "$hits" -gt 0 ]; then
          row_class="covered"

          # Check if we have test info for this line
          local test_info="${tests_by_line[$lineno]:-}"
          if [ -n "$test_info" ]; then
            # Build tooltip with test information
            local tooltip_html="<div class=\"hits-tooltip\"><div class=\"hits-tooltip-title\">Tests hitting this line</div><ul class=\"hits-tooltip-list\">"
            local test_file test_fn
            while IFS=':' read -r test_file test_fn; do
              [ -z "$test_file" ] && continue
              local short_file
              short_file=$(basename "$test_file")
              tooltip_html="$tooltip_html<li><span class=\"hits-tooltip-file\">${short_file}</span>:<span class=\"hits-tooltip-fn\">${test_fn}</span></li>"
            done <<<"$test_info"
            tooltip_html="$tooltip_html</ul></div>"
            hits_display="<span class=\"hits-badge has-tooltip\">${hits}×${tooltip_html}</span>"
          else
            hits_display="<span class=\"hits-badge\">${hits}×</span>"
          fi
        else
          row_class="uncovered"
          hits_display="<span class=\"hits-badge\">${hits}×</span>"
        fi
      fi

      echo "          <tr id=\"line-${lineno}\" class=\"$row_class line-anchor\">"
      echo "            <td class=\"line-num\">$lineno</td>"
      echo "            <td class=\"hits\">$hits_display</td>"
      echo "            <td class=\"code\">$escaped_line</td>"
      echo "          </tr>"
    done <"$file"

    cat <<'EOF'
        </table>
      </div>
    </div>
  </div>
  <footer class="footer">
    <p class="footer-text">
      Generated by <a href="https://bashunit.typeddevs.com" class="footer-link" target="_blank">bashunit</a>
    </p>
  </footer>
</body>
</html>
EOF
  } >"$output_file"
}

function bashunit::coverage::cleanup() {
  if [ -n "$_BASHUNIT_COVERAGE_DATA_FILE" ]; then
    local coverage_dir
    coverage_dir=$(dirname "$_BASHUNIT_COVERAGE_DATA_FILE")
    rm -rf "$coverage_dir"
  fi
}

# clock.sh

_BASHUNIT_CLOCK_NOW_IMPL=""

function bashunit::clock::_choose_impl() {
  local shell_time
  # Use explicit indices for Bash 3.0 compatibility (empty array access fails with set -u)
  local attempts_count=0
  local attempts

  # 1. Try native shell EPOCHREALTIME (fastest - no subprocess, Bash 5.0+)
  attempts[attempts_count]="EPOCHREALTIME"
  attempts_count=$((attempts_count + 1))
  if shell_time="$(bashunit::clock::shell_time)"; then
    _BASHUNIT_CLOCK_NOW_IMPL="shell"
    return 0
  fi

  # 2. Unix date +%s%N (no subprocess overhead on supported systems)
  attempts[attempts_count]="date"
  attempts_count=$((attempts_count + 1))
  if ! bashunit::check_os::is_macos && ! bashunit::check_os::is_alpine; then
    local result
    result=$(date +%s%N 2>/dev/null)
    if [ "$(echo "$result" | "$GREP" -cv 'N' || true)" -gt 0 ] \
      && [ "$(echo "$result" | "$GREP" -cE '^[0-9]+$' || true)" -gt 0 ]; then
      _BASHUNIT_CLOCK_NOW_IMPL="date"
      return 0
    fi
  fi

  # 3. Try Perl with Time::HiRes
  attempts[attempts_count]="Perl"
  attempts_count=$((attempts_count + 1))
  if bashunit::dependencies::has_perl && perl -MTime::HiRes -e "" &>/dev/null; then
    _BASHUNIT_CLOCK_NOW_IMPL="perl"
    return 0
  fi

  # 4. Try Python 3 with time module
  attempts[attempts_count]="Python"
  attempts_count=$((attempts_count + 1))
  if bashunit::dependencies::has_python; then
    _BASHUNIT_CLOCK_NOW_IMPL="python"
    return 0
  fi

  # 5. Try Node.js
  attempts[attempts_count]="Node"
  attempts_count=$((attempts_count + 1))
  if bashunit::dependencies::has_node; then
    _BASHUNIT_CLOCK_NOW_IMPL="node"
    return 0
  fi

  # 6. Windows fallback with PowerShell
  attempts[attempts_count]="PowerShell"
  attempts_count=$((attempts_count + 1))
  if bashunit::check_os::is_windows && bashunit::dependencies::has_powershell; then
    _BASHUNIT_CLOCK_NOW_IMPL="powershell"
    return 0
  fi

  # 7. Very last fallback: seconds resolution only
  attempts[attempts_count]="date-seconds"
  attempts_count=$((attempts_count + 1))
  if date +%s &>/dev/null; then
    _BASHUNIT_CLOCK_NOW_IMPL="date-seconds"
    return 0
  fi

  # 8. All methods failed
  printf "bashunit::clock::now implementations tried: %s\n" "${attempts[*]}" >&2
  echo ""
  return 1
}

function bashunit::clock::now() {
  if [ -z "$_BASHUNIT_CLOCK_NOW_IMPL" ]; then
    bashunit::clock::_choose_impl || return 1
  fi

  case "$_BASHUNIT_CLOCK_NOW_IMPL" in
  perl)
    perl -MTime::HiRes -e 'printf("%.0f\n", Time::HiRes::time() * 1000000000)'
    ;;
  python)
    python - <<'EOF'
import time, sys
sys.stdout.write(str(int(time.time() * 1000000000)))
EOF
    ;;
  node)
    node -e 'process.stdout.write((BigInt(Date.now()) * 1000000n).toString())'
    ;;
  powershell)
    powershell -Command "\
        \$unixEpoch = [DateTime]'1970-01-01 00:00:00';\
        \$now = [DateTime]::UtcNow;\
        \$ticksSinceEpoch = (\$now - \$unixEpoch).Ticks;\
        \$nanosecondsSinceEpoch = \$ticksSinceEpoch * 100;\
        Write-Output \$nanosecondsSinceEpoch\
      "
    ;;
  date)
    date +%s%N
    ;;
  date-seconds)
    local seconds
    seconds=$(date +%s)
    echo "$((seconds * 1000000000))"
    ;;
  shell)
    # shellcheck disable=SC2155
    local shell_time="$(bashunit::clock::shell_time)"
    local seconds="${shell_time%%.*}"
    local microseconds="${shell_time#*.}"
    # Pad to 6 digits and strip leading zeros for arithmetic
    microseconds="${microseconds}000000"
    microseconds="${microseconds:0:6}"
    microseconds="${microseconds#"${microseconds%%[!0]*}"}"
    microseconds="${microseconds:-0}"
    echo "$(( (seconds * 1000000000) + (microseconds * 1000) ))"
    ;;
  *)
    bashunit::clock::_choose_impl || return 1
    bashunit::clock::now
    ;;
  esac
}

function bashunit::clock::shell_time() {
  # Get time directly from the shell variable EPOCHREALTIME (Bash 5+)
  [ -n "${EPOCHREALTIME+x}" ] && [ -n "$EPOCHREALTIME" ] && LC_ALL=C echo "$EPOCHREALTIME"
}

function bashunit::clock::total_runtime_in_milliseconds() {
  local end_time
  end_time=$(bashunit::clock::now)
  if [ -n "$end_time" ]; then
    bashunit::math::calculate "($end_time - $_BASHUNIT_START_TIME) / 1000000"
  else
    echo ""
  fi
}

function bashunit::clock::total_runtime_in_nanoseconds() {
  local end_time
  end_time=$(bashunit::clock::now)
  if [ -n "$end_time" ]; then
    bashunit::math::calculate "$end_time - $_BASHUNIT_START_TIME"
  else
    echo ""
  fi
}

function bashunit::clock::init() {
  _BASHUNIT_START_TIME=$(bashunit::clock::now)
}

# state.sh

# Cache base64 -w flag support (Alpine needs -w 0, macOS does not support -w)
if [ "$(base64 --help 2>&1 | "$GREP" -c -- "-w" || true)" -gt 0 ]; then
  _BASHUNIT_BASE64_WRAP_FLAG=true
else
  _BASHUNIT_BASE64_WRAP_FLAG=false
fi

_BASHUNIT_TESTS_PASSED=0
_BASHUNIT_TESTS_FAILED=0
_BASHUNIT_TESTS_SKIPPED=0
_BASHUNIT_TESTS_INCOMPLETE=0
_BASHUNIT_TESTS_SNAPSHOT=0
_BASHUNIT_TESTS_RISKY=0
_BASHUNIT_ASSERTIONS_PASSED=0
_BASHUNIT_ASSERTIONS_FAILED=0
_BASHUNIT_ASSERTIONS_SKIPPED=0
_BASHUNIT_ASSERTIONS_INCOMPLETE=0
_BASHUNIT_ASSERTIONS_SNAPSHOT=0
_BASHUNIT_DUPLICATED_FUNCTION_NAMES=""
_BASHUNIT_FILE_WITH_DUPLICATED_FUNCTION_NAMES=""
_BASHUNIT_DUPLICATED_TEST_FUNCTIONS_FOUND=false
_BASHUNIT_TEST_OUTPUT=""
_BASHUNIT_TEST_TITLE=""
_BASHUNIT_TEST_EXIT_CODE=0
_BASHUNIT_TEST_HOOK_FAILURE=""
_BASHUNIT_TEST_HOOK_MESSAGE=""
_BASHUNIT_CURRENT_TEST_INTERPOLATED_NAME=""
_BASHUNIT_ASSERTION_FAILED_IN_TEST=0

function bashunit::state::get_tests_passed() {
  echo "$_BASHUNIT_TESTS_PASSED"
}

function bashunit::state::add_tests_passed() {
  ((_BASHUNIT_TESTS_PASSED++)) || true
}

function bashunit::state::get_tests_failed() {
  echo "$_BASHUNIT_TESTS_FAILED"
}

function bashunit::state::add_tests_failed() {
  ((_BASHUNIT_TESTS_FAILED++)) || true
}

function bashunit::state::get_tests_skipped() {
  echo "$_BASHUNIT_TESTS_SKIPPED"
}

function bashunit::state::add_tests_skipped() {
  ((_BASHUNIT_TESTS_SKIPPED++)) || true
}

function bashunit::state::get_tests_incomplete() {
  echo "$_BASHUNIT_TESTS_INCOMPLETE"
}

function bashunit::state::add_tests_incomplete() {
  ((_BASHUNIT_TESTS_INCOMPLETE++)) || true
}

function bashunit::state::get_tests_snapshot() {
  echo "$_BASHUNIT_TESTS_SNAPSHOT"
}

function bashunit::state::add_tests_snapshot() {
  ((_BASHUNIT_TESTS_SNAPSHOT++)) || true
}

function bashunit::state::get_tests_risky() {
  echo "$_BASHUNIT_TESTS_RISKY"
}

function bashunit::state::add_tests_risky() {
  ((_BASHUNIT_TESTS_RISKY++)) || true
}

function bashunit::state::get_assertions_passed() {
  echo "$_BASHUNIT_ASSERTIONS_PASSED"
}

function bashunit::state::add_assertions_passed() {
  ((_BASHUNIT_ASSERTIONS_PASSED++)) || true
}

function bashunit::state::get_assertions_failed() {
  echo "$_BASHUNIT_ASSERTIONS_FAILED"
}

function bashunit::state::add_assertions_failed() {
  ((_BASHUNIT_ASSERTIONS_FAILED++)) || true
}

function bashunit::state::get_assertions_skipped() {
  echo "$_BASHUNIT_ASSERTIONS_SKIPPED"
}

function bashunit::state::add_assertions_skipped() {
  ((_BASHUNIT_ASSERTIONS_SKIPPED++)) || true
}

function bashunit::state::get_assertions_incomplete() {
  echo "$_BASHUNIT_ASSERTIONS_INCOMPLETE"
}

function bashunit::state::add_assertions_incomplete() {
  ((_BASHUNIT_ASSERTIONS_INCOMPLETE++)) || true
}

function bashunit::state::get_assertions_snapshot() {
  echo "$_BASHUNIT_ASSERTIONS_SNAPSHOT"
}

function bashunit::state::add_assertions_snapshot() {
  ((_BASHUNIT_ASSERTIONS_SNAPSHOT++)) || true
}

function bashunit::state::is_duplicated_test_functions_found() {
  echo "$_BASHUNIT_DUPLICATED_TEST_FUNCTIONS_FOUND"
}

function bashunit::state::set_duplicated_test_functions_found() {
  _BASHUNIT_DUPLICATED_TEST_FUNCTIONS_FOUND=true
}

function bashunit::state::get_duplicated_function_names() {
  echo "$_BASHUNIT_DUPLICATED_FUNCTION_NAMES"
}

function bashunit::state::set_duplicated_function_names() {
  _BASHUNIT_DUPLICATED_FUNCTION_NAMES="$1"
}

function bashunit::state::get_file_with_duplicated_function_names() {
  echo "$_BASHUNIT_FILE_WITH_DUPLICATED_FUNCTION_NAMES"
}

function bashunit::state::set_file_with_duplicated_function_names() {
  _BASHUNIT_FILE_WITH_DUPLICATED_FUNCTION_NAMES="$1"
}

function bashunit::state::add_test_output() {
  _BASHUNIT_TEST_OUTPUT="$_BASHUNIT_TEST_OUTPUT$1"
}

function bashunit::state::get_test_exit_code() {
  echo "$_BASHUNIT_TEST_EXIT_CODE"
}

function bashunit::state::set_test_exit_code() {
  _BASHUNIT_TEST_EXIT_CODE="$1"
}

function bashunit::state::get_test_title() {
  echo "$_BASHUNIT_TEST_TITLE"
}

function bashunit::state::set_test_title() {
  _BASHUNIT_TEST_TITLE="$1"
}

function bashunit::state::reset_test_title() {
  _BASHUNIT_TEST_TITLE=""
}

function bashunit::state::get_current_test_interpolated_function_name() {
  echo "$_BASHUNIT_CURRENT_TEST_INTERPOLATED_NAME"
}

function bashunit::state::set_current_test_interpolated_function_name() {
  _BASHUNIT_CURRENT_TEST_INTERPOLATED_NAME="$1"
}

function bashunit::state::reset_current_test_interpolated_function_name() {
  _BASHUNIT_CURRENT_TEST_INTERPOLATED_NAME=""
}

function bashunit::state::get_test_hook_failure() {
  echo "$_BASHUNIT_TEST_HOOK_FAILURE"
}

function bashunit::state::set_test_hook_failure() {
  _BASHUNIT_TEST_HOOK_FAILURE="$1"
}

function bashunit::state::reset_test_hook_failure() {
  _BASHUNIT_TEST_HOOK_FAILURE=""
}

function bashunit::state::get_test_hook_message() {
  echo "$_BASHUNIT_TEST_HOOK_MESSAGE"
}

function bashunit::state::set_test_hook_message() {
  _BASHUNIT_TEST_HOOK_MESSAGE="$1"
}

function bashunit::state::reset_test_hook_message() {
  _BASHUNIT_TEST_HOOK_MESSAGE=""
}

function bashunit::state::is_assertion_failed_in_test() {
  ((_BASHUNIT_ASSERTION_FAILED_IN_TEST))
}

function bashunit::state::mark_assertion_failed_in_test() {
  _BASHUNIT_ASSERTION_FAILED_IN_TEST=1
}

function bashunit::state::set_duplicated_functions_merged() {
  bashunit::state::set_duplicated_test_functions_found
  bashunit::state::set_file_with_duplicated_function_names "$1"
  bashunit::state::set_duplicated_function_names "$2"
}

function bashunit::state::initialize_assertions_count() {
  _BASHUNIT_ASSERTIONS_PASSED=0
  _BASHUNIT_ASSERTIONS_FAILED=0
  _BASHUNIT_ASSERTIONS_SKIPPED=0
  _BASHUNIT_ASSERTIONS_INCOMPLETE=0
  _BASHUNIT_ASSERTIONS_SNAPSHOT=0
  _BASHUNIT_TEST_OUTPUT=""
  _BASHUNIT_TEST_TITLE=""
  _BASHUNIT_TEST_HOOK_FAILURE=""
  _BASHUNIT_TEST_HOOK_MESSAGE=""
  _BASHUNIT_ASSERTION_FAILED_IN_TEST=0
}

function bashunit::state::export_subshell_context() {
  local encoded_test_output
  local encoded_test_title

  local encoded_test_hook_message

  if [ "$_BASHUNIT_BASE64_WRAP_FLAG" = true ]; then
    # Alpine requires the -w 0 option to avoid wrapping
    encoded_test_output=$(echo -n "$_BASHUNIT_TEST_OUTPUT" | base64 -w 0)
    encoded_test_title=$(echo -n "$_BASHUNIT_TEST_TITLE" | base64 -w 0)
    encoded_test_hook_message=$(echo -n "$_BASHUNIT_TEST_HOOK_MESSAGE" | base64 -w 0)
  else
    # macOS and others: default base64 without wrapping
    encoded_test_output=$(echo -n "$_BASHUNIT_TEST_OUTPUT" | base64)
    encoded_test_title=$(echo -n "$_BASHUNIT_TEST_TITLE" | base64)
    encoded_test_hook_message=$(echo -n "$_BASHUNIT_TEST_HOOK_MESSAGE" | base64)
  fi

  cat <<EOF
##ASSERTIONS_FAILED=$_BASHUNIT_ASSERTIONS_FAILED\
##ASSERTIONS_PASSED=$_BASHUNIT_ASSERTIONS_PASSED\
##ASSERTIONS_SKIPPED=$_BASHUNIT_ASSERTIONS_SKIPPED\
##ASSERTIONS_INCOMPLETE=$_BASHUNIT_ASSERTIONS_INCOMPLETE\
##ASSERTIONS_SNAPSHOT=$_BASHUNIT_ASSERTIONS_SNAPSHOT\
##TEST_EXIT_CODE=$_BASHUNIT_TEST_EXIT_CODE\
##TEST_HOOK_FAILURE=$_BASHUNIT_TEST_HOOK_FAILURE\
##TEST_HOOK_MESSAGE=$encoded_test_hook_message\
##TEST_TITLE=$encoded_test_title\
##TEST_OUTPUT=$encoded_test_output##
EOF
}

function bashunit::state::calculate_total_assertions() {
  local input="$1"
  local total=0

  local numbers
  numbers=$(echo "$input" | grep -oE '##ASSERTIONS_\w+=[0-9]+' | grep -oE '[0-9]+')

  local number
  for number in $numbers; do
    total=$((total + number))
  done

  echo $total
}

function bashunit::state::print_line() {
  # shellcheck disable=SC2034
  local type=$1
  local line=$2

  ((_BASHUNIT_TOTAL_TESTS_COUNT++)) || true

  bashunit::state::add_test_output "[$type]$line"

  if bashunit::env::is_no_progress_enabled; then
    return
  fi

  if bashunit::env::is_tap_output_enabled; then
    bashunit::state::print_tap_line "$type" "$line"
    return
  fi

  if ! bashunit::env::is_simple_output_enabled; then
    printf "%s\n" "$line"
    return
  fi

  local char
  case "$type" in
  successful) char="." ;;
  failure) char="${_BASHUNIT_COLOR_FAILED}F${_BASHUNIT_COLOR_DEFAULT}" ;;
  failed) char="${_BASHUNIT_COLOR_FAILED}F${_BASHUNIT_COLOR_DEFAULT}" ;;
  failed_snapshot) char="${_BASHUNIT_COLOR_FAILED}F${_BASHUNIT_COLOR_DEFAULT}" ;;
  skipped) char="${_BASHUNIT_COLOR_SKIPPED}S${_BASHUNIT_COLOR_DEFAULT}" ;;
  incomplete) char="${_BASHUNIT_COLOR_INCOMPLETE}I${_BASHUNIT_COLOR_DEFAULT}" ;;
  snapshot) char="${_BASHUNIT_COLOR_SNAPSHOT}N${_BASHUNIT_COLOR_DEFAULT}" ;;
  risky) char="${_BASHUNIT_COLOR_RISKY}R${_BASHUNIT_COLOR_DEFAULT}" ;;
  error) char="${_BASHUNIT_COLOR_FAILED}E${_BASHUNIT_COLOR_DEFAULT}" ;;
  *) char="?" && bashunit::log "warning" "unknown test type '$type'" ;;
  esac

  if bashunit::parallel::is_enabled; then
    printf "%s" "$char"
  else
    if ((_BASHUNIT_TOTAL_TESTS_COUNT % 50 == 0)); then
      printf "%s\n" "$char"
    else
      printf "%s" "$char"
    fi
  fi
}

function bashunit::state::print_tap_line() {
  local type=$1
  local line=$2

  local clean_line
  clean_line=$(printf "%s" "$line" | sed 's/\x1B\[[0-9;]*[mK]//g')
  local test_name="${clean_line#*: }"
  test_name="${test_name%%$'\n'*}"
  # Strip trailing whitespace and duration
  test_name=$(printf "%s" "$test_name" | \
    sed 's/[[:space:]]*[0-9][0-9]*m\{0,1\}[[:space:]]*[0-9.]*[ms]*[[:space:]]*$//')

  case "$type" in
  successful)
    printf "ok %d - %s\n" "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name"
    ;;
  failure | failed | failed_snapshot | error)
    printf "not ok %d - %s\n" "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name"
    local detail_line
    printf "  ---\n"
    while IFS= read -r detail_line; do
      detail_line=$(printf "%s" "$detail_line" | sed 's/\x1B\[[0-9;]*[mK]//g')
      if [ -n "$detail_line" ] \
        && [ "$(echo "$detail_line" | "$GREP" -cF "Failed:" || true)" -eq 0 ] \
        && [ "$(echo "$detail_line" | "$GREP" -cF "Error:" || true)" -eq 0 ]; then
        local trimmed="${detail_line#"${detail_line%%[![:space:]]*}"}"
        printf "  %s\n" "$trimmed"
      fi
    done <<< "$clean_line"
    printf "  ...\n"
    ;;
  skipped)
    local skip_name="${test_name%%   *}"
    local skip_reason="${test_name#"$skip_name"}"
    skip_reason="${skip_reason#"${skip_reason%%[![:space:]]*}"}"
    if [ -n "$skip_reason" ]; then
      printf "ok %d - %s # SKIP %s\n" \
        "$_BASHUNIT_TOTAL_TESTS_COUNT" "$skip_name" "$skip_reason"
    else
      printf "ok %d - %s # SKIP\n" \
        "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name"
    fi
    ;;
  incomplete)
    printf "ok %d - %s # TODO incomplete\n" \
      "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name"
    ;;
  snapshot)
    printf "ok %d - %s # snapshot\n" \
      "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name"
    ;;
  risky)
    printf "ok %d - %s # RISKY no assertions\n" \
      "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name"
    ;;
  *)
    printf "not ok %d - %s\n" \
      "$_BASHUNIT_TOTAL_TESTS_COUNT" "$test_name"
    ;;
  esac
}

# colors.sh

# Pass in any number of ANSI SGR codes.
#
# Code reference:
#   https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
# Credit:
#   https://superuser.com/a/1119396
bashunit::sgr() {
  local codes=${1:-0}
  shift

  local c
  for c in "$@"; do
    codes="$codes;$c"
  done

  echo $'\e'"[${codes}m"
}

if bashunit::env::is_no_color_enabled; then
  _BASHUNIT_COLOR_BOLD=""
  _BASHUNIT_COLOR_FAINT=""
  _BASHUNIT_COLOR_BLACK=""
  _BASHUNIT_COLOR_FAILED=""
  _BASHUNIT_COLOR_PASSED=""
  _BASHUNIT_COLOR_SKIPPED=""
  _BASHUNIT_COLOR_INCOMPLETE=""
  _BASHUNIT_COLOR_SNAPSHOT=""
  _BASHUNIT_COLOR_RISKY=""
  _BASHUNIT_COLOR_RETURN_ERROR=""
  _BASHUNIT_COLOR_RETURN_SUCCESS=""
  _BASHUNIT_COLOR_RETURN_SKIPPED=""
  _BASHUNIT_COLOR_RETURN_INCOMPLETE=""
  _BASHUNIT_COLOR_RETURN_SNAPSHOT=""
  _BASHUNIT_COLOR_RETURN_RISKY=""
  _BASHUNIT_COLOR_DEFAULT=""
else
  _BASHUNIT_COLOR_BOLD="$(bashunit::sgr 1)"
  _BASHUNIT_COLOR_FAINT="$(bashunit::sgr 2)"
  _BASHUNIT_COLOR_BLACK="$(bashunit::sgr 30)"
  _BASHUNIT_COLOR_FAILED="$(bashunit::sgr 31)"
  _BASHUNIT_COLOR_PASSED="$(bashunit::sgr 32)"
  _BASHUNIT_COLOR_SKIPPED="$(bashunit::sgr 33)"
  _BASHUNIT_COLOR_INCOMPLETE="$(bashunit::sgr 36)"
  _BASHUNIT_COLOR_SNAPSHOT="$(bashunit::sgr 34)"
  _BASHUNIT_COLOR_RISKY="$(bashunit::sgr 35)"
  _BASHUNIT_COLOR_RETURN_ERROR="$(bashunit::sgr 41)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD"
  _BASHUNIT_COLOR_RETURN_SUCCESS="$(bashunit::sgr 42)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD"
  _BASHUNIT_COLOR_RETURN_SKIPPED="$(bashunit::sgr 43)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD"
  _BASHUNIT_COLOR_RETURN_INCOMPLETE="$(bashunit::sgr 46)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD"
  _BASHUNIT_COLOR_RETURN_SNAPSHOT="$(bashunit::sgr 44)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD"
  _BASHUNIT_COLOR_RETURN_RISKY="$(bashunit::sgr 45)$_BASHUNIT_COLOR_BLACK$_BASHUNIT_COLOR_BOLD"
  _BASHUNIT_COLOR_DEFAULT="$(bashunit::sgr 0)"
fi

# console_header.sh

function bashunit::console_header::print_version_with_env() {
  local filter=${1:-}
  shift || true

  if ! bashunit::env::is_show_header_enabled; then
    return
  fi

  bashunit::console_header::print_version "$filter" "$@"

  if bashunit::env::is_dev_mode_enabled; then
    printf "%sDev log:%s %s\n" \
      "${_BASHUNIT_COLOR_INCOMPLETE}" "${_BASHUNIT_COLOR_DEFAULT}" "$BASHUNIT_DEV_LOG"
  fi
}

function bashunit::console_header::print_version() {
  local filter=${1:-}
  shift || true

  # Bash 3.0 compatible: check argument count after shift
  local files_count=$#
  local total_tests
  if [ "$files_count" -eq 0 ]; then
    total_tests=0
  elif bashunit::parallel::is_enabled && bashunit::env::is_simple_output_enabled; then
    # Skip counting in parallel+simple mode for faster startup
    total_tests=0
  else
    total_tests=$(bashunit::helper::find_total_tests "$filter" "$@")
  fi

  if bashunit::env::is_header_ascii_art_enabled; then
    cat <<EOF
_               _                   _
| |__   __ _ ___| |__  __ __ ____ (_) |_
| '_ \ / _' / __| '_ \| | | | '_ \| | __|
| |_) | (_| \__ \ | | | |_| | | | | | |_
|_.__/ \__,_|___/_| |_|\___/|_| |_|_|\__|
EOF
    if [ "$total_tests" -eq 0 ]; then
      printf "%s\n" "$BASHUNIT_VERSION"
    else
      printf "%s | Tests: %s\n" "$BASHUNIT_VERSION" "$total_tests"
    fi
    return
  fi

  if [ "$total_tests" -eq 0 ]; then
    printf "%s%sbashunit%s - %s\n" \
      "$_BASHUNIT_COLOR_BOLD" "$_BASHUNIT_COLOR_PASSED" "$_BASHUNIT_COLOR_DEFAULT" "$BASHUNIT_VERSION"
  else
    printf "%s%sbashunit%s - %s | Tests: %s\n" \
      "${_BASHUNIT_COLOR_BOLD}" "${_BASHUNIT_COLOR_PASSED}" "${_BASHUNIT_COLOR_DEFAULT}" \
      "$BASHUNIT_VERSION" "$total_tests"
  fi
}

function bashunit::console_header::print_help() {
  cat <<EOF
Usage: bashunit <command> [arguments] [options]

Commands:
  test [path]         Run tests (default command)
  bench [path]        Run benchmarks
  assert <fn> <args>  Run standalone assertion
  doc [filter]        Display assertion documentation
  init [dir]          Initialize a new test directory
  learn               Start interactive tutorial
  watch [path]        Watch files and re-run tests on change
  upgrade             Upgrade bashunit to latest version

Global Options:
  -h, --help        Show this help message
  -v, --version     Display the current version

Run 'bashunit <command> --help' for command-specific options.

Examples:
  bashunit test tests/                Run all tests in directory
  bashunit tests/                     Run all tests (shorthand)
  bashunit bench                      Run all benchmarks
  bashunit assert equals "foo" "foo"  Run standalone assertion
  bashunit doc contains               Show docs for 'contains' assertions
  bashunit init                       Initialize test directory

More info: https://bashunit.typeddevs.com/command-line
EOF
}

function bashunit::console_header::print_test_help() {
  cat <<EOF
Usage: bashunit test [path] [options]
  bashunit [path] [options]

Run test files. If no path is provided, searches for tests in BASHUNIT_DEFAULT_PATH.

Arguments:
  path                        File or directory containing tests
    - Directories: runs all '*test.sh' files
    - Wildcards: supported to match multiple files

Options:
  -a, --assert <fn> <args>    Run a standalone assert function (deprecated: use 'bashunit assert')
  -e, --env, --boot <file>    Load a custom env/bootstrap file  (supports args)
  -f, --filter <name>         Only run tests matching the name
  --tag <name>                Only run tests with matching @tag (repeatable, OR logic)
  --exclude-tag <name>        Skip tests with matching @tag (repeatable, exclude wins)
  --log-junit <file>          Write JUnit XML report
  -j, --jobs <N>              Run tests in parallel with max N concurrent jobs
  -p, --parallel              Run tests in parallel (unlimited concurrency)
  --no-parallel               Run tests sequentially
  -r, --report-html <file>    Write HTML report
  -s, --simple                Simple output (dots)
  --detailed                  Detailed output (default)
  --output <format>           Output format: tap (TAP version 13)
  -R, --run-all               Run all assertions (don't stop on first failure)
  -S, --stop-on-failure       Stop on first failure
  -vvv, --verbose             Show execution details
  --debug [file]              Enable shell debug mode
  --no-output                 Suppress all output
  --failures-only             Only show failures (suppress passed/skipped/incomplete)
  --no-progress               Suppress real-time progress, show only final results
  --show-output               Show test output on failure (default: enabled)
  --no-output-on-failure      Hide test output on failure
  --strict                    Enable strict shell mode (set -euo pipefail)
  --skip-env-file             Skip .env loading, use shell environment only
  -l, --login                 Run tests in login shell context
  -w, --watch                 Watch for changes and re-run tests
  --no-color                  Disable colored output (honors NO_COLOR env var)
  -h, --help                  Show this help message

Coverage:
  --coverage                   Enable code coverage tracking
  --coverage-paths <paths>     Source paths to track (default: auto-discover)
  --coverage-exclude <pats>    Patterns to exclude (comma-separated)
  --coverage-report [file]     Output file (default: coverage/lcov.info)
  --coverage-report-html [dir] HTML report (default: coverage/html)
  --coverage-min <pct>         Fail if coverage below percentage
  --no-coverage-report         Disable file output, console only

Examples:
  bashunit test tests/
  bashunit test tests/unit/ --parallel
  bashunit test --filter "user" tests/
  bashunit test -a equals "foo" "foo"
  bashunit test tests/ --coverage
  bashunit test tests/ --coverage --coverage-min 80
  bashunit test tests/ --coverage-report-html
EOF
}

function bashunit::console_header::print_bench_help() {
  cat <<EOF
Usage: bashunit bench [path] [options]

Run benchmark files. Searches for '*bench.sh' files.

Arguments:
  path                        File or directory containing benchmarks

Options:
  -e, --env, --boot <file>    Load a custom env/bootstrap file (supports args)
  -f, --filter <name>         Only run benchmarks matching the name
  -s, --simple                Simple output
  --detailed                  Detailed output (default)
  -vvv, --verbose             Show execution details
  --skip-env-file             Skip .env loading, use shell environment only
  -l, --login                 Run in login shell context
  --no-color                  Disable colored output (honors NO_COLOR env var)
  -h, --help                  Show this help message

Examples:
  bashunit bench
  bashunit bench benchmarks/
  bashunit bench --filter "parse"
EOF
}

function bashunit::console_header::print_doc_help() {
  cat <<EOF
Usage: bashunit doc [filter]

Display documentation for assertion functions.

Arguments:
filter                      Optional filter to show only matching assertions

Examples:
bashunit doc                Show all assertions
bashunit doc equals         Show assertions containing 'equals'
bashunit doc file           Show file-related assertions
EOF
}

function bashunit::console_header::print_init_help() {
  cat <<EOF
Usage: bashunit init [directory]

Initialize a new test directory with sample files.

Arguments:
  directory                   Target directory (default: tests)

Creates:
  - bootstrap.sh              Setup file for test configuration
  - example_test.sh           Sample test file to get started

Examples:
  bashunit init               Create tests/ directory
  bashunit init spec          Create spec/ directory
EOF
}

function bashunit::console_header::print_learn_help() {
  cat <<EOF
Usage: bashunit learn

Start the interactive learning tutorial.

The tutorial includes 10 progressive lessons:
  1. Basics - Your First Test
  2. Assertions - Testing Different Conditions
  3. Setup & Teardown - Managing Test Lifecycle
  4. Testing Functions - Unit Testing Patterns
  5. Testing Scripts - Integration Testing
  6. Mocking - Test Doubles and Mocks
  7. Spies - Verifying Function Calls
  8. Data Providers - Parameterized Tests
  9. Exit Codes - Testing Success and Failure
  10. Complete Challenge - Real World Scenario

Your progress is saved automatically.
EOF
}

function bashunit::console_header::print_upgrade_help() {
  cat <<EOF
Usage: bashunit upgrade

Upgrade bashunit to the latest version.

Downloads and installs the newest release from GitHub.
EOF
}

function bashunit::console_header::print_assert_help() {
  cat <<EOF
Usage: bashunit assert <function> [args...]
  bashunit assert "<command>" <assertion1> <arg1> [<assertion2> <arg2>...]

Run standalone assertion(s) without creating a test file.

Single assertion:
  bashunit assert equals "foo" "foo"
  bashunit assert same "1" "1"
  bashunit assert contains "world" "hello world"
  bashunit assert exit_code 0 "echo 'success'"

Multiple assertions on command output:
  bashunit assert "echo 'error' && exit 1" exit_code "1" contains "error"
  bashunit assert "./my_script.sh" exit_code "0" contains "success" not_contains "error"

Arguments:
  function                    Assertion function name (with or without 'assert_' prefix)
  command                     Command to execute (for multi-assertion mode)
  assertion                   Assertion name (exit_code, contains, equals, etc.)
  arg                         Expected value for the assertion

Note: You can also use 'bashunit test --assert <fn> <args>' (deprecated).
  The 'bashunit assert' subcommand is the recommended approach.

More info: https://bashunit.typeddevs.com/standalone
EOF
}

function bashunit::console_header::print_watch_help() {
  cat << 'ENDOFHELP'
Usage: bashunit watch [path] [test-options]

Watch .sh files for changes and automatically re-run tests.

Arguments:
  [path]          Directory or file to watch and test (default: .)

Options:
  -h, --help      Show this help message
  Any option accepted by 'bashunit test' is also accepted here.

Requirements:
  Linux:  inotifywait  (sudo apt install inotify-tools)
  macOS:  fswatch      (brew install fswatch)

Examples:
  bashunit watch                      Watch current directory
  bashunit watch tests/               Watch the tests/ directory
  bashunit watch tests/ --filter user Watch and filter by name
  bashunit watch tests/ --simple      Watch with simple output
ENDOFHELP
}

# console_results.sh
# shellcheck disable=SC2155

_BASHUNIT_TOTAL_TESTS_COUNT=0

function bashunit::console_results::render_result() {
  if [ "$(bashunit::state::is_duplicated_test_functions_found)" = true ]; then
    bashunit::console_results::print_execution_time
    printf "%s%s%s\n" "${_BASHUNIT_COLOR_RETURN_ERROR}" "Duplicate test functions found" "${_BASHUNIT_COLOR_DEFAULT}"
    printf "File with duplicate functions: %s\n" "$(bashunit::state::get_file_with_duplicated_function_names)"
    printf "Duplicate functions: %s\n" "$(bashunit::state::get_duplicated_function_names)"
    return 1
  fi

  if bashunit::env::is_tap_output_enabled; then
    printf "1..%d\n" "$_BASHUNIT_TOTAL_TESTS_COUNT"
    if [ "$_BASHUNIT_TESTS_FAILED" -gt 0 ]; then
      return 1
    fi
    return 0
  fi

  if bashunit::env::is_simple_output_enabled; then
    printf "\n\n"
  fi

  # Cache state values to avoid repeated subshell invocations
  local tests_passed=$_BASHUNIT_TESTS_PASSED
  local tests_skipped=$_BASHUNIT_TESTS_SKIPPED
  local tests_incomplete=$_BASHUNIT_TESTS_INCOMPLETE
  local tests_snapshot=$_BASHUNIT_TESTS_SNAPSHOT
  local tests_failed=$_BASHUNIT_TESTS_FAILED
  local tests_risky=$_BASHUNIT_TESTS_RISKY
  local assertions_passed=$_BASHUNIT_ASSERTIONS_PASSED
  local assertions_skipped=$_BASHUNIT_ASSERTIONS_SKIPPED
  local assertions_incomplete=$_BASHUNIT_ASSERTIONS_INCOMPLETE
  local assertions_snapshot=$_BASHUNIT_ASSERTIONS_SNAPSHOT
  local assertions_failed=$_BASHUNIT_ASSERTIONS_FAILED

  local total_tests=0
  total_tests=$((total_tests + tests_passed))
  total_tests=$((total_tests + tests_skipped))
  total_tests=$((total_tests + tests_incomplete))
  total_tests=$((total_tests + tests_snapshot))
  total_tests=$((total_tests + tests_failed))
  total_tests=$((total_tests + tests_risky))

  local total_assertions=0
  total_assertions=$((total_assertions + assertions_passed))
  total_assertions=$((total_assertions + assertions_skipped))
  total_assertions=$((total_assertions + assertions_incomplete))
  total_assertions=$((total_assertions + assertions_snapshot))
  total_assertions=$((total_assertions + assertions_failed))

  printf "%sTests:     %s" "$_BASHUNIT_COLOR_FAINT" "$_BASHUNIT_COLOR_DEFAULT"
  if [ "$tests_passed" -gt 0 ] || [ "$assertions_passed" -gt 0 ]; then
    printf " %s%s passed%s," "$_BASHUNIT_COLOR_PASSED" "$tests_passed" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  if [ "$tests_skipped" -gt 0 ] || [ "$assertions_skipped" -gt 0 ]; then
    printf " %s%s skipped%s," "$_BASHUNIT_COLOR_SKIPPED" "$tests_skipped" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  if [ "$tests_incomplete" -gt 0 ] || [ "$assertions_incomplete" -gt 0 ]; then
    printf " %s%s incomplete%s," "$_BASHUNIT_COLOR_INCOMPLETE" "$tests_incomplete" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  if [ "$tests_snapshot" -gt 0 ] || [ "$assertions_snapshot" -gt 0 ]; then
    printf " %s%s snapshot%s," "$_BASHUNIT_COLOR_SNAPSHOT" "$tests_snapshot" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  if [ "$tests_failed" -gt 0 ] || [ "$assertions_failed" -gt 0 ]; then
    printf " %s%s failed%s," "$_BASHUNIT_COLOR_FAILED" "$tests_failed" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  if [ "$tests_risky" -gt 0 ]; then
    printf " %s%s risky%s," "$_BASHUNIT_COLOR_RISKY" "$tests_risky" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  printf " %s total\n" "$total_tests"

  printf "%sAssertions:%s" "$_BASHUNIT_COLOR_FAINT" "$_BASHUNIT_COLOR_DEFAULT"
  if [ "$tests_passed" -gt 0 ] || [ "$assertions_passed" -gt 0 ]; then
    printf " %s%s passed%s," "$_BASHUNIT_COLOR_PASSED" "$assertions_passed" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  if [ "$tests_skipped" -gt 0 ] || [ "$assertions_skipped" -gt 0 ]; then
    printf " %s%s skipped%s," "$_BASHUNIT_COLOR_SKIPPED" "$assertions_skipped" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  if [ "$tests_incomplete" -gt 0 ] || [ "$assertions_incomplete" -gt 0 ]; then
    printf " %s%s incomplete%s," "$_BASHUNIT_COLOR_INCOMPLETE" "$assertions_incomplete" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  if [ "$tests_snapshot" -gt 0 ] || [ "$assertions_snapshot" -gt 0 ]; then
    printf " %s%s snapshot%s," "$_BASHUNIT_COLOR_SNAPSHOT" "$assertions_snapshot" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  if [ "$tests_failed" -gt 0 ] || [ "$assertions_failed" -gt 0 ]; then
    printf " %s%s failed%s," "$_BASHUNIT_COLOR_FAILED" "$assertions_failed" "$_BASHUNIT_COLOR_DEFAULT"
  fi
  printf " %s total\n" "$total_assertions"

  if [ "$tests_failed" -gt 0 ]; then
    printf "\n%s%s%s\n" "$_BASHUNIT_COLOR_RETURN_ERROR" " Some tests failed " "$_BASHUNIT_COLOR_DEFAULT"
    bashunit::console_results::print_execution_time
    return 1
  fi

  if [ "$tests_risky" -gt 0 ]; then
    printf "\n%s%s%s\n" "$_BASHUNIT_COLOR_RETURN_RISKY" " Some tests risky (no assertions) " "$_BASHUNIT_COLOR_DEFAULT"
    bashunit::console_results::print_execution_time
    return 0
  fi

  if [ "$tests_incomplete" -gt 0 ]; then
    printf "\n%s%s%s\n" "$_BASHUNIT_COLOR_RETURN_INCOMPLETE" " Some tests incomplete " "$_BASHUNIT_COLOR_DEFAULT"
    bashunit::console_results::print_execution_time
    return 0
  fi

  if [ "$tests_skipped" -gt 0 ]; then
    printf "\n%s%s%s\n" "$_BASHUNIT_COLOR_RETURN_SKIPPED" " Some tests skipped " "$_BASHUNIT_COLOR_DEFAULT"
    bashunit::console_results::print_execution_time
    return 0
  fi

  if [ "$tests_snapshot" -gt 0 ]; then
    printf "\n%s%s%s\n" "$_BASHUNIT_COLOR_RETURN_SNAPSHOT" " Some snapshots created " "$_BASHUNIT_COLOR_DEFAULT"
    bashunit::console_results::print_execution_time
    return 0
  fi

  if [ "$total_tests" -eq 0 ]; then
    printf "\n%s%s%s\n" "$_BASHUNIT_COLOR_RETURN_ERROR" " No tests found " "$_BASHUNIT_COLOR_DEFAULT"
    bashunit::console_results::print_execution_time
    return 1
  fi

  printf "\n%s%s%s\n" "$_BASHUNIT_COLOR_RETURN_SUCCESS" " All tests passed " "$_BASHUNIT_COLOR_DEFAULT"
  bashunit::console_results::print_execution_time
  return 0
}

function bashunit::console_results::print_execution_time() {
  if ! bashunit::env::is_show_execution_time_enabled; then
    return
  fi

  local time
  time=$(bashunit::clock::total_runtime_in_milliseconds)
  # Strip decimal portion (integer truncation, Bash 3.0 compatible)
  time="${time%%.*}"
  time="${time:-0}"

  if [ "$time" -lt 1000 ]; then
    printf "${_BASHUNIT_COLOR_BOLD}%s${_BASHUNIT_COLOR_DEFAULT}\n" \
      "Time taken: ${time}ms"
    return
  fi

  local time_in_seconds=$((time / 1000))

  if [ "$time_in_seconds" -ge 60 ]; then
    local minutes=$((time_in_seconds / 60))
    local seconds=$((time_in_seconds % 60))
    printf "${_BASHUNIT_COLOR_BOLD}%s${_BASHUNIT_COLOR_DEFAULT}\n" \
      "Time taken: ${minutes}m ${seconds}s"
    return
  fi

  local integer_part=$((time / 1000))
  local decimal_part=$(( (time % 1000) / 10 ))
  local formatted_seconds
  formatted_seconds=$(printf "%d.%02d" "$integer_part" "$decimal_part")

  printf "${_BASHUNIT_COLOR_BOLD}%s${_BASHUNIT_COLOR_DEFAULT}\n" \
    "Time taken: ${formatted_seconds}s"
}

function bashunit::console_results::format_duration() {
  local duration_ms="$1"

  if [ "$duration_ms" -ge 60000 ]; then
    local time_in_seconds=$((duration_ms / 1000))
    local minutes=$((time_in_seconds / 60))
    local seconds=$((time_in_seconds % 60))
    echo "${minutes}m ${seconds}s"
  elif [ "$duration_ms" -ge 1000 ]; then
    local integer_part=$((duration_ms / 1000))
    local decimal_part=$(( (duration_ms % 1000) / 10 ))
    local formatted_seconds
    formatted_seconds=$(printf "%d.%02d" "$integer_part" "$decimal_part")
    echo "${formatted_seconds}s"
  else
    echo "${duration_ms}ms"
  fi
}

function bashunit::console_results::print_hook_completed() {
  local hook_name="$1"
  local duration_ms="$2"

  if bashunit::env::is_simple_output_enabled; then
    return
  fi

  if bashunit::env::is_failures_only_enabled; then
    return
  fi

  if bashunit::env::is_no_progress_enabled; then
    return
  fi

  if bashunit::env::is_tap_output_enabled; then
    return
  fi

  if bashunit::parallel::is_enabled; then
    return
  fi

  local line
  line=$(printf "%s● %s%s" \
    "$_BASHUNIT_COLOR_PASSED" "$hook_name" "$_BASHUNIT_COLOR_DEFAULT")

  local time_display
  time_display=$(bashunit::console_results::format_duration "$duration_ms")

  printf "%s\n" "$(bashunit::str::rpad "$line" "$time_display")"
}

function bashunit::console_results::print_successful_test() {
  local test_name=$1
  shift
  local duration=${1:-"0"}
  shift

  local line
  if [ -z "$*" ]; then
    line=$(printf "%s✓ Passed%s: %s" "$_BASHUNIT_COLOR_PASSED" "$_BASHUNIT_COLOR_DEFAULT" "$test_name")
  else
    local quoted_args=""
    local arg
    for arg in "$@"; do
      if [ -z "$quoted_args" ]; then
        quoted_args="'$arg'"
      else
        quoted_args="$quoted_args, '$arg'"
      fi
    done
    line=$(printf "%s✓ Passed%s: %s (%s)" \
      "$_BASHUNIT_COLOR_PASSED" "$_BASHUNIT_COLOR_DEFAULT" "$test_name" "$quoted_args")
  fi

  local full_line=$line
  if bashunit::env::is_show_execution_time_enabled; then
    local time_display
    if [ "$duration" -ge 60000 ]; then
      local time_in_seconds=$((duration / 1000))
      local minutes=$((time_in_seconds / 60))
      local seconds=$((time_in_seconds % 60))
      time_display="${minutes}m ${seconds}s"
    elif [ "$duration" -ge 1000 ]; then
      local integer_part=$((duration / 1000))
      local decimal_part=$(( (duration % 1000) / 10 ))
      local formatted_seconds
      formatted_seconds=$(printf "%d.%02d" "$integer_part" "$decimal_part")
      time_display="${formatted_seconds}s"
    else
      time_display="${duration}ms"
    fi
    full_line="$(printf "%s\n" "$(bashunit::str::rpad "$line" "$time_display")")"
  fi

  bashunit::state::print_line "successful" "$full_line"
}

function bashunit::console_results::print_failure_message() {
  local test_name=$1
  local failure_message=$2

  local line
  line="$(printf "\
${_BASHUNIT_COLOR_FAILED}✗ Failed${_BASHUNIT_COLOR_DEFAULT}: %s
    ${_BASHUNIT_COLOR_FAINT}Message:${_BASHUNIT_COLOR_DEFAULT} \
${_BASHUNIT_COLOR_BOLD}'%s'${_BASHUNIT_COLOR_DEFAULT}\n" \
    "${test_name}" "${failure_message}")"

  bashunit::state::print_line "failure" "$line"
}

function bashunit::console_results::print_failed_test() {
  local function_name=$1
  local expected=$2
  local failure_condition_message=$3
  local actual=$4
  local extra_key=${5-}
  local extra_value=${6-}

  local line
  line="$(printf "\
${_BASHUNIT_COLOR_FAILED}✗ Failed${_BASHUNIT_COLOR_DEFAULT}: %s
    ${_BASHUNIT_COLOR_FAINT}Expected${_BASHUNIT_COLOR_DEFAULT} ${_BASHUNIT_COLOR_BOLD}'%s'${_BASHUNIT_COLOR_DEFAULT}
    ${_BASHUNIT_COLOR_FAINT}%s${_BASHUNIT_COLOR_DEFAULT} ${_BASHUNIT_COLOR_BOLD}'%s'${_BASHUNIT_COLOR_DEFAULT}\n" \
    "${function_name}" "${expected}" "${failure_condition_message}" "${actual}")"

  if [ -n "$extra_key" ]; then
    line="$line$(printf "\

    ${_BASHUNIT_COLOR_FAINT}%s${_BASHUNIT_COLOR_DEFAULT} ${_BASHUNIT_COLOR_BOLD}'%s'${_BASHUNIT_COLOR_DEFAULT}\n" \
      "${extra_key}" "${extra_value}")"
  fi

  bashunit::state::print_line "failed" "$line"
}

function bashunit::console_results::print_failed_snapshot_test() {
  local function_name=$1
  local snapshot_file=$2
  local actual_content=${3-}

  local line
  line="$(printf "${_BASHUNIT_COLOR_FAILED}✗ Failed${_BASHUNIT_COLOR_DEFAULT}: %s
    ${_BASHUNIT_COLOR_FAINT}Expected to match the snapshot${_BASHUNIT_COLOR_DEFAULT}\n" "$function_name")"

  if bashunit::dependencies::has_git; then
    local actual_file="${snapshot_file}.tmp"
    echo "$actual_content" >"$actual_file"

    local git_diff_output
    git_diff_output="$(git diff --no-index --word-diff --color=always \
      "$snapshot_file" "$actual_file" 2>/dev/null |
      tail -n +6 | sed "s/^/    /")"

    line="$line$git_diff_output"
    rm "$actual_file"
  fi

  bashunit::state::print_line "failed_snapshot" "$line"
}

function bashunit::console_results::print_skipped_test() {
  local function_name=$1
  local reason=${2-}

  local line
  line="$(printf "${_BASHUNIT_COLOR_SKIPPED}↷ Skipped${_BASHUNIT_COLOR_DEFAULT}: %s\n" "${function_name}")"

  if [ -n "$reason" ]; then
    line="$line$(printf "${_BASHUNIT_COLOR_FAINT}    %s${_BASHUNIT_COLOR_DEFAULT}\n" "${reason}")"
  fi

  bashunit::state::print_line "skipped" "$line"
}

function bashunit::console_results::print_incomplete_test() {
  local function_name=$1
  local pending=${2-}

  local line
  line="$(printf "${_BASHUNIT_COLOR_INCOMPLETE}✒ Incomplete${_BASHUNIT_COLOR_DEFAULT}: %s\n" "${function_name}")"

  if [ -n "$pending" ]; then
    line="$line$(printf "${_BASHUNIT_COLOR_FAINT}    %s${_BASHUNIT_COLOR_DEFAULT}\n" "${pending}")"
  fi

  bashunit::state::print_line "incomplete" "$line"
}

function bashunit::console_results::print_snapshot_test() {
  local function_name=$1
  local test_name
  test_name=$(bashunit::helper::normalize_test_function_name "$function_name")

  local line
  line="$(printf "${_BASHUNIT_COLOR_SNAPSHOT}✎ Snapshot${_BASHUNIT_COLOR_DEFAULT}: %s\n" "${test_name}")"

  bashunit::state::print_line "snapshot" "$line"
}

function bashunit::console_results::print_risky_test() {
  local test_name=$1
  local duration=${2:-"0"}

  local line
  line=$(printf "%s⚠ Risky%s: %s" "$_BASHUNIT_COLOR_RISKY" "$_BASHUNIT_COLOR_DEFAULT" "$test_name")

  local full_line=$line
  if bashunit::env::is_show_execution_time_enabled; then
    local time_display
    time_display=$(bashunit::console_results::format_duration "$duration")
    full_line="$(printf "%s\n" "$(bashunit::str::rpad "$line" "$time_display")")"
  fi

  bashunit::state::print_line "risky" "$full_line"
}

function bashunit::console_results::print_error_test() {
  local function_name=$1
  local error="$2"
  local raw_output="${3:-}"

  local test_name
  test_name=$(bashunit::helper::normalize_test_function_name "$function_name")

  local line
  line="$(printf "${_BASHUNIT_COLOR_FAILED}✗ Error${_BASHUNIT_COLOR_DEFAULT}: %s
    ${_BASHUNIT_COLOR_FAINT}%s${_BASHUNIT_COLOR_DEFAULT}\n" "${test_name}" "${error}")"

  if [ -n "$raw_output" ] && bashunit::env::is_show_output_on_failure_enabled; then
    line="$line$(printf "    %sOutput:%s\n" "${_BASHUNIT_COLOR_FAINT}" "${_BASHUNIT_COLOR_DEFAULT}")"
    local output_line
    while IFS= read -r output_line; do
      line="$line$(printf "      %s\n" "$output_line")"
    done <<<"$raw_output"
  fi

  bashunit::state::print_line "error" "$line"
}

function bashunit::console_results::print_failing_tests_and_reset() {
  if [ -s "$FAILURES_OUTPUT_PATH" ]; then
    local total_failed
    total_failed=$(bashunit::state::get_tests_failed)

    if bashunit::env::is_simple_output_enabled; then
      printf "\n\n"
    fi

    if [ "$total_failed" -eq 1 ]; then
      echo -e "${_BASHUNIT_COLOR_BOLD}There was 1 failure:${_BASHUNIT_COLOR_DEFAULT}\n"
    else
      echo -e "${_BASHUNIT_COLOR_BOLD}There were $total_failed failures:${_BASHUNIT_COLOR_DEFAULT}\n"
    fi

    sed '${/^$/d;}' "$FAILURES_OUTPUT_PATH" | sed 's/^/|/'
    rm "$FAILURES_OUTPUT_PATH"

    echo ""
  fi
}

function bashunit::console_results::print_skipped_tests_and_reset() {
  if [ -s "$SKIPPED_OUTPUT_PATH" ] && bashunit::env::is_show_skipped_enabled; then
    local total_skipped
    total_skipped=$(bashunit::state::get_tests_skipped)

    if bashunit::env::is_simple_output_enabled; then
      printf "\n"
    fi

    if [ "$total_skipped" -eq 1 ]; then
      echo -e "${_BASHUNIT_COLOR_BOLD}There was 1 skipped test:${_BASHUNIT_COLOR_DEFAULT}\n"
    else
      echo -e "${_BASHUNIT_COLOR_BOLD}There were $total_skipped skipped tests:${_BASHUNIT_COLOR_DEFAULT}\n"
    fi

    tr -d '\r' <"$SKIPPED_OUTPUT_PATH" | sed '/^[[:space:]]*$/d' | sed 's/^/|/'
    rm "$SKIPPED_OUTPUT_PATH"

    echo ""
  fi
}

function bashunit::console_results::print_incomplete_tests_and_reset() {
  if [ -s "$INCOMPLETE_OUTPUT_PATH" ] && bashunit::env::is_show_incomplete_enabled; then
    local total_incomplete
    total_incomplete=$(bashunit::state::get_tests_incomplete)

    if bashunit::env::is_simple_output_enabled; then
      printf "\n"
    fi

    if [ "$total_incomplete" -eq 1 ]; then
      echo -e "${_BASHUNIT_COLOR_BOLD}There was 1 incomplete test:${_BASHUNIT_COLOR_DEFAULT}\n"
    else
      echo -e "${_BASHUNIT_COLOR_BOLD}There were $total_incomplete incomplete tests:${_BASHUNIT_COLOR_DEFAULT}\n"
    fi

    tr -d '\r' <"$INCOMPLETE_OUTPUT_PATH" | sed '/^[[:space:]]*$/d' | sed 's/^/|/'
    rm "$INCOMPLETE_OUTPUT_PATH"

    echo ""
  fi
}

function bashunit::console_results::print_risky_tests_and_reset() {
  if [ -s "$RISKY_OUTPUT_PATH" ]; then
    local total_risky
    total_risky=$(bashunit::state::get_tests_risky)

    if bashunit::env::is_simple_output_enabled; then
      printf "\n"
    fi

    if [ "$total_risky" -eq 1 ]; then
      echo -e "${_BASHUNIT_COLOR_BOLD}There was 1 risky test:${_BASHUNIT_COLOR_DEFAULT}\n"
    else
      echo -e "${_BASHUNIT_COLOR_BOLD}There were $total_risky risky tests:${_BASHUNIT_COLOR_DEFAULT}\n"
    fi

    tr -d '\r' <"$RISKY_OUTPUT_PATH" | sed '/^[[:space:]]*$/d' | sed 's/^/|/'
    rm "$RISKY_OUTPUT_PATH"

    echo ""
  fi
}

# helpers.sh

declare -r BASHUNIT_GIT_REPO="https://github.com/TypedDevs/bashunit"

#
# Walks up the call stack to find the first function that looks like a test function.
# A test function is one that starts with "test_" or "test" (camelCase).
# If no test function is found, falls back to the caller of the assertion function.
#
# @param $1 number Optional fallback depth (default: 2, i.e., the caller of the assertion)
#
# @return string The test function name, or fallback function name
#
function bashunit::helper::find_test_function_name() {
  local fallback_depth="${1:-2}"
  local i
  for ((i = 0; i < ${#FUNCNAME[@]}; i++)); do
    local fn="${FUNCNAME[$i]}"
    # Check if function starts with "test_" or "test" followed by uppercase
    local _re='^test[A-Z]'
    local _is_test=false
    case "$fn" in test_*) _is_test=true ;; esac
    if [ "$_is_test" = true ] || [ "$(echo "$fn" | "$GREP" -cE "$_re" || true)" -gt 0 ]; then
      echo "$fn"
      return
    fi
  done
  # No test function found, use fallback (caller of the assertion)
  # FUNCNAME[0] = bashunit::helper::find_test_function_name
  # FUNCNAME[1] = the assertion function (e.g., assert_same)
  # FUNCNAME[2] = caller of the assertion
  echo "${FUNCNAME[$fallback_depth]:-}"
}

#
# @param $1 string Eg: "test_some_logic_camelCase"
#
# @return string Eg: "Some logic camelCase"
#
function bashunit::helper::normalize_test_function_name() {
  local original_fn_name="${1-}"
  local interpolated_fn_name="${2-}"

  local custom_title
  custom_title="$(bashunit::state::get_test_title)"
  if [ -n "$custom_title" ]; then
    echo "$custom_title"
    return
  fi

  if [ -z "${interpolated_fn_name-}" ]; then
    case "${original_fn_name}" in
    *"::"*)
      local state_interpolated_fn_name
      state_interpolated_fn_name="$(bashunit::state::get_current_test_interpolated_function_name)"

      if [ -n "$state_interpolated_fn_name" ]; then
        interpolated_fn_name="$state_interpolated_fn_name"
      fi
      ;;
    esac
  fi

  if [ -n "${interpolated_fn_name-}" ]; then
    original_fn_name="$interpolated_fn_name"
  fi

  local result

  # Remove the first "test_" prefix, if present
  result="${original_fn_name#test_}"
  # If no "test_" was removed (e.g., "testFoo"), remove the "test" prefix
  if [ "$result" = "$original_fn_name" ]; then
    result="${original_fn_name#test}"
  fi
  # Replace underscores with spaces
  result="${result//_/ }"
  # Capitalize the first letter (bash 3.0 compatible, no subprocess)
  local first_char="${result:0:1}"
  case "$first_char" in
  a) first_char='A' ;; b) first_char='B' ;; c) first_char='C' ;; d) first_char='D' ;;
  e) first_char='E' ;; f) first_char='F' ;; g) first_char='G' ;; h) first_char='H' ;;
  i) first_char='I' ;; j) first_char='J' ;; k) first_char='K' ;; l) first_char='L' ;;
  m) first_char='M' ;; n) first_char='N' ;; o) first_char='O' ;; p) first_char='P' ;;
  q) first_char='Q' ;; r) first_char='R' ;; s) first_char='S' ;; t) first_char='T' ;;
  u) first_char='U' ;; v) first_char='V' ;; w) first_char='W' ;; x) first_char='X' ;;
  y) first_char='Y' ;; z) first_char='Z' ;;
  esac
  result="${first_char}${result:1}"

  echo "$result"
}

function bashunit::helper::escape_single_quotes() {
  local value="$1"
  # shellcheck disable=SC1003
  echo "${value//\'/'\'\\''\'}"
}

function bashunit::helper::interpolate_function_name() {
  local function_name="$1"
  shift
  local -a args
  local args_count=$#
  args=("$@")
  local result="$function_name"

  local i
  for ((i = 0; i < args_count; i++)); do
    local placeholder="::$((i + 1))::"
    # shellcheck disable=SC2155
    local value="$(bashunit::helper::escape_single_quotes "${args[$i]}")"
    value="'$value'"
    result="${result//${placeholder}/${value}}"
  done

  echo "$result"
}

function bashunit::helper::encode_base64() {
  local value="$1"

  # Handle empty string specially - base64 of "" is "", which gets lost in line parsing
  if [ -z "$value" ]; then
    printf '%s' "_BASHUNIT_EMPTY_"
    return
  fi

  if [ "$_BASHUNIT_BASE64_WRAP_FLAG" = true ]; then
    printf '%s' "$value" | base64 -w 0
  elif command -v base64 >/dev/null; then
    printf '%s' "$value" | base64 | tr -d '\n'
  else
    printf '%s' "$value" | openssl enc -base64 -A
  fi
}

function bashunit::helper::decode_base64() {
  local value="$1"

  # Handle empty string marker
  if [ "$value" = "_BASHUNIT_EMPTY_" ]; then
    printf ''
    return
  fi

  if command -v base64 >/dev/null; then
    printf '%s' "$value" | base64 -d
  else
    printf '%s' "$value" | openssl enc -d -base64
  fi
}

function bashunit::helper::check_duplicate_functions() {
  local script="$1"

  # Handle directory changes in set_up_before_script (issue #529)
  if [ ! -f "$script" ] && [ -n "${BASHUNIT_WORKING_DIR:-}" ]; then
    script="$BASHUNIT_WORKING_DIR/$script"
  fi

  local filtered_lines
  filtered_lines=$(grep -E '^[[:space:]]*(function[[:space:]]+)?test[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)\s*\{' "$script")

  local function_names
  function_names=$(echo "$filtered_lines" | awk '{
    for (i=1; i<=NF; i++) {
      if ($i ~ /^test[a-zA-Z_][a-zA-Z0-9_]*\(\)$/) {
        gsub(/\(\)/, "", $i)
        print $i
        break
      }
    }
  }')

  local duplicates
  duplicates=$(echo "$function_names" | sort | uniq -d)
  if [ -n "$duplicates" ]; then
    bashunit::state::set_duplicated_functions_merged "$script" "$duplicates"
    return 1
  fi
  return 0
}

#
# @param $1 string Eg: "prefix"
# @param $2 string Eg: "filter"
# @param $3 array Eg: "[fn1, fn2, prefix_filter_fn3, fn4, ...]"
#
# @return array Eg: "[prefix_filter_fn3, ...]" The filtered functions with prefix
#
function bashunit::helper::get_functions_to_run() {
  local prefix=$1
  local filter=${2/test_/}
  local function_names=$3

  local filtered_functions=""

  local fn
  for fn in $function_names; do
    local _fn_match=false
    case "$fn" in ${prefix}_*${filter}*) _fn_match=true ;; esac
    if [ "$_fn_match" = true ]; then
      local _dup=false
      case "$filtered_functions" in *" $fn"*) _dup=true ;; esac
      if [ "$_dup" = true ]; then
        return 1
      fi
      filtered_functions="$filtered_functions $fn"
    fi
  done

  echo "${filtered_functions# }"
}

#
# @param $1 string Eg: "do_something"
#
function bashunit::helper::execute_function_if_exists() {
  local fn_name="$1"

  if declare -F "$fn_name" >/dev/null 2>&1; then
    "$fn_name"
    return $?
  fi

  return 0
}

#
# @param $1 string Eg: "do_something"
#
function bashunit::helper::unset_if_exists() {
  unset "$1" 2>/dev/null
}

function bashunit::helper::find_files_recursive() {
  ## Remove trailing slash using parameter expansion
  local path="${1%%/}"
  local pattern="${2:-*[tT]est.sh}"

  local alt_pattern=""
  local _re='\[tT\]est\.sh$'
  local _pattern_match=false
  case "$pattern" in *test.sh) _pattern_match=true ;; esac
  if [ "$_pattern_match" = true ] || [ "$(echo "$pattern" | "$GREP" -cE "$_re" || true)" -gt 0 ]; then
    alt_pattern="${pattern%.sh}.bash"
  fi

  local _has_glob=false
  case "$path" in *"*"*) _has_glob=true ;; esac
  if [ "$_has_glob" = true ]; then
    if [ -n "$alt_pattern" ]; then
      eval "find $path -type f \( -name \"$pattern\" -o -name \"$alt_pattern\" \)" | sort -u
    else
      eval "find $path -type f -name \"$pattern\"" | sort -u
    fi
  elif [ -d "$path" ]; then
    if [ -n "$alt_pattern" ]; then
      find "$path" -type f \( -name "$pattern" -o -name "$alt_pattern" \) | sort -u
    else
      find "$path" -type f -name "$pattern" | sort -u
    fi
  else
    echo "$path"
  fi
}

function bashunit::helper::normalize_variable_name() {
  local input_string="$1"
  local normalized_string

  normalized_string="${input_string//[^a-zA-Z0-9_]/_}"

  local _re='^[a-zA-Z_]'
  if [ "$(echo "$normalized_string" | "$GREP" -cE "$_re" || true)" -eq 0 ]; then
    normalized_string="_$normalized_string"
  fi

  echo "$normalized_string"
}

function bashunit::helper::get_provider_data() {
  local function_name="$1"
  local script="$2"

  # Handle directory changes in set_up_before_script (issue #529)
  # If relative path doesn't exist, try with BASHUNIT_WORKING_DIR
  if [ ! -f "$script" ] && [ -n "${BASHUNIT_WORKING_DIR:-}" ]; then
    script="$BASHUNIT_WORKING_DIR/$script"
  fi

  if [ ! -f "$script" ]; then
    return
  fi

  local data_provider_function
  data_provider_function=$(
    # shellcheck disable=SC1087
    grep -B 2 -E "(function[[:space:]]+)?$function_name[[:space:]]*\(\)" "$script" 2>/dev/null |
      sed -nE 's/^[[:space:]]*# *@?data_provider[[:space:]]+//p'
  )

  if [ -n "$data_provider_function" ]; then
    bashunit::helper::execute_function_if_exists "$data_provider_function"
  fi
}

function bashunit::helper::trim() {
  local input_string="$1"
  local trimmed_string

  trimmed_string="${input_string#"${input_string%%[![:space:]]*}"}"
  trimmed_string="${trimmed_string%"${trimmed_string##*[![:space:]]}"}"

  echo "$trimmed_string"
}

function bashunit::helper::get_latest_tag() {
  if ! bashunit::dependencies::has_git; then
    return 1
  fi

  git ls-remote --tags "$BASHUNIT_GIT_REPO" |
    awk '{print $2}' |
    sed 's|^refs/tags/||' |
    grep -v '\^{}' |
    sort -Vr |
    head -n 1
}

function bashunit::helper::find_total_tests() {
  local filter=${1:-}
  shift || true

  if [ $# -eq 0 ]; then
    echo 0
    return
  fi

  local total_count=0
  local file

  for file in "$@"; do
    if [ ! -f "$file" ]; then
      continue
    fi

    local file_count
    file_count=$( (
      # shellcheck source=/dev/null
      source "$file"
      local all_fn_names
      all_fn_names=$(declare -F | awk '{print $3}')
      local filtered_functions
      filtered_functions=$(bashunit::helper::get_functions_to_run "test" "$filter" "$all_fn_names") || true

      local count=0
      local IFS=$' \t\n'
      if [ -n "$filtered_functions" ]; then
        local -a functions_to_run=()
        # shellcheck disable=SC2206
        functions_to_run=($filtered_functions)
        # shellcheck disable=SC2034
        local -a provider_data=()
        local provider_data_count=0
        local fn_name line
        for fn_name in "${functions_to_run[@]+"${functions_to_run[@]}"}"; do
          provider_data=()
          provider_data_count=0
          while IFS=" " read -r line; do
            [ -z "$line" ] && continue
            # shellcheck disable=SC2034
            provider_data[provider_data_count]="$line"
            provider_data_count=$((provider_data_count + 1))
          done <<<"$(bashunit::helper::get_provider_data "$fn_name" "$file")"

          if [ "$provider_data_count" -eq 0 ]; then
            count=$((count + 1))
          else
            count=$((count + provider_data_count))
          fi
        done
      fi

      echo "$count"
    ))

    total_count=$((total_count + file_count))
  done

  echo "$total_count"
}

function bashunit::helper::load_test_files() {
  local filter="${1:-}"
  shift || true
  # Bash 3.0 compatible: use $# after shift to check for files
  local has_files=$#

  if [ "$has_files" -eq 0 ]; then
    if [ -n "${BASHUNIT_DEFAULT_PATH:-}" ]; then
      bashunit::helper::find_files_recursive "$BASHUNIT_DEFAULT_PATH"
    fi
  else
    printf "%s\n" "$@"
  fi
}

function bashunit::helper::load_bench_files() {
  local filter="${1:-}"
  shift || true
  # Bash 3.0 compatible: use $# after shift to check for files
  local has_files=$#

  if [ "$has_files" -eq 0 ]; then
    if [ -n "${BASHUNIT_DEFAULT_PATH:-}" ]; then
      bashunit::helper::find_files_recursive "$BASHUNIT_DEFAULT_PATH" '*[bB]ench.sh'
    fi
  else
    printf "%s\n" "$@"
  fi
}

#
# @param $1 string function name
# @return number line number of the function in the source file
#
function bashunit::helper::get_function_line_number() {
  local fn_name=$1

  shopt -s extdebug
  local line_number
  line_number=$(declare -F "$fn_name" | awk '{print $2}')
  shopt -u extdebug

  echo "$line_number"
}

function bashunit::helper::generate_id() {
  local basename="$1"
  local sanitized_basename
  sanitized_basename="$(bashunit::helper::normalize_variable_name "$basename")"
  if bashunit::env::is_parallel_run_enabled; then
    echo "${sanitized_basename}_$$_$(bashunit::random_str 6)"
  else
    echo "${sanitized_basename}_$$"
  fi
}

#
# Parses a file path that may contain a filter suffix.
# Supports two syntaxes:
#   - path::function_name (filter by function name)
#   - path:line_number (filter by line number)
#
# @param $1 string Eg: "tests/test.sh::test_foo" or "tests/test.sh:123"
#
# @return string Two lines: first is file path, second is filter (or empty)
#
function bashunit::helper::parse_file_path_filter() {
  local input="$1"
  local file_path=""
  local filter=""

  # Check for :: syntax (function name filter)
  case "$input" in *"::"*)
    file_path="${input%%::*}"
    filter="${input#*::}"
    ;;
  *)
    # Check for :number syntax (line number filter)
    local _re='^(.+):([0-9]+)$'
    if [ "$(echo "$input" | "$GREP" -cE "$_re" || true)" -gt 0 ]; then
      file_path=$(echo "$input" | sed -nE 's/^(.+):([0-9]+)$/\1/p')
      local line_number
      line_number=$(echo "$input" | sed -nE 's/^(.+):([0-9]+)$/\2/p')
      # Line number will be resolved to function name later
      filter="__line__:${line_number}"
    else
      file_path="$input"
    fi
    ;;
  esac

  echo "$file_path"
  echo "$filter"
}

#
# Finds the test function that contains a given line number in a file.
#
# @param $1 string File path
# @param $2 number Line number
#
# @return string The function name, or empty if not found
#
function bashunit::helper::find_function_at_line() {
  local file="$1"
  local target_line="$2"

  if [ ! -f "$file" ]; then
    return 1
  fi

  # Find all test function definitions and their line numbers
  local best_match=""
  local best_line=0

  local line_num content
  while IFS=: read -r line_num content; do
    # Extract function name from the line
    local fn_name=""
    local fn_pattern='^[[:space:]]*(function[[:space:]]+)?(test[a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*\(\).*'
    fn_name=$(echo "$content" | sed -nE "s/$fn_pattern/\2/p")

    if [ -n "$fn_name" ] && [ "$line_num" -le "$target_line" ] && [ "$line_num" -gt "$best_line" ]; then
      best_match="$fn_name"
      best_line="$line_num"
    fi
  done < <(grep -n -E '^[[:space:]]*(function[[:space:]]+)?test[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\(\)' "$file")

  echo "$best_match"
}

#
# Extracts @tag annotations for a specific function from a test file.
# Looks for comment lines `# @tag <name>` immediately above the function definition.
#
# @param $1 string Function name
# @param $2 string Script file path
#
# @return string Comma-separated list of tags, or empty if none
#
function bashunit::helper::get_tags_for_function() {
  local function_name="$1"
  local script="$2"

  if [ ! -f "$script" ] && [ -n "${BASHUNIT_WORKING_DIR:-}" ]; then
    script="$BASHUNIT_WORKING_DIR/$script"
  fi

  if [ ! -f "$script" ]; then
    return
  fi

  # Find the line number of the function definition
  local fn_line_num
  fn_line_num=$(grep -n -E "(function[[:space:]]+)?${function_name}[[:space:]]*\(\)" "$script" 2>/dev/null | head -1)
  if [ -z "$fn_line_num" ]; then
    return
  fi
  fn_line_num="${fn_line_num%%:*}"

  # Walk backwards from the line above the function, collecting @tag comments
  local tags=""
  local check_line=$((fn_line_num - 1))
  while [ "$check_line" -ge 1 ]; do
    local content
    content=$(sed -n "${check_line}p" "$script")
    local _re='^[[:space:]]*#[[:space:]]*@tag[[:space:]]'
    if [ "$(echo "$content" | "$GREP" -cE "$_re" || true)" -gt 0 ]; then
      local tag_name
      tag_name=$(echo "$content" | sed -nE 's/^[[:space:]]*#[[:space:]]*@tag[[:space:]]+//p')
      if [ -n "$tag_name" ]; then
        if [ -z "$tags" ]; then
          tags="$tag_name"
        else
          tags="$tags,$tag_name"
        fi
      fi
    elif [ "$(echo "$content" | "$GREP" -cE '^[[:space:]]*#' || true)" -gt 0 ]; then
      # Other comment line, keep walking
      :
    elif [ "$(echo "$content" | "$GREP" -cE '^[[:space:]]*$' || true)" -gt 0 ]; then
      # Empty line, stop looking
      break
    else
      # Non-comment, non-empty line, stop
      break
    fi
    check_line=$((check_line - 1))
  done

  echo "$tags"
}

#
# Checks if a function's tags match the include/exclude filters.
# Include uses OR logic (any match passes).
# Exclude uses OR logic (any match fails).
# Exclude takes precedence over include.
#
# @param $1 string Comma-separated tags for the function
# @param $2 string Comma-separated include tags (empty = no filter)
# @param $3 string Comma-separated exclude tags (empty = no filter)
#
# @return 0 if function should run, 1 if it should be skipped
#
function bashunit::helper::function_matches_tags() {
  local fn_tags="$1"
  local include_tags="$2"
  local exclude_tags="$3"

  # Check exclude tags first (exclude wins over include)
  if [ -n "$exclude_tags" ]; then
    local IFS=','
    local etag
    for etag in $exclude_tags; do
      local check_tag
      for check_tag in $fn_tags; do
        if [ "$check_tag" = "$etag" ]; then
          return 1
        fi
      done
    done
  fi

  # Check include tags (OR logic: any match passes)
  if [ -n "$include_tags" ]; then
    if [ -z "$fn_tags" ]; then
      return 1
    fi
    local IFS=','
    local itag
    for itag in $include_tags; do
      local check_tag
      for check_tag in $fn_tags; do
        if [ "$check_tag" = "$itag" ]; then
          return 0
        fi
      done
    done
    return 1
  fi

  return 0
}

# test_title.sh

function bashunit::set_test_title() {
  bashunit::state::set_test_title "$1"
}

# upgrade.sh

function bashunit::upgrade::upgrade() {
  local script_path
  script_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  local latest_tag
  latest_tag="$(bashunit::helper::get_latest_tag)"

  if [ "$BASHUNIT_VERSION" = "$latest_tag" ]; then
    echo "> You are already on latest version"
    return
  fi

  echo "> Upgrading bashunit to latest version"
  cd "$script_path" || exit

  local url="https://github.com/TypedDevs/bashunit/releases/download/$latest_tag/bashunit"
  if ! bashunit::io::download_to "$url" "bashunit"; then
    echo "Failed to download bashunit"
  fi

  chmod u+x "bashunit"

  echo "> bashunit upgraded successfully to latest version $latest_tag"
}

# assertions.sh


# assert.sh

# Helper to mark assertion as failed and set the guard flag
function bashunit::assert::mark_failed() {
  bashunit::state::add_assertions_failed
  bashunit::state::mark_assertion_failed_in_test
}

# Guard clause to skip assertion if one already failed in test (when stop-on-assertion is enabled)
function bashunit::assert::should_skip() {
  bashunit::env::is_stop_on_assertion_failure_enabled && ((_BASHUNIT_ASSERTION_FAILED_IN_TEST))
}

function bashunit::fail() {
  bashunit::assert::should_skip && return 0

  local message="${1:-${FUNCNAME[1]}}"

  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label
  label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
  bashunit::assert::mark_failed
  bashunit::console_results::print_failure_message "${label}" "$message"
}

function assert_true() {
  bashunit::assert::should_skip && return 0

  local actual="$1"

  # Check for expected literal values first
  case "$actual" in
  "true" | "0")
    bashunit::state::add_assertions_passed
    return
    ;;
  "false" | "1")
    bashunit::handle_bool_assertion_failure "true or 0" "$actual"
    return
    ;;
  esac

  # Run command or eval and check the exit code
  bashunit::run_command_or_eval "$actual"
  local exit_code=$?

  if [ "$exit_code" -ne 0 ]; then
    bashunit::handle_bool_assertion_failure "command or function with zero exit code" "exit code: $exit_code"
  else
    bashunit::state::add_assertions_passed
  fi
}

function assert_false() {
  bashunit::assert::should_skip && return 0

  local actual="$1"

  # Check for expected literal values first
  case "$actual" in
  "false" | "1")
    bashunit::state::add_assertions_passed
    return
    ;;
  "true" | "0")
    bashunit::handle_bool_assertion_failure "false or 1" "$actual"
    return
    ;;
  esac

  # Run command or eval and check the exit code
  bashunit::run_command_or_eval "$actual"
  local exit_code=$?

  if [ "$exit_code" -eq 0 ]; then
    bashunit::handle_bool_assertion_failure "command or function with non-zero exit code" "exit code: $exit_code"
  else
    bashunit::state::add_assertions_passed
  fi
}

function bashunit::run_command_or_eval() {
  local cmd="$1"

  case "$cmd" in
  eval\ * | eval)
    eval "${cmd#eval }" &>/dev/null
    ;;
  *)
    if [ "$(command -v "$cmd" | "$GREP" -cE '^alias' || true)" -gt 0 ]; then
      eval "$cmd" &>/dev/null
    else
      "$cmd" &>/dev/null
    fi
    ;;
  esac
  return $?
}

function bashunit::handle_bool_assertion_failure() {
  local expected="$1"
  local got="$2"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label
  label="$(bashunit::helper::normalize_test_function_name "$test_fn")"

  bashunit::assert::mark_failed
  bashunit::console_results::print_failed_test "$label" "$expected" "but got " "$got"
}

function assert_same() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  if [ "$expected" != "$actual" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "but got " "${actual}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_equals() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  local actual_cleaned
  actual_cleaned=$(bashunit::str::strip_ansi "$actual")
  local expected_cleaned
  expected_cleaned=$(bashunit::str::strip_ansi "$expected")

  if [ "$expected_cleaned" != "$actual_cleaned" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected_cleaned}" "but got " "${actual_cleaned}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_not_equals() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  local actual_cleaned
  actual_cleaned=$(bashunit::str::strip_ansi "$actual")
  local expected_cleaned
  expected_cleaned=$(bashunit::str::strip_ansi "$expected")

  if [ "$expected_cleaned" = "$actual_cleaned" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected_cleaned}" "to not be" "${actual_cleaned}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_empty() {
  bashunit::assert::should_skip && return 0

  local expected="$1"

  if [ "$expected" != "" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "to be empty" "but got " "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_not_empty() {
  bashunit::assert::should_skip && return 0

  local expected="$1"

  if [ "$expected" = "" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "to not be empty" "but got " "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_not_same() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  if [ "$expected" = "$actual" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to not be" "${actual}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_contains() {
  bashunit::assert::should_skip && return 0
  local IFS=$' \t\n'

  local expected="$1"
  local -a actual_arr
  actual_arr=("${@:2}")
  local actual
  actual=$(printf '%s\n' "${actual_arr[@]}")

  case "$actual" in
  *"$expected"*) ;;
  *)
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}"
    return
    ;;
  esac

  bashunit::state::add_assertions_passed
}

function assert_contains_ignore_case() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  # Bash 3.0 compatible: use tr for case-insensitive comparison
  # (shopt nocasematch was introduced in Bash 3.1)
  local expected_lower
  local actual_lower
  expected_lower=$(printf '%s' "$expected" | tr '[:upper:]' '[:lower:]')
  actual_lower=$(printf '%s' "$actual" | tr '[:upper:]' '[:lower:]')

  case "$actual_lower" in
  *"$expected_lower"*) ;;
  *)
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}"
    return
    ;;
  esac

  bashunit::state::add_assertions_passed
}

function assert_not_contains() {
  bashunit::assert::should_skip && return 0
  local IFS=$' \t\n'

  local expected="$1"
  local -a actual_arr
  actual_arr=("${@:2}")
  local actual
  actual=$(printf '%s\n' "${actual_arr[@]}")

  case "$actual" in
  *"$expected"*)
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to not contain" "${expected}"
    return
    ;;
  esac

  bashunit::state::add_assertions_passed
}

function assert_matches() {
  bashunit::assert::should_skip && return 0
  local IFS=$' \t\n'

  local expected="$1"
  local -a actual_arr
  actual_arr=("${@:2}")
  local actual
  actual=$(printf '%s\n' "${actual_arr[@]}")

  if [ "$(printf '%s' "$actual" | "$GREP" -cE "$expected" || true)" -eq 0 ]; then
    # Retry with newlines collapsed for cross-line patterns
    if [ "$(printf '%s' "$actual" | tr '\n' ' ' | "$GREP" -cE "$expected" || true)" -eq 0 ]; then
      local test_fn
      test_fn="$(bashunit::helper::find_test_function_name)"
      local label
      label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
      bashunit::assert::mark_failed
      bashunit::console_results::print_failed_test "${label}" "${actual}" "to match" "${expected}"
      return
    fi
  fi

  bashunit::state::add_assertions_passed
}

function assert_not_matches() {
  bashunit::assert::should_skip && return 0
  local IFS=$' \t\n'

  local expected="$1"
  local -a actual_arr
  actual_arr=("${@:2}")
  local actual
  actual=$(printf '%s\n' "${actual_arr[@]}")

  # Check both line-by-line and with newlines collapsed for cross-line patterns
  if [ "$(printf '%s' "$actual" | "$GREP" -cE "$expected" || true)" -gt 0 ] ||
    [ "$(printf '%s' "$actual" | tr '\n' ' ' | "$GREP" -cE "$expected" || true)" -gt 0 ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to not match" "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_exec() {
  bashunit::assert::should_skip && return 0

  local cmd="$1"
  shift

  local expected_exit=0
  local expected_stdout=""
  local expected_stderr=""
  local check_stdout=false
  local check_stderr=false

  while [ $# -gt 0 ]; do
    case "$1" in
    --exit)
      expected_exit="$2"
      shift 2
      ;;
    --stdout)
      expected_stdout="$2"
      check_stdout=true
      shift 2
      ;;
    --stderr)
      expected_stderr="$2"
      check_stderr=true
      shift 2
      ;;
    *)
      shift
      ;;
    esac
  done

  local stdout_file stderr_file
  stdout_file=$("$MKTEMP")
  stderr_file=$("$MKTEMP")

  eval "$cmd" >"$stdout_file" 2>"$stderr_file"
  local exit_code=$?

  local stdout
  stdout=$(cat "$stdout_file")
  local stderr
  stderr=$(cat "$stderr_file")

  rm -f "$stdout_file" "$stderr_file"

  local expected_desc="exit: $expected_exit"
  local actual_desc="exit: $exit_code"
  local failed=0

  if [ "$exit_code" -ne "$expected_exit" ]; then
    failed=1
  fi

  if $check_stdout; then
    expected_desc="$expected_desc"$'\n'"stdout: $expected_stdout"
    actual_desc="$actual_desc"$'\n'"stdout: $stdout"
    if [ "$stdout" != "$expected_stdout" ]; then
      failed=1
    fi
  fi

  if $check_stderr; then
    expected_desc="$expected_desc"$'\n'"stderr: $expected_stderr"
    actual_desc="$actual_desc"$'\n'"stderr: $stderr"
    if [ "$stderr" != "$expected_stderr" ]; then
      failed=1
    fi
  fi

  if [ "$failed" -eq 1 ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "$label" "$expected_desc" "but got " "$actual_desc"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_exit_code() {
  local actual_exit_code=${3-"$?"} # Capture $? before guard check
  bashunit::assert::should_skip && return 0

  local expected_exit_code="$1"

  if [ "$actual_exit_code" -ne "$expected_exit_code" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual_exit_code}" "to be" "${expected_exit_code}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_successful_code() {
  local actual_exit_code=${3-"$?"} # Capture $? before guard check
  bashunit::assert::should_skip && return 0

  local expected_exit_code=0

  if [ "$actual_exit_code" -ne "$expected_exit_code" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test \
      "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_unsuccessful_code() {
  local actual_exit_code=${3-"$?"} # Capture $? before guard check
  bashunit::assert::should_skip && return 0

  if [ "$actual_exit_code" -eq 0 ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual_exit_code}" "to be non-zero" "but was 0"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_general_error() {
  local actual_exit_code=${3-"$?"} # Capture $? before guard check
  bashunit::assert::should_skip && return 0

  local expected_exit_code=1

  if [ "$actual_exit_code" -ne "$expected_exit_code" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test \
      "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_command_not_found() {
  local actual_exit_code=${3-"$?"} # Capture $? before guard check
  bashunit::assert::should_skip && return 0

  local expected_exit_code=127

  if [ "$actual_exit_code" -ne "$expected_exit_code" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test \
      "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_string_starts_with() {
  bashunit::assert::should_skip && return 0
  local IFS=$' \t\n'

  local expected="$1"
  local -a actual_arr
  actual_arr=("${@:2}")
  local actual
  actual=$(printf '%s\n' "${actual_arr[@]}")

  case "$actual" in
  "$expected"*) ;;
  *)
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to start with" "${expected}"
    return
    ;;
  esac

  bashunit::state::add_assertions_passed
}

function assert_string_not_starts_with() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  case "$actual" in
  "$expected"*)
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to not start with" "${expected}"
    return
    ;;
  esac

  bashunit::state::add_assertions_passed
}

function assert_string_ends_with() {
  bashunit::assert::should_skip && return 0
  local IFS=$' \t\n'

  local expected="$1"
  local -a actual_arr
  actual_arr=("${@:2}")
  local actual
  actual=$(printf '%s\n' "${actual_arr[@]}")

  case "$actual" in
  *"$expected") ;;
  *)
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to end with" "${expected}"
    return
    ;;
  esac

  bashunit::state::add_assertions_passed
}

function assert_string_not_ends_with() {
  bashunit::assert::should_skip && return 0
  local IFS=$' \t\n'

  local expected="$1"
  local -a actual_arr
  actual_arr=("${@:2}")
  local actual
  actual=$(printf '%s\n' "${actual_arr[@]}")

  case "$actual" in
  *"$expected")
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to not end with" "${expected}"
    return
    ;;
  esac

  bashunit::state::add_assertions_passed
}

function assert_less_than() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  if ! [ "$actual" -lt "$expected" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to be less than" "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_less_or_equal_than() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  if ! [ "$actual" -le "$expected" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to be less or equal than" "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_greater_than() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  if ! [ "$actual" -gt "$expected" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to be greater than" "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_greater_or_equal_than() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  if ! [ "$actual" -ge "$expected" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to be greater or equal than" "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_line_count() {
  bashunit::assert::should_skip && return 0
  local IFS=$' \t\n'

  local expected="$1"
  local -a input_arr
  input_arr=("${@:2}")
  local input_str
  input_str=$(printf '%s\n' ${input_arr+"${input_arr[@]}"})

  if [ -z "$input_str" ]; then
    local actual=0
  else
    local actual
    actual=$(echo "$input_str" | wc -l | tr -d '[:blank:]')
    local additional_new_lines
    additional_new_lines=$(grep -o '\\n' <<<"$input_str" | wc -l | tr -d '[:blank:]')
    actual=$((actual + additional_new_lines))
  fi

  if [ "$expected" != "$actual" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"

    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${input_str}" \
      "to contain number of lines equal to" "${expected}" \
      "but found" "${actual}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function bashunit::format_to_regex() {
  local format="$1"
  local regex=""
  local i=0
  local len=${#format}

  while [ $i -lt "$len" ]; do
    local char="${format:$i:1}"
    if [ "$char" = "%" ] && [ $((i + 1)) -lt "$len" ]; then
      local next="${format:$((i + 1)):1}"
      case "$next" in
      d) regex="${regex}[0-9]+" ;;
      i) regex="${regex}[+-]?[0-9]+" ;;
      f) regex="${regex}[+-]?[0-9]*\\.?[0-9]+" ;;
      s) regex="${regex}[^ ]+" ;;
      x) regex="${regex}[0-9a-fA-F]+" ;;
      e) regex="${regex}[+-]?[0-9]*\\.?[0-9]+[eE][+-]?[0-9]+" ;;
      %) regex="${regex}%" ;;
      *)
        regex="${regex}%${next}"
        ;;
      esac
      i=$((i + 2))
    else
      case "$char" in
      . | '*' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '^' | '$')
        regex="${regex}\\${char}"
        ;;
      \\)
        regex="${regex}\\\\"
        ;;
      *)
        regex="${regex}${char}"
        ;;
      esac
      i=$((i + 1))
    fi
  done

  printf '%s' "^${regex}$"
}

function assert_string_matches_format() {
  bashunit::assert::should_skip && return 0

  local format="$1"
  local actual="$2"

  local regex
  regex="$(bashunit::format_to_regex "$format")"

  if [ "$(printf '%s' "$actual" | "$GREP" -cE "$regex" || true)" -eq 0 ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to match format" "${format}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_string_not_matches_format() {
  bashunit::assert::should_skip && return 0

  local format="$1"
  local actual="$2"

  local regex
  regex="$(bashunit::format_to_regex "$format")"

  if [ "$(printf '%s' "$actual" | "$GREP" -cE "$regex" || true)" -gt 0 ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to not match format" "${format}"
    return
  fi

  bashunit::state::add_assertions_passed
}

# assert_arrays.sh

function assert_array_contains() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label
  label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
  shift

  local -a actual
  actual=("$@")

  case "${actual[*]:-}" in
  *"$expected"*)
    ;;
  *)
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual[*]}" "to contain" "${expected}"
    return
    ;;
  esac

  bashunit::state::add_assertions_passed
}

function assert_array_not_contains() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label
  label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
  shift
  local -a actual
  actual=("$@")

  case "${actual[*]:-}" in
  *"$expected"*)
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual[*]}" "to not contain" "${expected}"
    return
    ;;
  esac

  bashunit::state::add_assertions_passed
}

# assert_dates.sh

function bashunit::date::to_epoch() {
  local input="$1"

  # Already epoch seconds (all digits)
  case "$input" in
  *[!0-9]*) ;; # contains non-digits, continue to ISO parsing
  *)
    echo "$input"
    return 0
    ;;
  esac

  # Normalize ISO 8601: replace T with space, strip Z suffix, strip tz offset
  local normalized="$input"
  normalized="${normalized/T/ }"
  normalized="${normalized%Z}"
  # Strip timezone offset (+HHMM or -HHMM) at end for initial parsing
  case "$normalized" in
  *[+-][0-9][0-9][0-9][0-9])
    normalized="${normalized%[+-][0-9][0-9][0-9][0-9]}"
    ;;
  esac

  # Format conversion (GNU vs BSD date)
  local epoch
  # Try GNU date first (-d flag) with original input
  epoch=$(date -d "$input" +%s 2>/dev/null) && {
    echo "$epoch"
    return 0
  }
  # Try GNU date with normalized (space-separated) input
  if [ "$normalized" != "$input" ]; then
    epoch=$(date -d "$normalized" +%s 2>/dev/null) && {
      echo "$epoch"
      return 0
    }
  fi
  # Try BSD date (-j -f flag) with ISO 8601 datetime + timezone offset
  epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S%z" "$input" +%s 2>/dev/null) && {
    echo "$epoch"
    return 0
  }
  # Try BSD date with ISO 8601 datetime format
  epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$input" +%s 2>/dev/null) && {
    echo "$epoch"
    return 0
  }
  # Try BSD date with space-separated datetime format
  epoch=$(date -j -f "%Y-%m-%d %H:%M:%S" "$input" +%s 2>/dev/null) && {
    echo "$epoch"
    return 0
  }
  # Try BSD date with date-only format (append midnight for deterministic results)
  epoch=$(date -j -f "%Y-%m-%d %H:%M:%S" "$input 00:00:00" +%s 2>/dev/null) && {
    echo "$epoch"
    return 0
  }

  # Unsupported format
  echo "$input"
  return 1
}

function assert_date_equals() {
  bashunit::assert::should_skip && return 0

  local expected
  expected="$(bashunit::date::to_epoch "$1")"
  local actual
  actual="$(bashunit::date::to_epoch "$2")"

  if [ "$actual" -ne "$expected" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to be equal to" "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_date_before() {
  bashunit::assert::should_skip && return 0

  local expected
  expected="$(bashunit::date::to_epoch "$1")"
  local actual
  actual="$(bashunit::date::to_epoch "$2")"

  if [ "$actual" -ge "$expected" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to be before" "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_date_after() {
  bashunit::assert::should_skip && return 0

  local expected
  expected="$(bashunit::date::to_epoch "$1")"
  local actual
  actual="$(bashunit::date::to_epoch "$2")"

  if [ "$actual" -le "$expected" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to be after" "${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_date_within_range() {
  bashunit::assert::should_skip && return 0

  local from
  from="$(bashunit::date::to_epoch "$1")"
  local to
  to="$(bashunit::date::to_epoch "$2")"
  local actual
  actual="$(bashunit::date::to_epoch "$3")"

  if [ "$actual" -lt "$from" ] || [ "$actual" -gt "$to" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to be between" "${from} and ${to}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_date_within_delta() {
  bashunit::assert::should_skip && return 0

  local expected
  expected="$(bashunit::date::to_epoch "$1")"
  local actual
  actual="$(bashunit::date::to_epoch "$2")"
  local delta="$3"

  local diff=$((actual - expected))
  if [ "$diff" -lt 0 ]; then
    diff=$((-diff))
  fi

  if [ "$diff" -gt "$delta" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${actual}" "to be within" "${delta} seconds of ${expected}"
    return
  fi

  bashunit::state::add_assertions_passed
}

# assert_duration.sh

function bashunit::duration::measure_ms() {
  local command="$1"

  local start_ns
  start_ns=$(bashunit::clock::now)

  eval "$command" >/dev/null 2>&1

  local end_ns
  end_ns=$(bashunit::clock::now)

  local elapsed_ms
  elapsed_ms=$(bashunit::math::calculate "($end_ns - $start_ns) / 1000000" | awk '{printf "%.0f", $1}')

  echo "$elapsed_ms"
}

function assert_duration() {
  bashunit::assert::should_skip && return 0

  local command="$1"
  local threshold_ms="$2"

  local elapsed_ms
  elapsed_ms=$(bashunit::duration::measure_ms "$command")

  if [ "$elapsed_ms" -gt "$threshold_ms" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to complete within (ms)" "${command}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_duration_less_than() {
  bashunit::assert::should_skip && return 0

  local command="$1"
  local threshold_ms="$2"

  local elapsed_ms
  elapsed_ms=$(bashunit::duration::measure_ms "$command")

  if [ "$elapsed_ms" -ge "$threshold_ms" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to complete within (ms)" "${command}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_duration_greater_than() {
  bashunit::assert::should_skip && return 0

  local command="$1"
  local threshold_ms="$2"

  local elapsed_ms
  elapsed_ms=$(bashunit::duration::measure_ms "$command")

  if [ "$elapsed_ms" -le "$threshold_ms" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${threshold_ms}" "to take at least (ms)" "${command}"
    return
  fi

  bashunit::state::add_assertions_passed
}

# assert_files.sh

function assert_file_exists() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${3:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -f "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to exist but" "do not exist"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_file_not_exists() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${3:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ -f "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to not exist but" "the file exists"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_is_file() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${3:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -f "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to be a file" "but is not a file"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_is_file_empty() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${3:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ -s "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to be empty" "but is not empty"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_files_equals() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  if [ "$(diff -u "$expected" "$actual")" != '' ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed

    bashunit::console_results::print_failed_test "${label}" "${expected}" "Compared" "${actual}" \
      "Diff" "$(diff -u "$expected" "$actual" | sed '1,2d')"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_files_not_equals() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local actual="$2"

  if [ "$(diff -u "$expected" "$actual")" = '' ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed

    bashunit::console_results::print_failed_test "${label}" "${expected}" "Compared" "${actual}" \
      "Diff" "Files are equals"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_file_contains() {
  bashunit::assert::should_skip && return 0

  local file="$1"
  local string="$2"

  if ! grep -F -q "$string" "$file"; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed

    bashunit::console_results::print_failed_test "${label}" "${file}" "to contain" "${string}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_file_not_contains() {
  bashunit::assert::should_skip && return 0

  local file="$1"
  local string="$2"

  if grep -q "$string" "$file"; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed

    bashunit::console_results::print_failed_test "${label}" "${file}" "to not contain" "${string}"
    return
  fi

  bashunit::state::add_assertions_passed
}

# assert_folders.sh

function assert_directory_exists() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -d "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to exist but" "do not exist"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_directory_not_exists() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ -d "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to not exist but" "the directory exists"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_is_directory() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -d "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to be a directory" "but is not a directory"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_is_directory_empty() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -d "$expected" ] || [ -n "$(ls -A "$expected")" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to be empty" "but is not empty"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_is_directory_not_empty() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -d "$expected" ] || [ -z "$(ls -A "$expected")" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to not be empty" "but is empty"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_is_directory_readable() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -d "$expected" ] || [ ! -r "$expected" ] || [ ! -x "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to be readable" "but is not readable"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_is_directory_not_readable() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -d "$expected" ] || { [ -r "$expected" ] && [ -x "$expected" ]; }; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to be not readable" "but is readable"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_is_directory_writable() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -d "$expected" ] || [ ! -w "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to be writable" "but is not writable"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_is_directory_not_writable() {
  bashunit::assert::should_skip && return 0

  local expected="$1"
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label="${2:-$(bashunit::helper::normalize_test_function_name "$test_fn")}"

  if [ ! -d "$expected" ] || [ -w "$expected" ]; then
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "to be not writable" "but is writable"
    return
  fi

  bashunit::state::add_assertions_passed
}

# assert_json.sh

function bashunit::assert_json::require_jq() {
  if ! command -v jq >/dev/null 2>&1; then
    bashunit::skip "jq is required for JSON assertions"
    return 1
  fi
  return 0
}

function assert_json_key_exists() {
  bashunit::assert::should_skip && return 0
  bashunit::assert_json::require_jq || return 0

  local key="$1"
  local json="$2"

  local result
  if ! result=$(printf '%s' "$json" | jq -e "$key" 2>/dev/null) || [ "$result" = "null" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${json}" "to have key" "${key}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_json_contains() {
  bashunit::assert::should_skip && return 0
  bashunit::assert_json::require_jq || return 0

  local key="$1"
  local expected="$2"
  local json="$3"

  local result
  if ! result=$(printf '%s' "$json" | jq -e -r "$key" 2>/dev/null) || [ "$result" = "null" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${json}" "to have key" "${key}"
    return
  fi

  if [ "$result" != "$expected" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "but got " "${result}"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_json_equals() {
  bashunit::assert::should_skip && return 0
  bashunit::assert_json::require_jq || return 0

  local expected="$1"
  local actual="$2"

  local expected_sorted
  expected_sorted=$(printf '%s' "$expected" | jq -S '.' 2>/dev/null)
  local actual_sorted
  actual_sorted=$(printf '%s' "$actual" | jq -S '.' 2>/dev/null)

  if [ "$expected_sorted" != "$actual_sorted" ]; then
    local test_fn
    test_fn="$(bashunit::helper::find_test_function_name)"
    local label
    label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
    bashunit::assert::mark_failed
    bashunit::console_results::print_failed_test "${label}" "${expected}" "but got " "${actual}"
    return
  fi

  bashunit::state::add_assertions_passed
}

# assert_snapshot.sh
# shellcheck disable=SC2155

function assert_match_snapshot() {
  local actual=$(echo -n "$1" | tr -d '\r')
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local snapshot_file=$(bashunit::snapshot::resolve_file "${2:-}" "$test_fn")

  if [ ! -f "$snapshot_file" ]; then
    bashunit::snapshot::initialize "$snapshot_file" "$actual"
    return
  fi

  bashunit::snapshot::compare "$actual" "$snapshot_file" "$test_fn"
}

function assert_match_snapshot_ignore_colors() {
  local actual=$(echo -n "$1" | sed 's/\x1B\[[0-9;]*[mK]//g' | tr -d '\r')
  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local snapshot_file=$(bashunit::snapshot::resolve_file "${2:-}" "$test_fn")

  if [ ! -f "$snapshot_file" ]; then
    bashunit::snapshot::initialize "$snapshot_file" "$actual"
    return
  fi

  bashunit::snapshot::compare "$actual" "$snapshot_file" "$test_fn"
}

function bashunit::snapshot::match_with_placeholder() {
  local actual="$1"
  local snapshot="$2"
  local placeholder="${BASHUNIT_SNAPSHOT_PLACEHOLDER:-::ignore::}"
  local token="__BASHUNIT_IGNORE__"

  local sanitized="${snapshot//$placeholder/$token}"
  local escaped=$(printf '%s' "$sanitized" | sed -e 's/[.[\\^$*+?{}()|]/\\&/g')
  local regex="^${escaped//$token/(.|\\n)*}$"

  if command -v perl >/dev/null 2>&1; then
    echo "$actual" | REGEX="$regex" perl -0 -e '
      my $r = $ENV{REGEX};
      my $input = join("", <STDIN>);
      exit($input =~ /$r/s ? 0 : 1);
    ' && return 0 || return 1
  else
    local fallback=$(printf '%s' "$snapshot" | sed -e "s|$placeholder|.*|g" -e 's/[][\.^$*+?{}|()]/\\&/g')
    fallback="^${fallback}$"
    echo "$actual" | grep -Eq "$fallback" && return 0 || return 1
  fi
}

function bashunit::snapshot::resolve_file() {
  local file_hint="$1"
  local func_name="$2"

  if [ -n "$file_hint" ]; then
    echo "$file_hint"
  else
    local dir="./$(dirname "${BASH_SOURCE[2]}")/snapshots"
    local test_file="$(bashunit::helper::normalize_variable_name "$(basename "${BASH_SOURCE[2]}")")"
    local name="$(bashunit::helper::normalize_variable_name "$func_name").snapshot"
    echo "${dir}/${test_file}.${name}"
  fi
}

function bashunit::snapshot::initialize() {
  local path="$1"
  local content="$2"
  mkdir -p "$(dirname "$path")"
  echo "$content" >"$path"
  bashunit::state::add_assertions_snapshot
}

function bashunit::snapshot::compare() {
  local actual="$1"
  local snapshot_path="$2"
  local func_name="$3"

  local snapshot
  snapshot=$(tr -d '\r' <"$snapshot_path")

  if ! bashunit::snapshot::match_with_placeholder "$actual" "$snapshot"; then
    local label=$(bashunit::helper::normalize_test_function_name "$func_name")
    bashunit::state::add_assertions_failed
    bashunit::console_results::print_failed_snapshot_test "$label" "$snapshot_path" "$actual"
    return 1
  fi

  bashunit::state::add_assertions_passed
}

# skip_todo.sh

function bashunit::skip() {
  local reason=${1-}
  local label
  label="$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")"

  bashunit::console_results::print_skipped_test "${label}" "${reason}"

  bashunit::state::add_assertions_skipped
}

function bashunit::todo() {
  local pending=${1-}
  local label
  label="$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")"

  bashunit::console_results::print_incomplete_test "${label}" "${pending}"

  bashunit::state::add_assertions_incomplete
}

# test_doubles.sh

declare -a _BASHUNIT_MOCKED_FUNCTIONS=()

function bashunit::unmock() {
  local command=$1

  if [ "${#_BASHUNIT_MOCKED_FUNCTIONS[@]}" -eq 0 ]; then
    return
  fi

  local i
  for i in "${!_BASHUNIT_MOCKED_FUNCTIONS[@]}"; do
    if [ "${_BASHUNIT_MOCKED_FUNCTIONS[$i]:-}" = "$command" ]; then
      unset "_BASHUNIT_MOCKED_FUNCTIONS[$i]"
      unset -f "$command"
      local variable
      variable="$(bashunit::helper::normalize_variable_name "$command")"
      local times_file_var="${variable}_times_file"
      local params_file_var="${variable}_params_file"
      [ -f "${!times_file_var-}" ] && rm -f "${!times_file_var}"
      [ -f "${!params_file_var-}" ] && rm -f "${!params_file_var}"
      unset "$times_file_var"
      unset "$params_file_var"
      break
    fi
  done
}

function bashunit::mock() {
  local command=$1
  shift

  if [ $# -gt 0 ]; then
    eval "function $command() { $* \"\$@\"; }"
  else
    eval "function $command() { echo \"$($CAT)\" ; }"
  fi

  export -f "${command?}"

  _BASHUNIT_MOCKED_FUNCTIONS[${#_BASHUNIT_MOCKED_FUNCTIONS[@]}]="$command"
}

function bashunit::spy() {
  local command=$1
  local variable
  variable="$(bashunit::helper::normalize_variable_name "$command")"

  local times_file params_file
  local test_id="${BASHUNIT_CURRENT_TEST_ID:-global}"
  times_file=$(bashunit::temp_file "${test_id}_${variable}_times")
  params_file=$(bashunit::temp_file "${test_id}_${variable}_params")
  echo 0 >"$times_file"
  : >"$params_file"
  export "${variable}_times_file"="$times_file"
  export "${variable}_params_file"="$params_file"

  eval "function $command() {
    local raw=\"\$*\"
    local serialized=\"\"
    local arg
    for arg in \"\$@\"; do
      serialized=\"\$serialized\$(printf '%q' \"\$arg\")$'\\x1f'\"
    done
    serialized=\${serialized%$'\\x1f'}
    printf '%s\x1e%s\\n' \"\$raw\" \"\$serialized\" >> '$params_file'
    local _c
    _c=\$(cat '$times_file' 2>/dev/null || echo 0)
    _c=\$((_c+1))
    echo \"\$_c\" > '$times_file'
  }"

  export -f "${command?}"

  _BASHUNIT_MOCKED_FUNCTIONS[${#_BASHUNIT_MOCKED_FUNCTIONS[@]}]="$command"
}

function assert_have_been_called() {
  local command=$1
  local variable
  variable="$(bashunit::helper::normalize_variable_name "$command")"
  local file_var="${variable}_times_file"
  local times=0
  if [ -f "${!file_var-}" ]; then
    times=$(cat "${!file_var}" 2>/dev/null || echo 0)
  fi
  local label="${2:-$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")}"

  if [ "$times" -eq 0 ]; then
    bashunit::state::add_assertions_failed
    bashunit::console_results::print_failed_test "${label}" "${command}" "to have been called" "once"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_have_been_called_with() {
  local command=$1
  shift

  local index=""
  if [ "$(echo "${!#}" | "$GREP" -cE '^[0-9]+$' || true)" -gt 0 ]; then
    index=${!#}
    set -- "${@:1:$#-1}"
  fi

  local expected="$*"

  local variable
  variable="$(bashunit::helper::normalize_variable_name "$command")"
  local file_var="${variable}_params_file"
  local line=""
  if [ -f "${!file_var-}" ]; then
    if [ -n "$index" ]; then
      line=$(sed -n "${index}p" "${!file_var}" 2>/dev/null || true)
    else
      line=$(tail -n 1 "${!file_var}" 2>/dev/null || true)
    fi
  fi

  local raw
  IFS=$'\x1e' read -r raw _ <<<"$line" || true

  if [ "$expected" != "$raw" ]; then
    bashunit::state::add_assertions_failed
    bashunit::console_results::print_failed_test "$(bashunit::helper::normalize_test_function_name \
      "${FUNCNAME[1]}")" "$expected" "but got " "$raw"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_have_been_called_times() {
  local expected_count=$1
  local command=$2
  local variable
  variable="$(bashunit::helper::normalize_variable_name "$command")"
  local file_var="${variable}_times_file"
  local times=0
  if [ -f "${!file_var-}" ]; then
    times=$(cat "${!file_var}" 2>/dev/null || echo 0)
  fi
  local label="${3:-$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")}"
  if [ "$times" -ne "$expected_count" ]; then
    bashunit::state::add_assertions_failed
    bashunit::console_results::print_failed_test "${label}" "${command}" \
      "to have been called" "${expected_count} times" \
      "actual" "${times} times"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_have_been_called_nth_with() {
  local nth=$1
  local command=$2
  shift 2
  local expected="$*"

  local variable
  variable="$(bashunit::helper::normalize_variable_name "$command")"
  local times_file_var="${variable}_times_file"
  local file_var="${variable}_params_file"
  local label
  label="$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")"

  local times=0
  if [ -f "${!times_file_var-}" ]; then
    times=$(cat "${!times_file_var}" 2>/dev/null || echo 0)
  fi

  if [ "$nth" -gt "$times" ]; then
    bashunit::state::add_assertions_failed
    bashunit::console_results::print_failed_test "${label}" \
      "expected call" "at index ${nth} but" "only called ${times} times"
    return
  fi

  local line=""
  if [ -f "${!file_var-}" ]; then
    line=$(sed -n "${nth}p" "${!file_var}" 2>/dev/null || true)
  fi

  local raw
  IFS=$'\x1e' read -r raw _ <<<"$line" || true

  if [ "$expected" != "$raw" ]; then
    bashunit::state::add_assertions_failed
    bashunit::console_results::print_failed_test "${label}" \
      "$expected" "but got " "$raw"
    return
  fi

  bashunit::state::add_assertions_passed
}

function assert_not_called() {
  local command=$1
  local label="${2:-$(bashunit::helper::normalize_test_function_name "${FUNCNAME[1]}")}"
  assert_have_been_called_times 0 "$command" "$label"
}

# reports.sh
# shellcheck disable=SC2155

_BASHUNIT_REPORTS_TEST_FILES=()
_BASHUNIT_REPORTS_TEST_NAMES=()
_BASHUNIT_REPORTS_TEST_STATUSES=()
_BASHUNIT_REPORTS_TEST_DURATIONS=()
_BASHUNIT_REPORTS_TEST_ASSERTIONS=()
_BASHUNIT_REPORTS_TEST_FAILURES=()

function bashunit::reports::add_test_snapshot() {
  bashunit::reports::add_test "$1" "$2" "$3" "$4" "snapshot"
}

function bashunit::reports::add_test_incomplete() {
  bashunit::reports::add_test "$1" "$2" "$3" "$4" "incomplete"
}

function bashunit::reports::add_test_skipped() {
  bashunit::reports::add_test "$1" "$2" "$3" "$4" "skipped"
}

function bashunit::reports::add_test_passed() {
  bashunit::reports::add_test "$1" "$2" "$3" "$4" "passed"
}

function bashunit::reports::add_test_risky() {
  bashunit::reports::add_test "$1" "$2" "$3" "$4" "risky"
}

function bashunit::reports::add_test_failed() {
  bashunit::reports::add_test "$1" "$2" "$3" "$4" "failed" "$5"
}

function bashunit::reports::add_test() {
  # Skip tracking when no report output is requested
  { [ -n "${BASHUNIT_LOG_JUNIT:-}" ] || [ -n "${BASHUNIT_REPORT_HTML:-}" ]; } || return 0

  local file="$1"
  local test_name="$2"
  local duration="$3"
  local assertions="$4"
  local status="$5"
  local failure_message="${6:-}"

  _BASHUNIT_REPORTS_TEST_FILES[${#_BASHUNIT_REPORTS_TEST_FILES[@]}]="$file"
  _BASHUNIT_REPORTS_TEST_NAMES[${#_BASHUNIT_REPORTS_TEST_NAMES[@]}]="$test_name"
  _BASHUNIT_REPORTS_TEST_STATUSES[${#_BASHUNIT_REPORTS_TEST_STATUSES[@]}]="$status"
  _BASHUNIT_REPORTS_TEST_ASSERTIONS[${#_BASHUNIT_REPORTS_TEST_ASSERTIONS[@]}]="$assertions"
  _BASHUNIT_REPORTS_TEST_DURATIONS[${#_BASHUNIT_REPORTS_TEST_DURATIONS[@]}]="$duration"
  _BASHUNIT_REPORTS_TEST_FAILURES[${#_BASHUNIT_REPORTS_TEST_FAILURES[@]}]="$failure_message"
}

function bashunit::reports::__xml_escape() {
  local text="$1"
  # Strip ANSI escape sequences and control characters invalid in XML 1.0,
  # then escape XML special characters (& first to avoid double-escaping)
  echo "$text" \
    | sed -e 's/\x1b\[[0-9;]*[a-zA-Z]//g' \
    | tr -d '\000-\010\013\014\016-\037' \
    | sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g' -e 's/"/\&quot;/g' -e "s/'/\&apos;/g"
}

function bashunit::reports::generate_junit_xml() {
  local output_file="$1"

  local tests_skipped=$(bashunit::state::get_tests_skipped)
  local tests_incomplete=$(bashunit::state::get_tests_incomplete)
  local tests_failed=$(bashunit::state::get_tests_failed)
  local time_ms=$(bashunit::clock::total_runtime_in_milliseconds)
  local time
  time=$(LC_ALL=C awk -v ms="$time_ms" 'BEGIN {printf "%.3f", ms/1000}')

  {
    echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
    echo "<testsuites>"
    echo "  <testsuite name=\"bashunit\" tests=\"${#_BASHUNIT_REPORTS_TEST_NAMES[@]}\""
    echo "             failures=\"$tests_failed\" errors=\"0\""
    echo "             skipped=\"$(( tests_skipped + tests_incomplete ))\""
    echo "             time=\"$time\">"

    local i
    for i in "${!_BASHUNIT_REPORTS_TEST_NAMES[@]}"; do
      local file="${_BASHUNIT_REPORTS_TEST_FILES[$i]:-}"
      local name="${_BASHUNIT_REPORTS_TEST_NAMES[$i]:-}"
      local status="${_BASHUNIT_REPORTS_TEST_STATUSES[$i]:-}"
      local test_time_ms="${_BASHUNIT_REPORTS_TEST_DURATIONS[$i]:-}"
      local failure_message="${_BASHUNIT_REPORTS_TEST_FAILURES[$i]:-}"
      local test_time
      test_time=$(LC_ALL=C awk -v ms="$test_time_ms" 'BEGIN {printf "%.3f", ms/1000}')

      echo "    <testcase file=\"$file\""
      echo "        name=\"$name\""
      echo "        time=\"$test_time\">"

      # Add failure element for failed tests with actual failure message
      if [ "$status" = "failed" ]; then
        local escaped_message
        escaped_message=$(bashunit::reports::__xml_escape "$failure_message")
        echo "      <failure message=\"Test failed\">$escaped_message</failure>"
      elif [ "$status" = "risky" ]; then
        echo "      <skipped message=\"Test has no assertions (risky)\"/>"
      elif [ "$status" = "skipped" ]; then
        echo "      <skipped/>"
      elif [ "$status" = "incomplete" ]; then
        echo "      <skipped message=\"Test incomplete\"/>"
      fi

      echo "    </testcase>"
    done

    echo "  </testsuite>"
    echo "</testsuites>"
  } >"$output_file"
}

function bashunit::reports::generate_report_html() {
  local output_file="$1"

  local test_passed=$(bashunit::state::get_tests_passed)
  local tests_skipped=$(bashunit::state::get_tests_skipped)
  local tests_incomplete=$(bashunit::state::get_tests_incomplete)
  local tests_snapshot=$(bashunit::state::get_tests_snapshot)
  local tests_failed=$(bashunit::state::get_tests_failed)
  local time=$(bashunit::clock::total_runtime_in_milliseconds)

  # Temporary file to store test cases by file (use mktemp for parallel safety)
  local temp_file
  temp_file=$(mktemp "${TMPDIR:-/tmp}/bashunit-report.XXXXXX")

  # Collect test cases by file
  : >"$temp_file" # Clear temp file if it exists
  local i
  for i in "${!_BASHUNIT_REPORTS_TEST_NAMES[@]}"; do
    local file="${_BASHUNIT_REPORTS_TEST_FILES[$i]:-}"
    local name="${_BASHUNIT_REPORTS_TEST_NAMES[$i]:-}"
    local status="${_BASHUNIT_REPORTS_TEST_STATUSES[$i]:-}"
    local test_time="${_BASHUNIT_REPORTS_TEST_DURATIONS[$i]:-}"
    local test_case="$file|$name|$status|$test_time"

    echo "$test_case" >>"$temp_file"
  done

  {
    echo "<!DOCTYPE html>"
    echo "<html lang=\"en\">"
    echo "<head>"
    echo "  <meta charset=\"UTF-8\">"
    echo "  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
    echo "  <title>Test Report</title>"
    echo "  <style>"
    echo "    body { font-family: Arial, sans-serif; }"
    echo "    table { width: 100%; border-collapse: collapse; }"
    echo "    th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }"
    echo "    th { background-color: #f2f2f2; }"
    echo "    .passed { background-color: #dff0d8; }"
    echo "    .failed { background-color: #f2dede; }"
    echo "    .skipped { background-color: #fcf8e3; }"
    echo "    .incomplete { background-color: #d9edf7; }"
    echo "    .snapshot { background-color: #dfe6e9; }"
    echo "    .risky { background-color: #f5e6f5; }"
    echo "  </style>"
    echo "</head>"
    echo "<body>"
    echo "  <h1>Test Report</h1>"
    echo "  <table>"
    echo "    <thead>"
    echo "      <tr>"
    echo "        <th>Total Tests</th>"
    echo "        <th>Passed</th>"
    echo "        <th>Failed</th>"
    echo "        <th>Incomplete</th>"
    echo "        <th>Skipped</th>"
    echo "        <th>Snapshot</th>"
    echo "        <th>Time (ms)</th>"
    echo "      </tr>"
    echo "    </thead>"
    echo "    <tbody>"
    echo "      <tr>"
    echo "        <td>${#_BASHUNIT_REPORTS_TEST_NAMES[@]}</td>"
    echo "        <td>$test_passed</td>"
    echo "        <td>$tests_failed</td>"
    echo "        <td>$tests_incomplete</td>"
    echo "        <td>$tests_skipped</td>"
    echo "        <td>$tests_snapshot</td>"
    echo "        <td>$time</td>"
    echo "      </tr>"
    echo "    </tbody>"
    echo "  </table>"
    echo "  <p>Time: $time ms</p>"

    # Read the temporary file and group by file
    local current_file=""
    local file name status test_time
    while IFS='|' read -r file name status test_time; do
      if [ "$file" != "$current_file" ]; then
        if [ -n "$current_file" ]; then
          echo "    </tbody>"
          echo "  </table>"
        fi
        echo "  <h2>File: $file</h2>"
        echo "  <table>"
        echo "    <thead>"
        echo "      <tr>"
        echo "        <th>Test Name</th>"
        echo "        <th>Status</th>"
        echo "        <th>Time (ms)</th>"
        echo "      </tr>"
        echo "    </thead>"
        echo "    <tbody>"
        current_file="$file"
      fi
      echo "      <tr class=\"$status\">"
      echo "        <td>$name</td>"
      echo "        <td>$status</td>"
      echo "        <td>$test_time</td>"
      echo "      </tr>"
    done <"$temp_file"

    # Close the last table
    if [ -n "$current_file" ]; then
      echo "    </tbody>"
      echo "  </table>"
    fi

    echo "</body>"
    echo "</html>"
  } >"$output_file"

  # Clean up temporary file
  rm -f "$temp_file"
}

# runner.sh
# shellcheck disable=SC2155

# Pre-compiled regex pattern for parsing test result assertions
if [ -z "${_BASHUNIT_RUNNER_PARSE_RESULT_REGEX+x}" ]; then
  declare -r _BASHUNIT_RUNNER_PARSE_RESULT_REGEX='ASSERTIONS_FAILED=([0-9]*)##'\
'ASSERTIONS_PASSED=([0-9]*)##ASSERTIONS_SKIPPED=([0-9]*)##'\
'ASSERTIONS_INCOMPLETE=([0-9]*)##ASSERTIONS_SNAPSHOT=([0-9]*)##TEST_EXIT_CODE=([0-9]*)'
fi

function bashunit::runner::restore_workdir() {
  cd "$BASHUNIT_WORKING_DIR" 2>/dev/null || true
}

function bashunit::runner::wait_for_job_slot() {
  local max_jobs="${BASHUNIT_PARALLEL_JOBS:-0}"
  if [ "$max_jobs" -le 0 ]; then
    return 0
  fi

  while true; do
    local running_jobs
    running_jobs=$(jobs -r | wc -l)
    if [ "$running_jobs" -lt "$max_jobs" ]; then
      break
    fi
    sleep 0.05
  done
}

function bashunit::runner::load_test_files() {
  local filter=$1
  local tag_filter="${2:-}"
  local exclude_tag_filter="${3:-}"
  shift 3
  local IFS=$' \t\n'
  local -a files
  files=("$@")
  local -a scripts_ids=()
  local scripts_ids_count=0

  # Initialize coverage tracking if enabled
  if bashunit::env::is_coverage_enabled; then
    # 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

  local test_file
  for test_file in "${files[@]+"${files[@]}"}"; do
    if [ ! -f "$test_file" ]; then
      continue
    fi
    unset BASHUNIT_CURRENT_TEST_ID
    export BASHUNIT_CURRENT_SCRIPT_ID="$(bashunit::helper::generate_id "${test_file}")"
    scripts_ids[scripts_ids_count]="${BASHUNIT_CURRENT_SCRIPT_ID}"
    scripts_ids_count=$((scripts_ids_count + 1))
    bashunit::internal_log "Loading file" "$test_file"
    # shellcheck source=/dev/null
    source "$test_file"
    # Update function cache after sourcing new test file
    _BASHUNIT_CACHED_ALL_FUNCTIONS=$(declare -F | awk '{print $3}')
    # Check if any tests match the filter before rendering header or running hooks
    local filtered_functions
    filtered_functions=$(bashunit::helper::get_functions_to_run "test" "$filter" "$_BASHUNIT_CACHED_ALL_FUNCTIONS")
    local functions_for_script
    functions_for_script=$(bashunit::runner::functions_for_script "$test_file" "$filtered_functions")
    # Apply tag filtering to the early check as well
    if [ -n "$tag_filter" ] || [ -n "$exclude_tag_filter" ]; then
      local _early_filtered=""
      local _early_fn
      for _early_fn in $functions_for_script; do
        local _early_tags
        _early_tags=$(bashunit::helper::get_tags_for_function "$_early_fn" "$test_file")
        if bashunit::helper::function_matches_tags "$_early_tags" "$tag_filter" "$exclude_tag_filter"; then
          _early_filtered="$_early_filtered $_early_fn"
        fi
      done
      functions_for_script="${_early_filtered# }"
    fi
    if [ -z "$functions_for_script" ]; then
      bashunit::runner::clean_set_up_and_tear_down_after_script
      bashunit::runner::restore_workdir
      continue
    fi
    # Render header BEFORE set_up_before_script so user sees activity immediately
    bashunit::runner::render_running_file_header "$test_file"
    # Call hook directly (not with `if !`) to preserve errexit behavior inside the hook
    bashunit::runner::run_set_up_before_script "$test_file"
    local setup_before_script_status=$?
    if [ $setup_before_script_status -ne 0 ]; then
      # Count the test functions that couldn't run due to set_up_before_script failure
      # and add them as failed (minus 1 since the hook failure already counts as 1)
      local filtered_functions
      filtered_functions=$(bashunit::helper::get_functions_to_run "test" "$filter" "$_BASHUNIT_CACHED_ALL_FUNCTIONS")
      if [ -n "$filtered_functions" ]; then
        # Bash 3.0 compatible: separate declaration and assignment for arrays
        local functions_to_run
        # shellcheck disable=SC2206
        functions_to_run=($filtered_functions)
        local additional_failures=$((${#functions_to_run[@]} - 1))
        local i
        for ((i = 0; i < additional_failures; i++)); do
          bashunit::state::add_tests_failed
        done
      fi
      bashunit::runner::clean_set_up_and_tear_down_after_script
      if ! bashunit::parallel::is_enabled; then
        bashunit::cleanup_script_temp_files
      fi
      bashunit::runner::restore_workdir
      continue
    fi
    local _cached_fns="$functions_for_script"
    if bashunit::parallel::is_enabled; then
      bashunit::runner::wait_for_job_slot
      bashunit::runner::call_test_functions \
        "$test_file" "$filter" "$tag_filter" \
        "$exclude_tag_filter" "$_cached_fns" 2>/dev/null &
    else
      bashunit::runner::call_test_functions \
        "$test_file" "$filter" "$tag_filter" \
        "$exclude_tag_filter" "$_cached_fns"
    fi
    bashunit::runner::run_tear_down_after_script "$test_file"
    bashunit::runner::clean_set_up_and_tear_down_after_script
    if ! bashunit::parallel::is_enabled; then
      bashunit::cleanup_script_temp_files
    fi
    bashunit::internal_log "Finished file" "$test_file"
    bashunit::runner::restore_workdir
  done

  if bashunit::parallel::is_enabled; then
    wait
    bashunit::runner::spinner &
    local spinner_pid=$!
    bashunit::parallel::aggregate_test_results "$TEMP_DIR_PARALLEL_TEST_SUITE"
    # Kill the spinner once the aggregation finishes
    disown "$spinner_pid" 2>/dev/null || true
    kill "$spinner_pid" 2>/dev/null || true
    printf "\r  \r" # Clear the spinner output
    local script_id
    for script_id in "${scripts_ids[@]+"${scripts_ids[@]}"}"; do
      export BASHUNIT_CURRENT_SCRIPT_ID="${script_id}"
      bashunit::cleanup_script_temp_files
    done
  fi
}

function bashunit::runner::load_bench_files() {
  local filter=$1
  shift
  local IFS=$' \t\n'
  local -a files
  files=("$@")

  local bench_file
  for bench_file in "${files[@]+"${files[@]}"}"; do
    [ -f "$bench_file" ] || continue
    unset BASHUNIT_CURRENT_TEST_ID
    export BASHUNIT_CURRENT_SCRIPT_ID="$(bashunit::helper::generate_id "${bench_file}")"
    # shellcheck source=/dev/null
    source "$bench_file"
    # Update function cache after sourcing new bench file
    _BASHUNIT_CACHED_ALL_FUNCTIONS=$(declare -F | awk '{print $3}')
    # Call hook directly (not with `if !`) to preserve errexit behavior inside the hook
    bashunit::runner::run_set_up_before_script "$bench_file"
    local setup_before_script_status=$?
    if [ $setup_before_script_status -ne 0 ]; then
      # Count the bench functions that couldn't run due to set_up_before_script failure
      # and add them as failed (minus 1 since the hook failure already counts as 1)
      local filtered_functions
      filtered_functions=$(bashunit::helper::get_functions_to_run "bench" "$filter" "$_BASHUNIT_CACHED_ALL_FUNCTIONS")
      if [ -n "$filtered_functions" ]; then
        # Bash 3.0 compatible: separate declaration and assignment for arrays
        local functions_to_run
        # shellcheck disable=SC2206
        functions_to_run=($filtered_functions)
        local additional_failures=$((${#functions_to_run[@]} - 1))
        local i
        for ((i = 0; i < additional_failures; i++)); do
          bashunit::state::add_tests_failed
        done
      fi
      bashunit::runner::clean_set_up_and_tear_down_after_script
      bashunit::cleanup_script_temp_files
      bashunit::runner::restore_workdir
      continue
    fi
    bashunit::runner::call_bench_functions "$bench_file" "$filter"
    bashunit::runner::run_tear_down_after_script "$bench_file"
    bashunit::runner::clean_set_up_and_tear_down_after_script
    bashunit::cleanup_script_temp_files
    bashunit::runner::restore_workdir
  done
}

function bashunit::runner::spinner() {
  # Only show spinner when output is to a terminal
  if [ ! -t 1 ]; then
    # Not a terminal, just wait silently
    while true; do sleep 1; done
    return
  fi

  # Don't show spinner in no-progress mode
  if bashunit::env::is_no_progress_enabled; then
    while true; do sleep 1; done
    return
  fi

  if bashunit::env::is_simple_output_enabled; then
    printf "\n"
  fi

  local delay=0.1
  local spin_chars="|/-\\"
  while true; do
    local i
    for ((i = 0; i < ${#spin_chars}; i++)); do
      printf "\r%s" "${spin_chars:$i:1}"
      sleep "$delay"
    done
  done
}

function bashunit::runner::functions_for_script() {
  local script="$1"
  local all_fn_names="$2"

  # Filter the names down to the ones defined in the script, sort them by line number
  shopt -s extdebug
  # shellcheck disable=SC2086
  declare -F $all_fn_names |
    awk -v s="$script" '$3 == s {print $1" " $2}' |
    sort -k2 -n |
    awk '{print $1}'
  shopt -u extdebug
}

function bashunit::runner::parse_data_provider_args() {
  local input="$1"
  local current_arg=""
  local in_quotes=false
  local had_quotes=false # Track if arg was quoted (to preserve empty quoted strings)
  local quote_char=""
  local escaped=false
  local IFS=$' \t\n'
  local i=0
  local arg=""
  local encoded_arg
  local -a args=()
  local args_count=0

  # Check for shell metacharacters that would break eval or cause globbing
  local has_metachar=false
  local _re1='[^\\][\|\&\;\*]'
  local _re2='^[\|\&\;\*]'
  if [ "$(echo "$input" | "$GREP" -cE "$_re1" || true)" -gt 0 ] \
    || [ "$(echo "$input" | "$GREP" -cE "$_re2" || true)" -gt 0 ]; then
    has_metachar=true
  fi

  # Try eval first (needed for $'...' from printf '%q'), unless metacharacters present
  if [ "$has_metachar" = false ] && eval "args=($input)" 2>/dev/null; then
    # Check if args has elements after eval
    args_count=0
    local _tmp arg
    for _tmp in ${args+"${args[@]}"}; do args_count=$((args_count + 1)); done
    if [ "$args_count" -gt 0 ]; then
      # Successfully parsed - remove sentinel if present
      local last_idx=$((args_count - 1))
      if [ -z "${args[$last_idx]}" ]; then
        unset 'args[$last_idx]'
      fi
      # Print args and return early
      for arg in "${args[@]+"${args[@]}"}"; do
        encoded_arg="$(bashunit::helper::encode_base64 "${arg}")"
        printf '%s\n' "$encoded_arg"
      done
      return
    fi
  fi

  # Fallback: parse args from the input string into an array, respecting quotes and escapes
  local i
  for ((i = 0; i < ${#input}; i++)); do
    local char="${input:$i:1}"
    if [ "$escaped" = true ]; then
      case "$char" in
      t) current_arg="$current_arg"$'\t' ;;
      n) current_arg="$current_arg"$'\n' ;;
      *) current_arg="$current_arg$char" ;;
      esac
      escaped=false
    elif [ "$char" = "\\" ]; then
      escaped=true
    elif [ "$in_quotes" = false ]; then
      case "$char" in
      "$")
        # Handle $'...' syntax
        if [ "${input:$i:2}" = "$'" ]; then
          in_quotes=true
          had_quotes=true
          quote_char="'"
          # Skip the $
          i=$((i + 1))
        else
          current_arg="$current_arg$char"
        fi
        ;;
      "'" | '"')
        in_quotes=true
        had_quotes=true
        quote_char="$char"
        ;;
      " " | $'\t')
        # Add if non-empty OR if was quoted (to preserve empty quoted strings like '')
        if [ -n "$current_arg" ] || [ "$had_quotes" = true ]; then
          args[args_count]="$current_arg"
          args_count=$((args_count + 1))
        fi
        current_arg=""
        had_quotes=false
        ;;
      *)
        current_arg="$current_arg$char"
        ;;
      esac
    elif [ "$char" = "$quote_char" ]; then
      in_quotes=false
      quote_char=""
    else
      current_arg="$current_arg$char"
    fi
  done
  args[args_count]="$current_arg"
  args_count=$((args_count + 1))
  # Remove all trailing empty strings
  while [ "$args_count" -gt 0 ]; do
    local last_idx=$((args_count - 1))
    if [ -z "${args[$last_idx]}" ]; then
      unset 'args[$last_idx]'
      args_count=$((args_count - 1))
    else
      break
    fi
  done
  # Print one arg per line to stdout, base64-encoded to preserve newlines in the data
  local arg
  for arg in ${args+"${args[@]}"}; do
    encoded_arg="$(bashunit::helper::encode_base64 "${arg}")"
    printf '%s\n' "$encoded_arg"
  done
}

function bashunit::runner::call_test_functions() {
  local script="$1"
  local filter="$2"
  local tag_filter="${3:-}"
  local exclude_tag_filter="${4:-}"
  local cached_functions="${5:-}"
  local IFS=$' \t\n'
  local -a functions_to_run=()
  local functions_to_run_count=0

  if [ -n "$cached_functions" ]; then
    # Use pre-computed function list from load_test_files (already tag-filtered)
    local _fn
    for _fn in $cached_functions; do
      [ -z "$_fn" ] && continue
      functions_to_run[functions_to_run_count]="$_fn"
      functions_to_run_count=$((functions_to_run_count + 1))
    done
  else
    # Fallback: compute function list (for direct calls without cache)
    local prefix="test"
    local filtered_functions
    filtered_functions=$(bashunit::helper::get_functions_to_run \
      "$prefix" "$filter" "$_BASHUNIT_CACHED_ALL_FUNCTIONS")
    local _fn
    while IFS= read -r _fn; do
      [ -z "$_fn" ] && continue
      functions_to_run[functions_to_run_count]="$_fn"
      functions_to_run_count=$((functions_to_run_count + 1))
    done < <(bashunit::runner::functions_for_script "$script" "$filtered_functions")

    # Apply tag filtering if --tag or --exclude-tag was specified
    if [ -n "$tag_filter" ] || [ -n "$exclude_tag_filter" ]; then
      local -a tag_filtered=()
      local tag_filtered_count=0
      local _tf_fn
      for _tf_fn in "${functions_to_run[@]+"${functions_to_run[@]}"}"; do
        local fn_tags
        fn_tags=$(bashunit::helper::get_tags_for_function "$_tf_fn" "$script")
        if bashunit::helper::function_matches_tags "$fn_tags" "$tag_filter" "$exclude_tag_filter"; then
          tag_filtered[tag_filtered_count]="$_tf_fn"
          tag_filtered_count=$((tag_filtered_count + 1))
        fi
      done
      functions_to_run=("${tag_filtered[@]+"${tag_filtered[@]}"}")
      functions_to_run_count=$tag_filtered_count
    fi
  fi

  if [ "$functions_to_run_count" -le 0 ]; then
    return
  fi

  bashunit::helper::check_duplicate_functions "$script" || true

  # Check if test file opts out of test-level parallelism
  local allow_test_parallel=true
  if grep -q "^# bashunit: no-parallel-tests" "$script" 2>/dev/null; then
    allow_test_parallel=false
  fi

  local -a provider_data=()
  local provider_data_count=0
  local -a parsed_data=()
  local parsed_data_count=0

  for fn_name in "${functions_to_run[@]+"${functions_to_run[@]}"}"; do
    if bashunit::parallel::is_enabled && bashunit::parallel::must_stop_on_failure; then
      break
    fi

    provider_data=()
    provider_data_count=0
    local line
    while IFS=" " read -r line; do
      [ -z "$line" ] && continue
      provider_data[provider_data_count]="$line"
      provider_data_count=$((provider_data_count + 1))
    done <<<"$(bashunit::helper::get_provider_data "$fn_name" "$script")"

    # No data provider found
    if [ "$provider_data_count" -eq 0 ]; then
      if bashunit::parallel::is_enabled && [ "$allow_test_parallel" = true ]; then
        bashunit::runner::wait_for_job_slot
        bashunit::runner::run_test "$script" "$fn_name" &
      else
        bashunit::runner::run_test "$script" "$fn_name"
      fi
      unset -v fn_name
      continue
    fi

    # Execute the test function for each line of data
    local data
    for data in "${provider_data[@]+"${provider_data[@]}"}"; do
      parsed_data=()
      parsed_data_count=0
      local line
      while IFS= read -r line; do
        [ -z "$line" ] && continue
        parsed_data[parsed_data_count]="$(bashunit::helper::decode_base64 "${line}")"
        parsed_data_count=$((parsed_data_count + 1))
      done <<<"$(bashunit::runner::parse_data_provider_args "$data")"
      if bashunit::parallel::is_enabled && [ "$allow_test_parallel" = true ]; then
        bashunit::runner::wait_for_job_slot
        bashunit::runner::run_test "$script" "$fn_name" ${parsed_data+"${parsed_data[@]}"} &
      else
        bashunit::runner::run_test "$script" "$fn_name" ${parsed_data+"${parsed_data[@]}"}
      fi
    done
    unset -v fn_name
  done

  # Wait for all parallel tests within this file to complete
  if bashunit::parallel::is_enabled && [ "$allow_test_parallel" = true ]; then
    wait
  fi
}

function bashunit::runner::call_bench_functions() {
  local script="$1"
  local filter="$2"
  local IFS=$' \t\n'
  local prefix="bench"

  # Use cached function names for better performance
  local filtered_functions
  filtered_functions=$(bashunit::helper::get_functions_to_run \
    "$prefix" "$filter" "$_BASHUNIT_CACHED_ALL_FUNCTIONS")
  local -a functions_to_run=()
  local functions_to_run_count=0
  local _fn
  while IFS= read -r _fn; do
    [ -z "$_fn" ] && continue
    functions_to_run[functions_to_run_count]="$_fn"
    functions_to_run_count=$((functions_to_run_count + 1))
  done < <(bashunit::runner::functions_for_script "$script" "$filtered_functions")

  if [ "$functions_to_run_count" -le 0 ]; then
    return
  fi

  if bashunit::env::is_bench_mode_enabled; then
    bashunit::runner::render_running_file_header "$script"
  fi

  local fn_name
  for fn_name in "${functions_to_run[@]+"${functions_to_run[@]}"}"; do
    read -r revs its max_ms <<<"$(bashunit::benchmark::parse_annotations "$fn_name" "$script")"
    bashunit::benchmark::run_function "$fn_name" "$revs" "$its" "$max_ms"
    unset -v fn_name
  done

  if ! bashunit::env::is_simple_output_enabled; then
    echo ""
  fi
}

function bashunit::runner::render_running_file_header() {
  local script="$1"
  local force="${2:-false}"

  bashunit::internal_log "Running file" "$script"

  if [ "$force" != true ] && bashunit::parallel::is_enabled; then
    return
  fi

  # Suppress file headers in failures-only mode
  if bashunit::env::is_failures_only_enabled; then
    return
  fi

  # Suppress file headers in no-progress mode
  if bashunit::env::is_no_progress_enabled; then
    return
  fi

  if bashunit::env::is_tap_output_enabled; then
    printf "# %s\n" "$script"
  elif ! bashunit::env::is_simple_output_enabled; then
    if bashunit::env::is_verbose_enabled; then
      printf "\n${_BASHUNIT_COLOR_BOLD}%s${_BASHUNIT_COLOR_DEFAULT}\n" "Running $script"
    else
      printf "${_BASHUNIT_COLOR_BOLD}%s${_BASHUNIT_COLOR_DEFAULT}\n" "Running $script"
    fi
  elif bashunit::env::is_verbose_enabled; then
    printf "\n\n${_BASHUNIT_COLOR_BOLD}%s${_BASHUNIT_COLOR_DEFAULT}" "Running $script"
  fi
}

function bashunit::runner::run_test() {
  local start_time
  start_time=$(bashunit::clock::now)

  local test_file="$1"
  shift
  local fn_name="$1"
  shift

  bashunit::internal_log "Running test" "$fn_name" "$*"
  # Export a unique test identifier so that test doubles can
  # create temporary files scoped per test run. This prevents
  # race conditions when running tests in parallel.
  export BASHUNIT_CURRENT_TEST_ID="$(bashunit::helper::generate_id "$fn_name")"
  # Export current test file and function for coverage tracking (only when coverage enabled)
  if bashunit::env::is_coverage_enabled; then
    export _BASHUNIT_COVERAGE_CURRENT_TEST_FILE="$test_file"
    export _BASHUNIT_COVERAGE_CURRENT_TEST_FN="$fn_name"
  fi

  bashunit::state::reset_test_title

  local interpolated_fn_name="$(bashunit::helper::interpolate_function_name "$fn_name" "$@")"
  if [ "$interpolated_fn_name" != "$fn_name" ]; then
    bashunit::state::set_current_test_interpolated_function_name "$interpolated_fn_name"
  else
    bashunit::state::reset_current_test_interpolated_function_name
  fi
  local current_assertions_failed="$_BASHUNIT_ASSERTIONS_FAILED"
  local current_assertions_snapshot="$_BASHUNIT_ASSERTIONS_SNAPSHOT"
  local current_assertions_incomplete="$_BASHUNIT_ASSERTIONS_INCOMPLETE"
  local current_assertions_skipped="$_BASHUNIT_ASSERTIONS_SKIPPED"

  # (FD = File Descriptor)
  # Duplicate the current std-output (FD 1) and assigns it to FD 3.
  # This means that FD 3 now points to wherever the std-output was pointing.
  exec 3>&1

  local test_execution_result=$(
    # Save subshell stdout to FD 5 so the EXIT trap can restore it.
    # When set -e kills the subshell during a redirected block in
    # execute_test_hook, the redirect leaks into the EXIT trap,
    # causing export_subshell_context output to be lost.
    exec 5>&1
    # shellcheck disable=SC2064
    trap "exit_code=\$?; bashunit::runner::cleanup_on_exit \"$test_file\" \"\$exit_code\"" EXIT
    bashunit::state::initialize_assertions_count

    # Source login shell profiles if enabled
    if bashunit::env::is_login_shell_enabled; then
      # shellcheck disable=SC1091
      [ -f /etc/profile ] && source /etc/profile 2>/dev/null || true
      # shellcheck disable=SC1090
      [ -f ~/.bash_profile ] && source ~/.bash_profile 2>/dev/null || true
      # shellcheck disable=SC1090
      [ -f ~/.bash_login ] && source ~/.bash_login 2>/dev/null || true
      # shellcheck disable=SC1090
      [ -f ~/.profile ] && source ~/.profile 2>/dev/null || true
    fi

    # Enable coverage tracking early to include set_up/tear_down hooks
    if bashunit::env::is_coverage_enabled; then
      bashunit::coverage::enable_trap
    fi

    # Run set_up and capture exit code without || to preserve errexit behavior
    # shellcheck disable=SC2030
    _BASHUNIT_SETUP_COMPLETED=false
    local setup_exit_code=0
    bashunit::runner::run_set_up "$test_file"
    setup_exit_code=$?
    _BASHUNIT_SETUP_COMPLETED=true
    if [ $setup_exit_code -ne 0 ]; then
      exit $setup_exit_code
    fi

    # Apply shell mode setting for test execution
    if bashunit::env::is_strict_mode_enabled; then
      set -euo pipefail
    else
      set +euo pipefail
    fi

    # 2>&1: Redirects the std-error (FD 2) to the std-output (FD 1).
    # points to the original std-output.
    "$fn_name" "$@" 2>&1

  )

  # Closes FD 3, which was used temporarily to hold the original stdout.
  exec 3>&-

  local end_time=$(bashunit::clock::now)
  local duration_ns=$((end_time - start_time))
  local duration=$((duration_ns / 1000000))

  if bashunit::env::is_verbose_enabled; then
    if bashunit::env::is_simple_output_enabled; then
      echo ""
    fi

    printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '='
    printf "%s\n" "File:     $test_file"
    printf "%s\n" "Function: $fn_name"
    printf "%s\n" "Duration: $duration ms"
    local raw_text=${test_execution_result%%##ASSERTIONS_*}
    [ -n "$raw_text" ] && printf "%s" "Raw text: ${test_execution_result%%##ASSERTIONS_*}"
    printf "%s\n" "##ASSERTIONS_${test_execution_result#*##ASSERTIONS_}"
    printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '-'
  fi

  local subshell_output=$(bashunit::runner::decode_subshell_output "$test_execution_result")

  if [ -n "$subshell_output" ]; then
    # Formatted as "[type]line" @see `bashunit::state::print_line()`
    local type="${subshell_output%%]*}" # Remove everything after "]"
    type="${type#[}"                    # Remove the leading "["
    local line="${subshell_output#*]}"  # Remove everything before and including "]"

    # Replace [type] with a newline to split the messages
    line=${line//\[failed\]/$'\n'}     # Replace [failed] with newline
    line=${line//\[skipped\]/$'\n'}    # Replace [skipped] with newline
    line=${line//\[incomplete\]/$'\n'} # Replace [incomplete] with newline

    if ! bashunit::env::is_failures_only_enabled; then
      bashunit::state::print_line "$type" "$line"
    fi

    subshell_output=$line
  fi

  local runtime_output="${test_execution_result%%##ASSERTIONS_*}"

  local runtime_error=""
  local error=""
  for error in "command not found" "unbound variable" "permission denied" \
    "no such file or directory" "syntax error" "bad substitution" \
    "division by 0" "cannot allocate memory" "bad file descriptor" \
    "segmentation fault" "illegal option" "argument list too long" \
    "readonly variable" "missing keyword" "killed" \
    "cannot execute binary file" "invalid arithmetic operator" \
    "ambiguous redirect" "integer expression expected" \
    "too many arguments" "value too great" \
    "not a valid identifier" "unexpected EOF"; do
    case "$runtime_output" in
    *"$error"*)
      runtime_error="${runtime_output#*: }"  # Remove everything up to and including ": "
      runtime_error=${runtime_error//$'\n'/} # Remove all newlines using parameter expansion
      break
      ;;
    esac
  done

  bashunit::runner::parse_result "$fn_name" "$test_execution_result" "$@"

  local test_exit_code="$_BASHUNIT_TEST_EXIT_CODE"

  # Extract assertion counts directly via parameter expansion
  # instead of spawning grep subprocesses
  local _te_failed="${test_execution_result##*##ASSERTIONS_FAILED=}"
  _te_failed="${_te_failed%%##*}"
  local _te_passed="${test_execution_result##*##ASSERTIONS_PASSED=}"
  _te_passed="${_te_passed%%##*}"
  local _te_skipped="${test_execution_result##*##ASSERTIONS_SKIPPED=}"
  _te_skipped="${_te_skipped%%##*}"
  local _te_incomplete="${test_execution_result##*##ASSERTIONS_INCOMPLETE=}"
  _te_incomplete="${_te_incomplete%%##*}"
  local _te_snapshot="${test_execution_result##*##ASSERTIONS_SNAPSHOT=}"
  _te_snapshot="${_te_snapshot%%##*}"
  local total_assertions=$(( \
    ${_te_failed:-0} + ${_te_passed:-0} + ${_te_skipped:-0} + \
    ${_te_incomplete:-0} + ${_te_snapshot:-0} \
  ))

  local encoded_test_title
  encoded_test_title="${test_execution_result##*##TEST_TITLE=}"
  encoded_test_title="${encoded_test_title%%##*}"
  local test_title=""
  [ -n "$encoded_test_title" ] && test_title="$(bashunit::helper::decode_base64 "$encoded_test_title")"

  local encoded_hook_failure
  encoded_hook_failure="${test_execution_result##*##TEST_HOOK_FAILURE=}"
  encoded_hook_failure="${encoded_hook_failure%%##*}"
  local hook_failure=""
  if [ "$encoded_hook_failure" != "$test_execution_result" ]; then
    hook_failure="$encoded_hook_failure"
  fi

  local encoded_hook_message
  encoded_hook_message="${test_execution_result##*##TEST_HOOK_MESSAGE=}"
  encoded_hook_message="${encoded_hook_message%%##*}"
  local hook_message=""
  if [ -n "$encoded_hook_message" ]; then
    hook_message="$(bashunit::helper::decode_base64 "$encoded_hook_message")"
  fi

  bashunit::set_test_title "$test_title"
  local label
  label="$(bashunit::helper::normalize_test_function_name "$fn_name" "$interpolated_fn_name")"
  bashunit::state::reset_test_title
  bashunit::state::reset_current_test_interpolated_function_name

  local failure_label="$label"
  local failure_function="$fn_name"
  if [ -n "$hook_failure" ]; then
    failure_label="$(bashunit::helper::normalize_test_function_name "$hook_failure")"
    failure_function="$hook_failure"
  fi

  if [ -n "$runtime_error" ] || [ "$test_exit_code" -ne 0 ]; then
    bashunit::state::add_tests_failed
    local error_message="$runtime_error"
    if [ -n "$hook_failure" ] && [ -n "$hook_message" ]; then
      error_message="$hook_message"
    elif [ -z "$error_message" ] && [ -n "$hook_message" ]; then
      error_message="$hook_message"
    fi
    bashunit::console_results::print_error_test "$failure_function" "$error_message" "$runtime_output"
    bashunit::reports::add_test_failed "$test_file" "$failure_label" "$duration" "$total_assertions" "$error_message"
    bashunit::runner::write_failure_result_output "$test_file" "$failure_function" "$error_message" "$runtime_output"
    bashunit::internal_log "Test error" "$failure_label" "$error_message"
    return
  fi

  if [ "$current_assertions_failed" != "$_BASHUNIT_ASSERTIONS_FAILED" ]; then
    bashunit::state::add_tests_failed
    bashunit::reports::add_test_failed "$test_file" "$label" "$duration" "$total_assertions" "$subshell_output"
    bashunit::runner::write_failure_result_output "$test_file" "$fn_name" "$subshell_output"

    bashunit::internal_log "Test failed" "$label"

    if bashunit::env::is_stop_on_failure_enabled; then
      if bashunit::parallel::is_enabled; then
        bashunit::parallel::mark_stop_on_failure
      else
        exit "$EXIT_CODE_STOP_ON_FAILURE"
      fi
    fi
    return
  fi

  if [ "$current_assertions_snapshot" != "$_BASHUNIT_ASSERTIONS_SNAPSHOT" ]; then
    bashunit::state::add_tests_snapshot
    # In failures-only mode, suppress snapshot test output
    if ! bashunit::env::is_failures_only_enabled; then
      bashunit::console_results::print_snapshot_test "$label"
    fi
    bashunit::reports::add_test_snapshot "$test_file" "$label" "$duration" "$total_assertions"
    bashunit::internal_log "Test snapshot" "$label"
    return
  fi

  if [ "$current_assertions_incomplete" != "$_BASHUNIT_ASSERTIONS_INCOMPLETE" ]; then
    bashunit::state::add_tests_incomplete
    bashunit::reports::add_test_incomplete "$test_file" "$label" "$duration" "$total_assertions"
    bashunit::runner::write_incomplete_result_output "$test_file" "$fn_name" "$subshell_output"
    bashunit::internal_log "Test incomplete" "$label"
    return
  fi

  if [ "$current_assertions_skipped" != "$_BASHUNIT_ASSERTIONS_SKIPPED" ]; then
    bashunit::state::add_tests_skipped
    bashunit::reports::add_test_skipped "$test_file" "$label" "$duration" "$total_assertions"
    bashunit::runner::write_skipped_result_output "$test_file" "$fn_name" "$subshell_output"
    bashunit::internal_log "Test skipped" "$label"
    return
  fi

  # Check for risky test (zero assertions)
  if [ "$total_assertions" -eq 0 ]; then
    bashunit::state::add_tests_risky
    if ! bashunit::env::is_failures_only_enabled; then
      bashunit::console_results::print_risky_test "${label}" "$duration"
    fi
    bashunit::reports::add_test_risky "$test_file" "$label" "$duration" "$total_assertions"
    bashunit::runner::write_risky_result_output "$test_file" "$fn_name"
    bashunit::internal_log "Test risky" "$label"
    return
  fi

  # In failures-only mode, suppress successful test output
  if ! bashunit::env::is_failures_only_enabled; then
    if [ "$fn_name" = "$interpolated_fn_name" ]; then
      bashunit::console_results::print_successful_test "${label}" "$duration" "$@"
    else
      bashunit::console_results::print_successful_test "${label}" "$duration"
    fi
  fi
  bashunit::state::add_tests_passed
  bashunit::reports::add_test_passed "$test_file" "$label" "$duration" "$total_assertions"
  bashunit::internal_log "Test passed" "$label"
}

function bashunit::runner::cleanup_on_exit() {
  local test_file="$1"
  local exit_code="$2"

  # Disable coverage trap before cleanup to avoid interference
  if bashunit::env::is_coverage_enabled; then
    bashunit::coverage::disable_trap
  fi

  set +e

  # Detect unexpected subshell exit during set_up (Issue #611).
  # When 'source' of a non-existent file fails under set -eE, the ERR trap
  # does not fire. On macOS Bash 3.2, $? is 0 in the EXIT trap; on Linux
  # Bash 5.x, $? is 1. In both cases the hook failure is not recorded.
  # Additionally, the stdout redirect from execute_test_hook leaks into the
  # EXIT trap. Restore stdout from saved FD 5 so export_subshell_context
  # output reaches test_execution_result.
  # shellcheck disable=SC2031
  if [ "${_BASHUNIT_SETUP_COMPLETED:-true}" != "true" ]; then
    exec 1>&5
    if [ "$exit_code" -eq 0 ]; then
      exit_code=1
    fi
    if [ -z "${_BASHUNIT_TEST_HOOK_FAILURE:-}" ]; then
      bashunit::state::set_test_hook_failure "set_up"
      bashunit::state::set_test_hook_message "Hook 'set_up' failed unexpectedly (e.g., source of non-existent file)"
    fi
  fi

  # Don't use || here - it disables ERR trap in the entire call chain
  bashunit::runner::run_tear_down "$test_file"
  local teardown_status=$?
  bashunit::runner::clear_mocks
  bashunit::cleanup_testcase_temp_files

  if [ $teardown_status -ne 0 ]; then
    bashunit::state::set_test_exit_code "$teardown_status"
  else
    bashunit::state::set_test_exit_code "$exit_code"
  fi

  bashunit::state::export_subshell_context
}

function bashunit::runner::decode_subshell_output() {
  local test_execution_result="$1"

  local test_output_base64="${test_execution_result##*##TEST_OUTPUT=}"
  test_output_base64="${test_output_base64%%##*}"
  bashunit::helper::decode_base64 "$test_output_base64"
}

function bashunit::runner::parse_result() {
  local fn_name=$1
  shift
  local execution_result=$1
  shift
  local IFS=$' \t\n'
  local -a args
  args=("$@")

  if bashunit::parallel::is_enabled; then
    bashunit::runner::parse_result_parallel "$fn_name" "$execution_result" ${args+"${args[@]}"}
  else
    bashunit::runner::parse_result_sync "$fn_name" "$execution_result"
  fi
}

function bashunit::runner::parse_result_parallel() {
  local fn_name=$1
  shift
  local execution_result=$1
  shift
  local IFS=$' \t\n'
  local -a args
  args=("$@")

  local test_suite_dir="${TEMP_DIR_PARALLEL_TEST_SUITE}/$(basename "$test_file" .sh)"
  mkdir -p "$test_suite_dir"

  local sanitized_args
  sanitized_args=$(echo "${args[*]+"${args[*]}"}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-|-$//')
  local template
  if [ -z "$sanitized_args" ]; then
    template="${fn_name}.XXXXXX"
  else
    template="${fn_name}-${sanitized_args}.XXXXXX"
  fi

  local unique_test_result_file
  if unique_test_result_file=$("$MKTEMP" -p "$test_suite_dir" "$template" 2>/dev/null); then
    true
  else
    unique_test_result_file=$("$MKTEMP" "$test_suite_dir/$template")
  fi
  mv "$unique_test_result_file" "${unique_test_result_file}.result"
  unique_test_result_file="${unique_test_result_file}.result"

  bashunit::internal_log "[PARA]" "fn_name:$fn_name" "execution_result:$execution_result"

  bashunit::runner::parse_result_sync "$fn_name" "$execution_result"

  echo "$execution_result" >"$unique_test_result_file"
}

# shellcheck disable=SC2295
function bashunit::runner::parse_result_sync() {
  local fn_name=$1
  local execution_result=$2

  local result_line
  result_line="${execution_result##*$'\n'}"

  local assertions_failed=0
  local assertions_passed=0
  local assertions_skipped=0
  local assertions_incomplete=0
  local assertions_snapshot=0
  local test_exit_code=0

  # Extract values using sed instead of BASH_REMATCH for Bash 3.0+ compatibility
  # shellcheck disable=SC2001
  if [ "$(echo "$result_line" | "$GREP" -cE 'ASSERTIONS_FAILED=[0-9]*##ASSERTIONS_PASSED=[0-9]*' || true)" -gt 0 ]; then
    assertions_failed=$(echo "$result_line" | sed 's/.*ASSERTIONS_FAILED=\([0-9]*\)##.*/\1/')
    assertions_passed=$(echo "$result_line" | sed 's/.*ASSERTIONS_PASSED=\([0-9]*\)##.*/\1/')
    assertions_skipped=$(echo "$result_line" | sed 's/.*ASSERTIONS_SKIPPED=\([0-9]*\)##.*/\1/')
    assertions_incomplete=$(echo "$result_line" | sed 's/.*ASSERTIONS_INCOMPLETE=\([0-9]*\)##.*/\1/')
    assertions_snapshot=$(echo "$result_line" | sed 's/.*ASSERTIONS_SNAPSHOT=\([0-9]*\)##.*/\1/')
    test_exit_code=$(echo "$result_line" | sed 's/.*TEST_EXIT_CODE=\([0-9]*\).*/\1/')
  fi

  bashunit::internal_log "[SYNC]" "fn_name:$fn_name" "execution_result:$execution_result"

  _BASHUNIT_ASSERTIONS_PASSED=$((_BASHUNIT_ASSERTIONS_PASSED + assertions_passed))
  _BASHUNIT_ASSERTIONS_FAILED=$((_BASHUNIT_ASSERTIONS_FAILED + assertions_failed))
  _BASHUNIT_ASSERTIONS_SKIPPED=$((_BASHUNIT_ASSERTIONS_SKIPPED + assertions_skipped))
  _BASHUNIT_ASSERTIONS_INCOMPLETE=$((_BASHUNIT_ASSERTIONS_INCOMPLETE + assertions_incomplete))
  _BASHUNIT_ASSERTIONS_SNAPSHOT=$((_BASHUNIT_ASSERTIONS_SNAPSHOT + assertions_snapshot))
  _BASHUNIT_TEST_EXIT_CODE=$((_BASHUNIT_TEST_EXIT_CODE + test_exit_code))

  bashunit::internal_log "result_summary" \
    "failed:$assertions_failed" \
    "passed:$assertions_passed" \
    "skipped:$assertions_skipped" \
    "incomplete:$assertions_incomplete" \
    "snapshot:$assertions_snapshot" \
    "exit_code:$test_exit_code"
}

function bashunit::runner::write_failure_result_output() {
  local test_file=$1
  local fn_name=$2
  local error_msg=$3
  local raw_output="${4:-}"

  local line_number
  line_number=$(bashunit::helper::get_function_line_number "$fn_name")

  local test_nr="*"
  if ! bashunit::parallel::is_enabled; then
    test_nr=$(bashunit::state::get_tests_failed)
  fi

  local output_section=""
  if [ -n "$raw_output" ] && bashunit::env::is_show_output_on_failure_enabled; then
    output_section="\n    Output:\n$raw_output"
  fi

  local source_context=""
  if [ -n "$line_number" ] && [ -f "$test_file" ]; then
    source_context=$(bashunit::runner::get_failure_source_context \
      "$test_file" "$line_number")
  fi

  echo -e "$test_nr) $test_file:$line_number\n$error_msg$output_section$source_context" \
    >>"$FAILURES_OUTPUT_PATH"
}

function bashunit::runner::get_failure_source_context() {
  local file=$1
  local fn_line=$2

  local end_line start_line
  end_line=$(wc -l <"$file")
  start_line=$((fn_line + 1))

  local line_text line_num assert_lines=""
  line_num=$start_line
  while [ "$line_num" -le "$end_line" ]; do
    line_text=$(sed -n "${line_num}p" "$file")
    # Stop at the closing brace of the function
    if [ "$(echo "$line_text" | "$GREP" -cE '^[[:space:]]*\}[[:space:]]*$' || true)" -gt 0 ]; then
      break
    fi
    # Collect lines containing assert calls
    case "$line_text" in
    *assert_* | *assert\ *)
      local trimmed="${line_text#"${line_text%%[![:space:]]*}"}"
      assert_lines="${assert_lines}\n    ${_BASHUNIT_COLOR_FAINT}${line_num}:${_BASHUNIT_COLOR_DEFAULT} ${trimmed}"
      ;;
    esac
    line_num=$((line_num + 1))
  done

  if [ -n "$assert_lines" ]; then
    echo -e "\n    ${_BASHUNIT_COLOR_FAINT}Source:${_BASHUNIT_COLOR_DEFAULT}${assert_lines}"
  fi
}

function bashunit::runner::write_skipped_result_output() {
  local test_file=$1
  local fn_name=$2
  local output_msg=$3

  local line_number
  line_number=$(bashunit::helper::get_function_line_number "$fn_name")

  local test_nr="*"
  if ! bashunit::parallel::is_enabled; then
    test_nr=$(bashunit::state::get_tests_skipped)
  fi

  echo -e "$test_nr) $test_file:$line_number\n$output_msg" >>"$SKIPPED_OUTPUT_PATH"
}

function bashunit::runner::write_incomplete_result_output() {
  local test_file=$1
  local fn_name=$2
  local output_msg=$3

  local line_number
  line_number=$(bashunit::helper::get_function_line_number "$fn_name")

  local test_nr="*"
  if ! bashunit::parallel::is_enabled; then
    test_nr=$(bashunit::state::get_tests_incomplete)
  fi

  echo -e "$test_nr) $test_file:$line_number\n$output_msg" >>"$INCOMPLETE_OUTPUT_PATH"
}

function bashunit::runner::write_risky_result_output() {
  local test_file=$1
  local fn_name=$2

  local line_number
  line_number=$(bashunit::helper::get_function_line_number "$fn_name")

  local test_nr="*"
  if ! bashunit::parallel::is_enabled; then
    test_nr=$(bashunit::state::get_tests_risky)
  fi

  echo -e "$test_nr) $test_file:$line_number\nTest has no assertions (risky)" >>"$RISKY_OUTPUT_PATH"
}

function bashunit::runner::record_file_hook_failure() {
  local hook_name="$1"
  local test_file="$2"
  local hook_output="$3"
  local status="$4"
  local render_header="${5:-false}"

  if [ "$render_header" = true ]; then
    bashunit::runner::render_running_file_header "$test_file" true
  fi

  if [ -z "$hook_output" ]; then
    hook_output="Hook '$hook_name' failed with exit code $status"
  fi

  bashunit::state::add_tests_failed
  bashunit::console_results::print_error_test "$hook_name" "$hook_output"
  local _normalized_hook
  _normalized_hook="$(bashunit::helper::normalize_test_function_name "$hook_name")"
  bashunit::reports::add_test_failed "$test_file" "$_normalized_hook" 0 0 "$hook_output"
  bashunit::runner::write_failure_result_output "$test_file" "$hook_name" "$hook_output"

  return "$status"
}

function bashunit::runner::execute_file_hook() {
  local hook_name="$1"
  local test_file="$2"
  local render_header="${3:-false}"

  declare -F "$hook_name" >/dev/null 2>&1 || return 0

  local hook_output=""
  local status=0
  local hook_output_file
  hook_output_file=$(bashunit::temp_file "${hook_name}_output")

  # Enable errtrace to catch any failing command in the hook.
  # Using -E (errtrace) without -e (errexit) prevents the main process from
  # exiting on source failures (Bash 3.2 doesn't trigger ERR trap with -eE).
  # The ERR trap saves the exit status to a global variable, cleans up shell
  # options, and returns from the hook function to prevent subsequent commands
  # from executing.
  # Variables set before the failure are preserved since we don't use a subshell.
  _BASHUNIT_HOOK_ERR_STATUS=0
  set -E
  if bashunit::env::is_strict_mode_enabled; then
    set -uo pipefail
  fi
  trap '_BASHUNIT_HOOK_ERR_STATUS=$?; set +Eu +o pipefail; trap - ERR; return $_BASHUNIT_HOOK_ERR_STATUS' ERR

  {
    "$hook_name"
  } >"$hook_output_file" 2>&1

  # Capture exit status from global variable and clean up
  status=$_BASHUNIT_HOOK_ERR_STATUS
  trap - ERR
  set +Eu +o pipefail

  if [ -f "$hook_output_file" ]; then
    hook_output=""
    local line
    while IFS= read -r line; do
      [ -z "$hook_output" ] && hook_output="$line" || hook_output="$hook_output"$'\n'"$line"
    done <"$hook_output_file"
    rm -f "$hook_output_file"
  fi

  if [ $status -ne 0 ]; then
    bashunit::runner::record_file_hook_failure "$hook_name" "$test_file" "$hook_output" "$status" "$render_header"
    return $status
  fi

  if [ -n "$hook_output" ] && bashunit::env::is_verbose_enabled; then
    printf "%s\n" "$hook_output"
  fi

  return 0
}

function bashunit::runner::run_set_up() {
  local _test_file="${1-}"
  bashunit::internal_log "run_set_up"
  bashunit::runner::execute_test_hook 'set_up'
}

function bashunit::runner::run_set_up_before_script() {
  local test_file="$1"
  bashunit::internal_log "run_set_up_before_script"

  # Check if hook exists first
  if ! declare -F "set_up_before_script" >/dev/null 2>&1; then
    return 0
  fi

  local start_time
  start_time=$(bashunit::clock::now)

  # Enable coverage trap to attribute lines executed during set_up_before_script
  if bashunit::env::is_coverage_enabled; then
    bashunit::coverage::enable_trap
  fi

  # Execute the hook (render_header=false since header is already rendered)
  bashunit::runner::execute_file_hook 'set_up_before_script' "$test_file" false
  local status=$?

  # Disable coverage trap after hook execution
  if bashunit::env::is_coverage_enabled; then
    bashunit::coverage::disable_trap
  fi

  local end_time
  end_time=$(bashunit::clock::now)
  local duration_ns=$((end_time - start_time))
  local duration_ms=$((duration_ns / 1000000))

  # Print completion message only if hook succeeded
  if [ $status -eq 0 ]; then
    bashunit::console_results::print_hook_completed "set_up_before_script" "$duration_ms"
  fi

  return $status
}

function bashunit::runner::run_tear_down() {
  local _test_file="${1-}"
  bashunit::internal_log "run_tear_down"
  bashunit::runner::execute_test_hook 'tear_down'
}

function bashunit::runner::execute_test_hook() {
  local hook_name="$1"

  declare -F "$hook_name" >/dev/null 2>&1 || return 0

  local hook_output=""
  local status=0
  local hook_output_file
  hook_output_file=$(bashunit::temp_file "${hook_name}_output")

  # Enable errtrace to catch any failing command in the hook.
  # Using -E (errtrace) without -e (errexit) prevents the subshell from
  # exiting on source failures (Bash 3.2 doesn't trigger ERR trap with -eE).
  # The ERR trap saves the exit status to a global variable, cleans up shell
  # options, and returns from the hook function to prevent subsequent commands
  # from executing.
  # Variables set before the failure are preserved since we don't use a subshell.
  _BASHUNIT_HOOK_ERR_STATUS=0
  set -E
  if bashunit::env::is_strict_mode_enabled; then
    set -uo pipefail
  fi
  trap '_BASHUNIT_HOOK_ERR_STATUS=$?; set +Eu +o pipefail; trap - ERR; return $_BASHUNIT_HOOK_ERR_STATUS' ERR

  {
    "$hook_name"
  } >"$hook_output_file" 2>&1

  # Capture exit status from global variable and clean up
  status=$_BASHUNIT_HOOK_ERR_STATUS
  trap - ERR
  set +Eu +o pipefail

  if [ -f "$hook_output_file" ]; then
    hook_output=""
    local line
    while IFS= read -r line; do
      [ -z "$hook_output" ] && hook_output="$line" || hook_output="$hook_output"$'\n'"$line"
    done <"$hook_output_file"
    rm -f "$hook_output_file"
  fi

  if [ $status -ne 0 ]; then
    local message="$hook_output"
    if [ -n "$hook_output" ]; then
      printf "%s" "$hook_output"
    else
      message="Hook '$hook_name' failed with exit code $status"
      printf "%s\n" "$message" >&2
    fi
    bashunit::runner::record_test_hook_failure "$hook_name" "$message" "$status"
    return "$status"
  fi

  if [ -n "$hook_output" ]; then
    printf "%s" "$hook_output"
  fi

  return 0
}

function bashunit::runner::record_test_hook_failure() {
  local hook_name="$1"
  local hook_message="$2"
  local status="$3"

  if [ -n "$_BASHUNIT_TEST_HOOK_FAILURE" ]; then
    return "$status"
  fi

  bashunit::state::set_test_hook_failure "$hook_name"
  bashunit::state::set_test_hook_message "$hook_message"

  return "$status"
}

function bashunit::runner::clear_mocks() {
  if [ "${#_BASHUNIT_MOCKED_FUNCTIONS[@]}" -eq 0 ]; then
    return
  fi

  local i
  for i in "${!_BASHUNIT_MOCKED_FUNCTIONS[@]}"; do
    bashunit::unmock "${_BASHUNIT_MOCKED_FUNCTIONS[$i]:-}"
  done
}

function bashunit::runner::run_tear_down_after_script() {
  local test_file="$1"
  bashunit::internal_log "run_tear_down_after_script"

  # Check if hook exists first
  if ! declare -F "tear_down_after_script" >/dev/null 2>&1; then
    # Add blank line after tests if no tear_down hook
    if ! bashunit::env::is_simple_output_enabled &&
      ! bashunit::env::is_failures_only_enabled &&
      ! bashunit::env::is_no_progress_enabled &&
      ! bashunit::parallel::is_enabled; then
      echo ""
    fi
    return 0
  fi

  local start_time
  start_time=$(bashunit::clock::now)

  # Enable coverage trap to attribute lines executed during tear_down_after_script
  if bashunit::env::is_coverage_enabled; then
    bashunit::coverage::enable_trap
  fi

  # Execute the hook
  bashunit::runner::execute_file_hook 'tear_down_after_script' "$test_file"
  local status=$?

  # Disable coverage trap after hook execution
  if bashunit::env::is_coverage_enabled; then
    bashunit::coverage::disable_trap
  fi

  local end_time
  end_time=$(bashunit::clock::now)
  local duration_ns=$((end_time - start_time))
  local duration_ms=$((duration_ns / 1000000))

  # Print completion message only if hook succeeded
  if [ $status -eq 0 ]; then
    bashunit::console_results::print_hook_completed "tear_down_after_script" "$duration_ms"
  fi

  # Add blank line after tear_down output
  if ! bashunit::env::is_simple_output_enabled &&
    ! bashunit::env::is_failures_only_enabled &&
    ! bashunit::env::is_no_progress_enabled &&
    ! bashunit::parallel::is_enabled; then
    echo ""
  fi

  return $status
}

function bashunit::runner::clean_set_up_and_tear_down_after_script() {
  bashunit::internal_log "clean_set_up_and_tear_down_after_script"
  bashunit::helper::unset_if_exists 'set_up'
  bashunit::helper::unset_if_exists 'tear_down'
  bashunit::helper::unset_if_exists 'set_up_before_script'
  bashunit::helper::unset_if_exists 'tear_down_after_script'
}

# benchmark.sh

_BASHUNIT_BENCH_NAMES=()
_BASHUNIT_BENCH_REVS=()
_BASHUNIT_BENCH_ITS=()
_BASHUNIT_BENCH_AVERAGES=()
_BASHUNIT_BENCH_MAX_MILLIS=()

function bashunit::benchmark::parse_annotations() {
  local fn_name=$1
  local script=$2
  local revs=1
  local its=1
  local max_ms=""

  local annotation
  annotation=$(awk "/function[[:space:]]+${fn_name}[[:space:]]*\(/ {print prev; exit} {prev=\$0}" "$script")

  local _extracted
  _extracted=$(echo "$annotation" | sed -n 's/.*@revs=\([0-9][0-9]*\).*/\1/p')
  if [ -n "$_extracted" ]; then
    revs="$_extracted"
  else
    _extracted=$(echo "$annotation" | sed -n 's/.*@revolutions=\([0-9][0-9]*\).*/\1/p')
    if [ -n "$_extracted" ]; then
      revs="$_extracted"
    fi
  fi

  _extracted=$(echo "$annotation" | sed -n 's/.*@its=\([0-9][0-9]*\).*/\1/p')
  if [ -n "$_extracted" ]; then
    its="$_extracted"
  else
    _extracted=$(echo "$annotation" | sed -n 's/.*@iterations=\([0-9][0-9]*\).*/\1/p')
    if [ -n "$_extracted" ]; then
      its="$_extracted"
    fi
  fi

  _extracted=$(echo "$annotation" | sed -n 's/.*@max_ms=\([0-9.][0-9.]*\).*/\1/p')
  if [ -n "$_extracted" ]; then
    max_ms="$_extracted"
  fi

  if [ -n "$max_ms" ]; then
    echo "$revs" "$its" "$max_ms"
  else
    echo "$revs" "$its"
  fi
}

function bashunit::benchmark::add_result() {
  _BASHUNIT_BENCH_NAMES[${#_BASHUNIT_BENCH_NAMES[@]}]="$1"
  _BASHUNIT_BENCH_REVS[${#_BASHUNIT_BENCH_REVS[@]}]="$2"
  _BASHUNIT_BENCH_ITS[${#_BASHUNIT_BENCH_ITS[@]}]="$3"
  _BASHUNIT_BENCH_AVERAGES[${#_BASHUNIT_BENCH_AVERAGES[@]}]="$4"
  _BASHUNIT_BENCH_MAX_MILLIS[${#_BASHUNIT_BENCH_MAX_MILLIS[@]}]="$5"
}

# shellcheck disable=SC2155
function bashunit::benchmark::run_function() {
  local fn_name=$1
  local revs=$2
  local its=$3
  local max_ms=$4
  local IFS=$' \t\n'
  local -a durations=()
  local durations_count=0
  local i r

  for ((i = 1; i <= its; i++)); do
    local start_time=$(bashunit::clock::now)
    (
      for ((r = 1; r <= revs; r++)); do
        "$fn_name" >/dev/null 2>&1
      done
    )
    local end_time=$(bashunit::clock::now)
    local dur_ns=$(bashunit::math::calculate "($end_time - $start_time)")
    local dur_ms=$(bashunit::math::calculate "$dur_ns / 1000000")
    durations[durations_count]="$dur_ms"
    durations_count=$((durations_count + 1))

    if bashunit::env::is_bench_mode_enabled; then
      local label="$(bashunit::helper::normalize_test_function_name "$fn_name")"
      local line="$label [$i/$its] ${dur_ms} ms"
      bashunit::state::print_line "successful" "$line"
    fi
  done

  local sum=0
  local d
  for d in "${durations[@]+"${durations[@]}"}"; do
    sum=$(bashunit::math::calculate "$sum + $d")
  done
  local avg=$(bashunit::math::calculate "$sum / ${#durations[@]}")
  bashunit::benchmark::add_result "$fn_name" "$revs" "$its" "$avg" "$max_ms"
}

function bashunit::benchmark::print_results() {
  if ! bashunit::env::is_bench_mode_enabled; then
    return
  fi

  if ((${#_BASHUNIT_BENCH_NAMES[@]} == 0)); then
    return
  fi

  if bashunit::env::is_simple_output_enabled; then
    printf "\n"
  fi

  printf "\nBenchmark Results (avg ms)\n"
  bashunit::print_line 80 "="
  printf "\n"

  local IFS=$' \t\n'
  local has_threshold=false
  local val
  for val in "${_BASHUNIT_BENCH_MAX_MILLIS[@]+"${_BASHUNIT_BENCH_MAX_MILLIS[@]}"}"; do
    if [ -n "$val" ]; then
      has_threshold=true
      break
    fi
  done

  if $has_threshold; then
    printf '%-40s %6s %6s %10s %12s\n' "Name" "Revs" "Its" "Avg(ms)" "Status"
  else
    printf '%-40s %6s %6s %10s\n' "Name" "Revs" "Its" "Avg(ms)"
  fi

  local i
  for i in "${!_BASHUNIT_BENCH_NAMES[@]}"; do
    local name="${_BASHUNIT_BENCH_NAMES[$i]:-}"
    local revs="${_BASHUNIT_BENCH_REVS[$i]:-}"
    local its="${_BASHUNIT_BENCH_ITS[$i]:-}"
    local avg="${_BASHUNIT_BENCH_AVERAGES[$i]:-}"
    local max_ms="${_BASHUNIT_BENCH_MAX_MILLIS[$i]:-}"

    if [ -z "$max_ms" ]; then
      printf '%-40s %6s %6s %10s\n' "$name" "$revs" "$its" "$avg"
      continue
    fi

    if [ "$avg" -le "$max_ms" ]; then
      local raw="≤ ${max_ms}"
      local padded
      padded=$(printf "%14s" "$raw")
      printf '%-40s %6s %6s %10s %12s\n' "$name" "$revs" "$its" "$avg" "$padded"
      continue
    fi

    local raw="> ${max_ms}"
    local padded
    padded=$(printf "%12s" "$raw")
    printf '%-40s %6s %6s %10s %s%s%s\n' \
      "$name" "$revs" "$its" "$avg" \
      "$_BASHUNIT_COLOR_FAILED" "$padded" "${_BASHUNIT_COLOR_DEFAULT}"
  done

  bashunit::console_results::print_execution_time
}

# init.sh

function bashunit::init::project() {
  local tests_dir="${1:-$BASHUNIT_DEFAULT_PATH}"
  mkdir -p "$tests_dir"

  local bootstrap_file="$tests_dir/bootstrap.sh"
  if [ ! -f "$bootstrap_file" ]; then
    cat >"$bootstrap_file" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
# Place your common test setup here
SH
    chmod +x "$bootstrap_file"
    echo "> Created $bootstrap_file"
  fi

  local example_test="$tests_dir/example_test.sh"
  if [ ! -f "$example_test" ]; then
    cat >"$example_test" <<'SH'
#!/usr/bin/env bash

function test_bashunit_is_installed() {
  assert_same "bashunit is installed" "bashunit is installed"
}
SH
    chmod +x "$example_test"
    echo "> Created $example_test"
  fi

  local env_file=".env"
  local env_line="BASHUNIT_BOOTSTRAP=$bootstrap_file"
  if [ -f "$env_file" ]; then
    if grep -q "^BASHUNIT_BOOTSTRAP=" "$env_file"; then
      if bashunit::check_os::is_macos; then
        sed -i '' -e "s/^BASHUNIT_BOOTSTRAP=/#&/" "$env_file"
      else
        sed -i -e "s/^BASHUNIT_BOOTSTRAP=/#&/" "$env_file"
      fi
    fi
    echo "$env_line" >>"$env_file"
  else
    echo "$env_line" >"$env_file"
  fi

  echo "> bashunit initialized in $tests_dir"
}

# learn.sh
# shellcheck disable=SC2016

##
# Interactive learning module for bashunit
# Provides guided tutorials and exercises to learn bashunit
##

declare -r LEARN_TEMP_DIR="/tmp/bashunit_learn_$$"
declare -r LEARN_PROGRESS_FILE="$HOME/.bashunit_learn_progress"

##
# Initialize learning environment
##
function bashunit::learn::init() {
  mkdir -p "$LEARN_TEMP_DIR"
  mkdir -p tests
}

##
# Cleanup learning environment
##
function bashunit::learn::cleanup() {
  rm -rf "$LEARN_TEMP_DIR"
}

##
# Print the learning menu
##
function bashunit::learn::print_menu() {
  cat <<EOF
${_BASHUNIT_COLOR_BOLD}${_BASHUNIT_COLOR_PASSED}bashunit${_BASHUNIT_COLOR_DEFAULT} - Interactive Learning

Choose a lesson:

  ${_BASHUNIT_COLOR_BOLD}1.${_BASHUNIT_COLOR_DEFAULT} Basics - Your First Test
  ${_BASHUNIT_COLOR_BOLD}2.${_BASHUNIT_COLOR_DEFAULT} Assertions - Testing Different Conditions
  ${_BASHUNIT_COLOR_BOLD}3.${_BASHUNIT_COLOR_DEFAULT} Setup & Teardown - Managing Test Lifecycle
  ${_BASHUNIT_COLOR_BOLD}4.${_BASHUNIT_COLOR_DEFAULT} Testing Functions - Unit Testing Patterns
  ${_BASHUNIT_COLOR_BOLD}5.${_BASHUNIT_COLOR_DEFAULT} Testing Scripts - Integration Testing
  ${_BASHUNIT_COLOR_BOLD}6.${_BASHUNIT_COLOR_DEFAULT} Mocking - Test Doubles and Mocks
  ${_BASHUNIT_COLOR_BOLD}7.${_BASHUNIT_COLOR_DEFAULT} Spies - Verifying Function Calls
  ${_BASHUNIT_COLOR_BOLD}8.${_BASHUNIT_COLOR_DEFAULT} Data Providers - Parameterized Tests
  ${_BASHUNIT_COLOR_BOLD}9.${_BASHUNIT_COLOR_DEFAULT} Exit Codes - Testing Success and Failure
  ${_BASHUNIT_COLOR_BOLD}10.${_BASHUNIT_COLOR_DEFAULT} Complete Challenge - Real World Scenario

  ${_BASHUNIT_COLOR_BOLD}p.${_BASHUNIT_COLOR_DEFAULT} Show Progress
  ${_BASHUNIT_COLOR_BOLD}r.${_BASHUNIT_COLOR_DEFAULT} Reset Progress
  ${_BASHUNIT_COLOR_BOLD}q.${_BASHUNIT_COLOR_DEFAULT} Quit

Enter your choice:
EOF
}

##
# Main learning loop
##
function bashunit::learn::start() {
  bashunit::learn::init

  trap 'bashunit::learn::cleanup' EXIT

  while true; do
    echo ""
    bashunit::learn::print_menu
    read -r choice
    echo ""

    case "$choice" in
    1) bashunit::learn::lesson_basics || true ;;
    2) bashunit::learn::lesson_assertions || true ;;
    3) bashunit::learn::lesson_lifecycle || true ;;
    4) bashunit::learn::lesson_functions || true ;;
    5) bashunit::learn::lesson_scripts || true ;;
    6) bashunit::learn::lesson_mocking || true ;;
    7) bashunit::learn::lesson_spies || true ;;
    8) bashunit::learn::lesson_data_providers || true ;;
    9) bashunit::learn::lesson_exit_codes || true ;;
    10) bashunit::learn::lesson_challenge || true ;;
    p) bashunit::learn::show_progress ;;
    r) bashunit::learn::reset_progress ;;
    q)
      echo "${_BASHUNIT_COLOR_PASSED}Happy testing!${_BASHUNIT_COLOR_DEFAULT}"
      break
      ;;
    *)
      echo "${_BASHUNIT_COLOR_FAILED}Invalid choice. Please try again.${_BASHUNIT_COLOR_DEFAULT}"
      ;;
    esac
  done

  bashunit::learn::cleanup
}

##
# Mark lesson as completed
##
function bashunit::learn::mark_completed() {
  local lesson=$1
  echo "$lesson" >>"$LEARN_PROGRESS_FILE"
}

##
# Check if lesson is completed
##
function bashunit::learn::is_completed() {
  local lesson=$1
  [ -f "$LEARN_PROGRESS_FILE" ] && [ "$("$GREP" -c "^$lesson$" "$LEARN_PROGRESS_FILE" || true)" -gt 0 ]
}

##
# Show learning progress
##
function bashunit::learn::show_progress() {
  if [ ! -f "$LEARN_PROGRESS_FILE" ]; then
    echo "${_BASHUNIT_COLOR_INCOMPLETE}No progress yet. Start with lesson 1!${_BASHUNIT_COLOR_DEFAULT}"
    return
  fi

  echo "${_BASHUNIT_COLOR_BOLD}Your Progress:${_BASHUNIT_COLOR_DEFAULT}"
  echo ""

  local total_lessons=10
  local completed=0

  local i
  for i in $(seq 1 $total_lessons); do
    if bashunit::learn::is_completed "lesson_$i"; then
      echo "  ${_BASHUNIT_COLOR_PASSED}✓${_BASHUNIT_COLOR_DEFAULT} Lesson $i completed"
      ((completed++)) || true
    else
      echo "  ${_BASHUNIT_COLOR_INCOMPLETE}○${_BASHUNIT_COLOR_DEFAULT} Lesson $i"
    fi
  done

  echo ""
  echo "Progress: $completed/$total_lessons lessons completed"

  if [ $completed -eq $total_lessons ]; then
    echo ""
    printf "%s%s🎉 Congratulations! You've completed all lessons!%s\n" \
      "$_BASHUNIT_COLOR_PASSED" "$_BASHUNIT_COLOR_BOLD" "$_BASHUNIT_COLOR_DEFAULT"
  fi

  read -p "Press Enter to continue..." -r
}

##
# Reset learning progress
##
function bashunit::learn::reset_progress() {
  rm -f "$LEARN_PROGRESS_FILE"
  echo "${_BASHUNIT_COLOR_PASSED}Progress reset successfully.${_BASHUNIT_COLOR_DEFAULT}"
  read -p "Press Enter to continue..." -r
}

##
# Create the example file automatically
# Arguments: $1 - filename, $2 - file content
##
function bashunit::learn::create_example_file() {
  local filename=$1
  local content=$2

  echo ""
  echo "Creating example file ${_BASHUNIT_COLOR_BOLD}$filename${_BASHUNIT_COLOR_DEFAULT}..."
  echo "$content" >"$filename"
  chmod +x "$filename"
  echo "${_BASHUNIT_COLOR_PASSED}✓ Created $filename${_BASHUNIT_COLOR_DEFAULT}"
  echo ""
  echo "File created! Edit it to complete the TODO items, then run this lesson again."
  read -p "Press Enter to continue..." -r
  return 0
}

##
# Run a lesson test and check results
##
function bashunit::learn::run_lesson_test() {
  local test_file=$1
  local lesson_number=$2

  echo "${_BASHUNIT_COLOR_BOLD}Running your test...${_BASHUNIT_COLOR_DEFAULT}"
  echo ""

  if "$BASHUNIT_ROOT_DIR/bashunit" "$test_file" --simple; then
    echo ""
    printf "%s%s✓ Excellent! Lesson %s completed!%s\n" \
      "$_BASHUNIT_COLOR_PASSED" "$_BASHUNIT_COLOR_BOLD" "$lesson_number" "$_BASHUNIT_COLOR_DEFAULT"
    bashunit::learn::mark_completed "lesson_$lesson_number"
    read -p "Press Enter to continue..." -r
    return 0
  else
    echo ""
    echo "${_BASHUNIT_COLOR_FAILED}Not quite right. Review the requirements and try again.${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
    return 1
  fi
}

##
# Lesson 1: Basics - Your First Test
##
function bashunit::learn::lesson_basics() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║                    Lesson 1: Your First Test                   ║
╚════════════════════════════════════════════════════════════════╝

Welcome to bashunit! Let's write your first test.

CONCEPT: A test is a function that starts with 'test_' and uses
assertions to verify behavior.

TASK: Create a test file that checks if two values are equal.

File: tests/first_test.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function test_bashunit_works() {
  # TODO: Use assert_same to check if "hello" equals "hello"
  # Hint: assert_same "expected" "actual"
}
───────────────────────────────────────────────────────────────

TIPS:
  • The assert_same function takes two arguments:
    assert_same "expected" "actual"
  • Test functions must start with "test_" prefix
  • Always quote your strings to avoid word splitting
  • Keep test files in a tests/ directory for better organization
EOF

  local default_file="tests/first_test.sh"
  echo ""
  printf "When ready, enter file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function test_bashunit_works() {
  # TODO: Use assert_same to check if "hello" equals "hello"
  # Hint: assert_same "expected" "actual"
}'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  # Check if file contains assert_same
  if [ "$("$GREP" -c "assert_same" "$test_file" || true)" -eq 0 ]; then
    echo "${_BASHUNIT_COLOR_FAILED}Your test should use assert_same${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
    return 1
  fi

  bashunit::learn::run_lesson_test "$test_file" 1
}

##
# Lesson 2: Assertions - Testing Different Conditions
##
function bashunit::learn::lesson_assertions() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║              Lesson 2: Testing Different Conditions            ║
╚════════════════════════════════════════════════════════════════╝

CONCEPT: bashunit provides many assertion functions for different checks:
  • assert_same - exact equality
  • assert_contains - substring check
  • assert_matches - regex pattern
  • assert_not_same - inequality
  • assert_empty - checks if value is empty
  • assert_not_empty - checks if value is not empty

TASK: Write a test file with 3 different assertions.

File: tests/assertions_test.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function test_multiple_assertions() {
  local message="Hello, bashunit!"

  # TODO: Check that message contains "bashunit"
  # Hint: assert_contains "substring" "$message"

  # TODO: Check that message matches the pattern "Hello.*!"
  # Hint: assert_matches "pattern" "$message"

  # TODO: Check that message is not empty
  # Hint: assert_not_empty "$message"
}
───────────────────────────────────────────────────────────────

TIPS:
  • assert_same checks exact equality (useful for strings/numbers)
  • assert_contains is more flexible for partial matches
  • assert_matches uses regex patterns (e.g., "^[0-9]+$" for numbers)
  • Explore more: assert_empty, assert_true, assert_false
EOF

  local default_file="tests/assertions_test.sh"
  echo ""
  printf "When ready, enter file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function test_multiple_assertions() {
  local message="Hello, bashunit!"

  # TODO: Check that message contains "bashunit"
  # Hint: assert_contains "substring" "$message"

  # TODO: Check that message matches the pattern "Hello.*!"
  # Hint: assert_matches "pattern" "$message"

  # TODO: Check that message is not empty
  # Hint: assert_not_empty "$message"
}'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  if [ "$("$GREP" -c "assert_contains" "$test_file" || true)" -eq 0 ] ||
    [ "$("$GREP" -c "assert_matches" "$test_file" || true)" -eq 0 ] ||
    [ "$("$GREP" -c "assert_not_empty" "$test_file" || true)" -eq 0 ]; then
    echo "${_BASHUNIT_COLOR_FAILED}Your test should use all three assertion types${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
    return 1
  fi

  bashunit::learn::run_lesson_test "$test_file" 2
}

##
# Lesson 3: Setup & Teardown - Managing Test Lifecycle
##
function bashunit::learn::lesson_lifecycle() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║           Lesson 3: Setup and Teardown Functions               ║
╚════════════════════════════════════════════════════════════════╝

CONCEPT: Tests often need preparation and cleanup. bashunit provides:
  • set_up() - runs before EACH test
  • tear_down() - runs after EACH test
  • set_up_before_script() - runs once before ALL tests
  • tear_down_after_script() - runs once after ALL tests

TASK: Create a test that uses setup and teardown to manage files.

File: tests/lifecycle_test.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function set_up() {
  # Create a temp file before each test
  # TODO: export TEST_FILE="/tmp/test_$$"
  # TODO: echo "test content" > "$TEST_FILE"
}

function tear_down() {
  # Clean up after each test
  # TODO: rm -f "$TEST_FILE"
}

function test_file_exists() {
  # TODO: assert_file_exists "$TEST_FILE"
}

function test_file_has_content() {
  # TODO: assert_file_contains "test content" "$TEST_FILE"
}
───────────────────────────────────────────────────────────────

TIPS:
  • set_up() runs before EACH test (good for test isolation)
  • set_up_before_script() runs ONCE before all tests (good for expensive setup)
  • Always clean up in tear_down() to avoid polluting other tests
  • Use $$ for unique temp file names to avoid conflicts
EOF

  local default_file="tests/lifecycle_test.sh"
  echo ""
  printf "When ready, enter file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function set_up() {
  # Create a temp file before each test
  # TODO: export TEST_FILE="/tmp/test_$$"
  # TODO: echo "test content" > "$TEST_FILE"
}

function tear_down() {
  # Clean up after each test
  # TODO: rm -f "$TEST_FILE"
}

function test_file_exists() {
  # TODO: assert_file_exists "$TEST_FILE"
}

function test_file_has_content() {
  # TODO: assert_file_contains "test content" "$TEST_FILE"
}'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  if [ "$("$GREP" -c "function set_up()" "$test_file" || true)" -eq 0 ] ||
    [ "$("$GREP" -c "function tear_down()" "$test_file" || true)" -eq 0 ]; then
    echo "${_BASHUNIT_COLOR_FAILED}Your test should define set_up and tear_down functions${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
    return 1
  fi

  bashunit::learn::run_lesson_test "$test_file" 3
}

##
# Lesson 4: Testing Functions
##
function bashunit::learn::lesson_functions() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║              Lesson 4: Testing Bash Functions                  ║
╚════════════════════════════════════════════════════════════════╝

CONCEPT: To test functions, source the file containing them, then
call them in your tests.

TASK: Create a script with a function, then test it.

File: calculator.sh (source code)
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function add() {
  echo $(($1 + $2))
}
───────────────────────────────────────────────────────────────

File: tests/calculator_test.sh (test file)
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function set_up() {
  # TODO: Source calculator.sh from parent directory
  # Hint: source ../calculator.sh
}

function test_add_positive_numbers() {
  # TODO: Test that add 2 3 returns "5"
  # Hint: result=$(add 2 3)
  # Hint: assert_same "5" "$result"
}

function test_add_negative_numbers() {
  # TODO: Test that add -2 -3 returns "-5"
  # Hint: result=$(add -2 -3)
  # Hint: assert_same "-5" "$result"
}
───────────────────────────────────────────────────────────────

TIPS:
  • Source files in set_up() to reload them fresh for each test
  • Capture function output with: result=$(function_name args)
  • Test edge cases: positive, negative, zero, large numbers
  • Source files from parent directory: source ../file.sh
EOF

  local default_file="tests/calculator_test.sh"
  echo ""
  printf "When ready, enter TEST file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function set_up() {
  # TODO: Source calculator.sh from parent directory
  # Hint: source ../calculator.sh
}

function test_add_positive_numbers() {
  # TODO: Test that add 2 3 returns "5"
  # Hint: result=$(add 2 3)
  # Hint: assert_same "5" "$result"
}

function test_add_negative_numbers() {
  # TODO: Test that add -2 -3 returns "-5"
  # Hint: result=$(add -2 -3)
  # Hint: assert_same "-5" "$result"
}'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  if [ "$("$GREP" -c "source" "$test_file" || true)" -eq 0 ]; then
    echo "${_BASHUNIT_COLOR_FAILED}Your test should source the calculator.sh file${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
    return 1
  fi

  bashunit::learn::run_lesson_test "$test_file" 4
}

##
# Lesson 5: Testing Scripts
##
function bashunit::learn::lesson_scripts() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║                 Lesson 5: Testing Bash Scripts                 ║
╚════════════════════════════════════════════════════════════════╝

CONCEPT: Scripts that execute commands directly are tested differently.
Run them and capture their output.

TASK: Create a script and test its output.

File: greeter.sh (source code)
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash
name=${1:-World}
echo "Hello, $name!"
───────────────────────────────────────────────────────────────

File: tests/greeter_test.sh (test file)
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function test_default_greeting() {
  # TODO: Run greeter.sh from parent directory and capture output
  # Hint: output=$(../greeter.sh)

  # TODO: Assert output contains "Hello, World!"
  # Hint: assert_contains "Hello, World!" "$output"
}

function test_custom_greeting() {
  # TODO: Run greeter.sh with argument "Alice"
  # Hint: output=$(../greeter.sh "Alice")

  # TODO: Assert output contains "Hello, Alice!"
  # Hint: assert_contains "Hello, Alice!" "$output"
}
───────────────────────────────────────────────────────────────

TIPS:
  • Use command substitution: output=$(./script.sh)
  • Make scripts executable: chmod +x script.sh
  • Test both default behavior and with various arguments
  • Scripts run in subshells, so they can't modify parent environment
  • Run scripts from parent directory: ../script.sh
EOF

  local default_file="tests/greeter_test.sh"
  echo ""
  printf "When ready, enter TEST file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function test_default_greeting() {
  # TODO: Run greeter.sh from parent directory and capture output
  # Hint: output=$(../greeter.sh)

  # TODO: Assert output contains "Hello, World!"
  # Hint: assert_contains "Hello, World!" "$output"
}

function test_custom_greeting() {
  # TODO: Run greeter.sh with argument "Alice"
  # Hint: output=$(../greeter.sh "Alice")

  # TODO: Assert output contains "Hello, Alice!"
  # Hint: assert_contains "Hello, Alice!" "$output"
}'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  bashunit::learn::run_lesson_test "$test_file" 5
}

##
# Lesson 6: Mocking
##
function bashunit::learn::lesson_mocking() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║               Lesson 6: Mocking External Commands              ║
╚════════════════════════════════════════════════════════════════╝

CONCEPT: Mocks let you override external commands or functions to
control their behavior in tests.

TASK: Test a function that uses external commands.

File: system_info.sh (source code)
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function get_system_info() {
  echo "OS: $(uname -s)"
}
───────────────────────────────────────────────────────────────

File: tests/system_info_test.sh (test file)
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function set_up() {
  source ../system_info.sh
}

function test_system_info_on_linux() {
  # TODO: Mock uname to return "Linux"
  # Hint: mock uname echo "Linux"

  local output
  output=$(get_system_info)

  # TODO: Assert output contains "OS: Linux"
}

function test_system_info_on_macos() {
  # TODO: Mock uname to return "Darwin"

  local output
  output=$(get_system_info)

  # TODO: Assert output contains "OS: Darwin"
}
───────────────────────────────────────────────────────────────

TIPS:
  • Mocks replace commands/functions with custom behavior
  • Syntax: mock command_name echo "mocked output"
  • Mocks are automatically cleaned up after each test
  • Use mocks to avoid calling expensive external commands
EOF

  local default_file="tests/system_info_test.sh"
  echo ""
  printf "When ready, enter TEST file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function set_up() {
  source ../system_info.sh
}

function test_system_info_on_linux() {
  # TODO: Mock uname to return "Linux"
  # Hint: mock uname echo "Linux"

  local output
  output=$(get_system_info)

  # TODO: Assert output contains "OS: Linux"
}

function test_system_info_on_macos() {
  # TODO: Mock uname to return "Darwin"

  local output
  output=$(get_system_info)

  # TODO: Assert output contains "OS: Darwin"
}'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  if [ "$("$GREP" -c "mock" "$test_file" || true)" -eq 0 ]; then
    echo "${_BASHUNIT_COLOR_FAILED}Your test should use mock${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
    return 1
  fi

  bashunit::learn::run_lesson_test "$test_file" 6
}

##
# Lesson 7: Spies
##
function bashunit::learn::lesson_spies() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║              Lesson 7: Spies - Verifying Calls                 ║
╚════════════════════════════════════════════════════════════════╝

CONCEPT: Spies let you verify that functions were called with specific
arguments or a certain number of times.

KEY DIFFERENCE: Spies track calls without changing behavior, while
mocks (Lesson 6) replace the function entirely with custom behavior.

TASK: Use spies to verify function calls.

File: deploy.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function deploy_app() {
  git push origin main
  docker build -t myapp .
  docker push myapp
}
───────────────────────────────────────────────────────────────

File: deploy_test.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function set_up() {
  source deploy.sh
}

function test_deploy_calls_git_push() {
  # TODO: Create spies for git and docker
  # Hint: spy git
  # Hint: spy docker

  deploy_app

  # TODO: Assert git was called
  # Hint: assert_have_been_called git

  # TODO: Assert docker was called
}

function test_deploy_calls_docker_twice() {
  # TODO: Spy on docker

  deploy_app

  # TODO: Assert docker was called exactly 2 times
  # Hint: assert_have_been_called_times 2 docker
}
───────────────────────────────────────────────────────────────

TIPS:
  • Spies track calls but don't change behavior (unlike mocks)
  • assert_have_been_called - verifies at least one call
  • assert_have_been_called_times N - verifies exact call count
  • assert_have_been_called_with - verifies specific arguments
  • Spies are cleaned up automatically after each test
EOF

  local default_file="deploy_test.sh"
  echo ""
  printf "When ready, enter TEST file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function set_up() {
  source deploy.sh
}

function test_deploy_calls_git_push() {
  # TODO: Create spies for git and docker
  # Hint: spy git
  # Hint: spy docker

  deploy_app

  # TODO: Assert git was called
  # Hint: assert_have_been_called git

  # TODO: Assert docker was called
}

function test_deploy_calls_docker_twice() {
  # TODO: Spy on docker

  deploy_app

  # TODO: Assert docker was called exactly 2 times
  # Hint: assert_have_been_called_times 2 docker
}'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  if [ "$("$GREP" -c "spy" "$test_file" || true)" -eq 0 ]; then
    echo "${_BASHUNIT_COLOR_FAILED}Your test should use spy${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
    return 1
  fi

  bashunit::learn::run_lesson_test "$test_file" 7
}

##
# Lesson 8: Data Providers
##
function bashunit::learn::lesson_data_providers() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║           Lesson 8: Data Providers - Parameterized Tests       ║
╚════════════════════════════════════════════════════════════════╝

CONCEPT: Data providers let you run the same test with different inputs.
Define a function that echoes test data, one per line.

HOW IT WORKS: Each line from data_provider_* becomes $1 in your test.
The test runs once for each line of data.

TASK: Test multiple email formats using a data provider.

File: validator.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function is_valid_email() {
  local email_pattern='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
  [ "$(echo "$1" | "$GREP" -cE "$email_pattern" || true)" -gt 0 ]
}
───────────────────────────────────────────────────────────────

File: validator_test.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function set_up() {
  source validator.sh
}

function data_provider_valid_emails() {
  # TODO: Echo valid email addresses, one per line
  # Example: echo "user@example.com"
}

function test_valid_emails() {
  # $1 contains the email from data provider
  # TODO: Assert is_valid_email succeeds
  # Hint: assert_successful_code "is_valid_email \"$1\""
}

function data_provider_invalid_emails() {
  # TODO: Echo invalid email addresses, one per line
  # Example: echo "not-an-email"
}

function test_invalid_emails() {
  # TODO: Assert is_valid_email fails
  # Hint: assert_general_error "is_valid_email \"$1\""
}
───────────────────────────────────────────────────────────────

TIPS:
  • Data providers must be named: data_provider_<test_name>
  • Each line of output becomes one test case
  • The test function receives the line as $1
  • Great for testing multiple inputs without duplicating code
  • You can have multiple data provider/test pairs in one file
EOF

  local default_file="validator_test.sh"
  echo ""
  printf "When ready, enter TEST file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function set_up() {
  source validator.sh
}

function data_provider_valid_emails() {
  # TODO: Echo valid email addresses, one per line
  # Example: echo "user@example.com"
}

function test_valid_emails() {
  # $1 contains the email from data provider
  # TODO: Assert is_valid_email succeeds
  # Hint: assert_successful_code "is_valid_email \"$1\""
}

function data_provider_invalid_emails() {
  # TODO: Echo invalid email addresses, one per line
  # Example: echo "not-an-email"
}

function test_invalid_emails() {
  # TODO: Assert is_valid_email fails
  # Hint: assert_general_error "is_valid_email \"$1\""
}'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  if [ "$("$GREP" -c "function data_provider_" "$test_file" || true)" -eq 0 ]; then
    echo "${_BASHUNIT_COLOR_FAILED}Your test should define data provider functions${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
    return 1
  fi

  bashunit::learn::run_lesson_test "$test_file" 8
}

##
# Lesson 9: Exit Codes
##
function bashunit::learn::lesson_exit_codes() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║             Lesson 9: Testing Exit Codes                       ║
╚════════════════════════════════════════════════════════════════╝

CONCEPT: Exit codes indicate success (0) or failure (non-zero).
bashunit provides assertions to test them:
  • assert_successful_code - expects exit code 0
  • assert_general_error - expects exit code 1
  • assert_exit_code N - expects specific exit code N

TASK: Test different exit codes.

File: checker.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function check_file() {
  if [ ! -e "$1" ]; then
    echo "File not found" >&2
    return 127
  fi

  if [ ! -r "$1" ]; then
    echo "Permission denied" >&2
    return 1
  fi

  echo "File OK"
  return 0
}
───────────────────────────────────────────────────────────────

File: checker_test.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function set_up() {
  source checker.sh
  # Create a test file
  export TEST_FILE="/tmp/test_file_$$"
  touch "$TEST_FILE"
}

function tear_down() {
  rm -f "$TEST_FILE"
}

function test_existing_file_returns_success() {
  # TODO: Assert check_file succeeds with TEST_FILE
  # Hint: assert_successful_code "check_file '$TEST_FILE'"
}

function test_missing_file_returns_127() {
  # TODO: Assert check_file returns exit code 127 for missing file
  # Hint: assert_exit_code 127 "check_file '/nonexistent/file'"
}
───────────────────────────────────────────────────────────────

TIPS:
  • Exit code 0 = success (assert_successful_code)
  • Exit code 1 = general error (assert_general_error)
  • Other codes = specific errors (assert_exit_code N)
  • Bash uses 'return N' in functions, 'exit N' in scripts
  • Common codes: 127=not found, 126=not executable, 2=misuse
EOF

  local default_file="checker_test.sh"
  echo ""
  printf "When ready, enter TEST file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function set_up() {
  source checker.sh
  # Create a test file
  export TEST_FILE="/tmp/test_file_$$"
  touch "$TEST_FILE"
}

function tear_down() {
  rm -f "$TEST_FILE"
}

function test_existing_file_returns_success() {
  # TODO: Assert check_file succeeds with TEST_FILE
  # Hint: assert_successful_code "check_file '\''$TEST_FILE'\''"
}

function test_missing_file_returns_127() {
  # TODO: Assert check_file returns exit code 127 for missing file
  # Hint: assert_exit_code 127 "check_file '\''/nonexistent/file'\''"
}'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  local _exit_assert_pattern="assert_successful_code\|assert_exit_code\|assert_general_error"
  if [ "$("$GREP" -c "$_exit_assert_pattern" "$test_file" || true)" -eq 0 ]; then
    echo "${_BASHUNIT_COLOR_FAILED}Your test should use exit code assertions${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
    return 1
  fi

  bashunit::learn::run_lesson_test "$test_file" 9
}

##
# Lesson 10: Complete Challenge
##
function bashunit::learn::lesson_challenge() {
  clear
  cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║          Lesson 10: Complete Challenge - Backup Script         ║
╚════════════════════════════════════════════════════════════════╝

FINAL CHALLENGE: Combine everything you've learned!

CONCEPT: Real-world tests combine multiple concepts: lifecycle
management, assertions, exit codes, and test doubles.

TASK: Create a backup script and comprehensive tests.

File: backup.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

function create_backup() {
  local source=$1
  local dest=$2

  if [ ! -d "$source" ]; then
    echo "Source directory not found" >&2
    return 1
  fi

  tar -czf "$dest" -C "$source" .
  echo "Backup created: $dest"
}
───────────────────────────────────────────────────────────────

File: backup_test.sh
───────────────────────────────────────────────────────────────
#!/usr/bin/env bash

Your test must include:
  1. set_up and tear_down functions
  2. Test successful backup creation
  3. Test failure when source doesn't exist
  4. Mock or spy on tar command
  5. Verify backup file exists
  6. Check output message

TIP: Combine patterns from all previous lessons!
EOF

  local default_file="backup_test.sh"
  echo ""
  printf "When ready, enter TEST file path %s[%s]%s: " \
    "${_BASHUNIT_COLOR_FAINT}" "$default_file" "${_BASHUNIT_COLOR_DEFAULT}"
  read -r test_file
  test_file="${test_file:-$default_file}"

  if [ ! -f "$test_file" ]; then
    local template='#!/usr/bin/env bash

function set_up() {
  source backup.sh
  # TODO: Create test directories and variables
}

function tear_down() {
  # TODO: Clean up test files
}

function test_successful_backup() {
  # TODO: Test backup creation
}

function test_backup_failure_when_source_missing() {
  # TODO: Test failure case
}

# Add more tests as needed:
# - Mock or spy on tar command
# - Verify backup file exists
# - Check output message
#
# TIPS:
# - Combine lifecycle (set_up/tear_down) with file assertions
# - Use spies to verify tar was called correctly
# - Test both success and failure scenarios
# - Mock external commands to avoid side effects'

    bashunit::learn::create_example_file "$test_file" "$template"
    return 1
  fi

  # Verify the test has key components
  local -a missing_components=()
  local missing_components_count=0

  if [ "$("$GREP" -c "function set_up()" "$test_file" || true)" -eq 0 ]; then
    missing_components[missing_components_count]="set_up function"
    missing_components_count=$((missing_components_count + 1))
  fi

  if [ "$("$GREP" -c "function tear_down()" "$test_file" || true)" -eq 0 ]; then
    missing_components[missing_components_count]="tear_down function"
    missing_components_count=$((missing_components_count + 1))
  fi

  if [ "$missing_components_count" -gt 0 ]; then
    echo "${_BASHUNIT_COLOR_FAILED}Missing required components:${_BASHUNIT_COLOR_DEFAULT}"
    printf "  - %s\n" "${missing_components[@]}"
    read -p "Press Enter to continue..." -r
    return 1
  fi

  if bashunit::learn::run_lesson_test "$test_file" 10; then
    echo ""
    echo "${_BASHUNIT_COLOR_PASSED}${_BASHUNIT_COLOR_BOLD}"
    cat <<'EOF'
╔════════════════════════════════════════════════════════════════╗
║                   🎉 CONGRATULATIONS! 🎉                       ║
║                                                                ║
║          You've completed all bashunit lessons!                ║
║                                                                ║
║  You now know how to:                                          ║
║    ✓ Write and run tests                                       ║
║    ✓ Use various assertions                                    ║
║    ✓ Manage test lifecycle                                     ║
║    ✓ Test functions and scripts                                ║
║    ✓ Mock external dependencies                                ║
║    ✓ Spy on function calls                                     ║
║    ✓ Use data providers                                        ║
║    ✓ Test exit codes                                           ║
║                                                                ║
║  Next steps:                                                   ║
║    • Explore https://bashunit.typeddevs.com                    ║
║    • Check out /common-patterns for more examples              ║
║    • Start testing your own bash scripts!                      ║
╚════════════════════════════════════════════════════════════════╝
EOF
    echo "${_BASHUNIT_COLOR_DEFAULT}"
    read -p "Press Enter to continue..." -r
  fi
}

# doc.sh

# This function returns the embedded assertions.md content.
# During development, it reads from the file.
# During build, this function is replaced with actual content.
function bashunit::doc::get_embedded_docs() {
  cat <<'__BASHUNIT_DOCS_EOF__'
__BASHUNIT_DOCS_EOF__
}

function bashunit::doc::print_asserts() {
  local filter="${1:-}"
  local docstring=""
  local fn=""
  local should_print=0

  local line
  while IFS='' read -r line || [ -n "$line" ]; do
    fn=$(echo "$line" | sed -n 's/^## \([A-Za-z0-9_]*\).*/\1/p')
    if [ -n "$fn" ]; then
      local _match=0
      if [ -z "$filter" ]; then
        _match=1
      else
        case "$fn" in *"$filter"*) _match=1 ;; esac
      fi
      if [ "$_match" -eq 1 ]; then
        should_print=1
        echo "$line"
        docstring=""
      else
        should_print=0
      fi
      continue
    fi

    if ((should_print)); then
      # Check for code fence using pattern matching instead of regex
      # Avoids backtick escaping issues in Bash 3.0
      case "$line" in
      '```'*)
        echo "--------------"
        echo "$docstring"
        should_print=0
        continue
        ;;
      esac

      case "$line" in
      "::: code-group"*) continue ;;
      esac

      # Remove markdown link brackets and anchor tags
      line="${line//[\[\]]/}"
      line="$(sed -E 's/ *\(#[-a-z0-9]+\)//g' <<<"$line")"
      docstring="$docstring$line"$'\n'
    fi
  done <<<"$(bashunit::doc::get_embedded_docs)"
}

# bashunit.sh

# This file provides a facade to developers who wants
# to interact with the internals of bashunit.
# e.g. adding custom assertions

function bashunit::assertion_failed() {
  bashunit::assert::should_skip && return 0

  local expected=$1
  local actual=$2
  local failure_condition_message=${3:-"but got "}

  local test_fn
  test_fn="$(bashunit::helper::find_test_function_name)"
  local label
  label="$(bashunit::helper::normalize_test_function_name "$test_fn")"
  bashunit::assert::mark_failed
  bashunit::console_results::print_failed_test "${label}" "${expected}" \
    "$failure_condition_message" "${actual}"
}

function bashunit::assertion_passed() {
  bashunit::assert::should_skip && return 0

  bashunit::state::add_assertions_passed
}

# main.sh

#############################
# Subcommand: test
#############################
function bashunit::main::cmd_test() {
  local filter=""
  local tag_filter=""
  local exclude_tag_filter=""
  local IFS=$' \t\n'
  local -a raw_args=()
  local raw_args_count=0
  local -a args=()
  local args_count=0
  local assert_fn=""
  local _bashunit_coverage_opt_set=false

  # Parse test-specific options
  while [ $# -gt 0 ]; do
    case "$1" in
    -a | --assert)
      assert_fn="$2"
      shift
      ;;
    -f | --filter)
      filter="$2"
      shift
      ;;
    --tag)
      if [ -z "$tag_filter" ]; then
        tag_filter="$2"
      else
        tag_filter="$tag_filter,$2"
      fi
      shift
      ;;
    --exclude-tag)
      if [ -z "$exclude_tag_filter" ]; then
        exclude_tag_filter="$2"
      else
        exclude_tag_filter="$exclude_tag_filter,$2"
      fi
      shift
      ;;
    -s | --simple)
      export BASHUNIT_SIMPLE_OUTPUT=true
      ;;
    --detailed)
      export BASHUNIT_SIMPLE_OUTPUT=false
      ;;
    --output)
      export BASHUNIT_OUTPUT_FORMAT="$2"
      shift
      ;;
    --debug)
      local output_file="${2:-}"
      if [ -n "$output_file" ] && [ "${output_file:0:1}" != "-" ]; then
        exec >"$output_file" 2>&1
        shift
      fi
      set -x
      ;;
    -S | --stop-on-failure)
      export BASHUNIT_STOP_ON_FAILURE=true
      ;;
    -p | --parallel)
      export BASHUNIT_PARALLEL_RUN=true
      ;;
    -j | --jobs)
      export BASHUNIT_PARALLEL_RUN=true
      export BASHUNIT_PARALLEL_JOBS="$2"
      shift
      ;;
    --no-parallel)
      export BASHUNIT_PARALLEL_RUN=false
      ;;
    -w | --watch)
      export BASHUNIT_WATCH_MODE=true
      ;;
    -e | --env | --boot)
      # Support: --env "bootstrap.sh arg1 arg2"
      local boot_file="${2%% *}"
      local boot_args="${2#* }"
      if [ "$boot_args" != "$2" ]; then
        export BASHUNIT_BOOTSTRAP_ARGS="$boot_args"
      fi
      # Export all variables from the env file so they're available in subshells
      # (e.g., process substitution used in load_test_files)
      set -o allexport
      # shellcheck disable=SC1090,SC2086
      source "$boot_file" ${BASHUNIT_BOOTSTRAP_ARGS:-}
      set +o allexport
      shift
      ;;
    --log-junit)
      export BASHUNIT_LOG_JUNIT="$2"
      shift
      ;;
    -r | --report-html)
      export BASHUNIT_REPORT_HTML="$2"
      shift
      ;;
    --no-output)
      export BASHUNIT_NO_OUTPUT=true
      ;;
    -vvv | --verbose)
      export BASHUNIT_VERBOSE=true
      ;;
    -h | --help)
      bashunit::console_header::print_test_help
      exit 0
      ;;
    --show-skipped)
      export BASHUNIT_SHOW_SKIPPED=true
      ;;
    --show-incomplete)
      export BASHUNIT_SHOW_INCOMPLETE=true
      ;;
    --failures-only)
      export BASHUNIT_FAILURES_ONLY=true
      ;;
    --show-output)
      export BASHUNIT_SHOW_OUTPUT_ON_FAILURE=true
      ;;
    --no-output-on-failure)
      export BASHUNIT_SHOW_OUTPUT_ON_FAILURE=false
      ;;
    --no-progress)
      export BASHUNIT_NO_PROGRESS=true
      ;;
    --strict)
      export BASHUNIT_STRICT_MODE=true
      ;;
    -R | --run-all)
      export BASHUNIT_STOP_ON_ASSERTION_FAILURE=false
      ;;
    --skip-env-file)
      export BASHUNIT_SKIP_ENV_FILE=true
      ;;
    -l | --login)
      export BASHUNIT_LOGIN_SHELL=true
      ;;
    --no-color)
      # shellcheck disable=SC2034
      BASHUNIT_NO_COLOR=true
      ;;
    --coverage)
      # Don't export - prevents nested bashunit runs from inheriting coverage
      # shellcheck disable=SC2034
      BASHUNIT_COVERAGE=true
      ;;
    --coverage-paths)
      # shellcheck disable=SC2034
      BASHUNIT_COVERAGE_PATHS="$2"
      shift
      ;;
    --coverage-exclude)
      # shellcheck disable=SC2034
      BASHUNIT_COVERAGE_EXCLUDE="$2"
      shift
      ;;
    --coverage-report)
      # shellcheck disable=SC2034
      BASHUNIT_COVERAGE_REPORT="$2"
      _bashunit_coverage_opt_set=true
      shift
      ;;
    --coverage-min)
      # shellcheck disable=SC2034
      BASHUNIT_COVERAGE_MIN="$2"
      _bashunit_coverage_opt_set=true
      shift
      ;;
    --no-coverage-report)
      # shellcheck disable=SC2034
      BASHUNIT_COVERAGE_REPORT=""
      ;;
    --coverage-report-html)
      # shellcheck disable=SC2034
      # Use default if no value provided or next arg is a flag
      if [ -z "${2:-}" ]; then
        BASHUNIT_COVERAGE_REPORT_HTML="coverage/html"
      else
        case "${2:-}" in
        -*)
          BASHUNIT_COVERAGE_REPORT_HTML="coverage/html"
          ;;
        *)
          BASHUNIT_COVERAGE_REPORT_HTML="$2"
          shift
          ;;
        esac
      fi
      _bashunit_coverage_opt_set=true
      ;;
    *)
      raw_args[raw_args_count]="$1"
      raw_args_count=$((raw_args_count + 1))
      ;;
    esac
    shift
  done

  # Auto-enable coverage when any coverage output option is specified
  if [ "$_bashunit_coverage_opt_set" = true ]; then
    # shellcheck disable=SC2034
    BASHUNIT_COVERAGE=true
  fi

  # Expand positional arguments and extract inline filters
  # Skip filter parsing for assert mode - args are not file paths
  local inline_filter=""
  local inline_filter_file=""
  if [ "$raw_args_count" -gt 0 ]; then
    if [ -n "$assert_fn" ]; then
      # Assert mode: pass args as-is without file path processing
      args=("${raw_args[@]}")
      args_count="$raw_args_count"
    else
      # Test mode: process file paths and extract inline filters
      local arg
      for arg in "${raw_args[@]+"${raw_args[@]}"}"; do
        local parsed_path parsed_filter
        {
          read -r parsed_path
          read -r parsed_filter
        } < <(bashunit::helper::parse_file_path_filter "$arg")

        # If an inline filter was found, store it
        if [ -n "$parsed_filter" ]; then
          inline_filter="$parsed_filter"
          inline_filter_file="$parsed_path"
        fi

        local file
        while IFS= read -r file; do
          args[args_count]="$file"
          args_count=$((args_count + 1))
        done < <(bashunit::helper::find_files_recursive "$parsed_path" '*[tT]est.sh')
      done

      # Resolve line number filter to function name
      case "$inline_filter" in
      "__line__:"*)
        local line_number="${inline_filter#__line__:}"
        local resolved_file="${inline_filter_file}"

        # If the file path was a pattern, use the first resolved file
        if [ "$args_count" -gt 0 ]; then
          resolved_file="${args[0]}"
        fi

        inline_filter=$(bashunit::helper::find_function_at_line "$resolved_file" "$line_number")
        if [ -z "$inline_filter" ]; then
          printf "%sError: No test function found at line %s in %s%s\n" \
            "${_BASHUNIT_COLOR_FAILED}" "$line_number" "$resolved_file" "${_BASHUNIT_COLOR_DEFAULT}"
          exit 1
        fi
        ;;
      esac

      # Use inline filter if no -f filter was provided
      if [ -z "$filter" ] && [ -n "$inline_filter" ]; then
        filter="$inline_filter"
      fi
    fi
  fi

  # Optional bootstrap
  # shellcheck disable=SC1090,SC2086
  [ -f "${BASHUNIT_BOOTSTRAP:-}" ] && source "$BASHUNIT_BOOTSTRAP" ${BASHUNIT_BOOTSTRAP_ARGS:-}

  if [ "${BASHUNIT_NO_OUTPUT:-false}" = true ]; then
    exec >/dev/null 2>&1
  fi

  # Disable strict mode for test execution to allow:
  # - Empty array expansion (set +u)
  # - Non-zero exit codes from failing tests (set +e)
  # - Pipe failures in test output (set +o pipefail)
  set +euo pipefail
  if [ -n "$assert_fn" ]; then
    # Disable coverage for assert mode - it's meant for running single assertions,
    # not tracking code coverage. This also prevents issues when parent bashunit
    # runs with coverage and calls subprocess bashunit with -a flag.
    export BASHUNIT_COVERAGE=false
    bashunit::main::exec_assert "$assert_fn" ${args+"${args[@]}"}
  else
    if [ "${BASHUNIT_WATCH_MODE:-false}" = true ]; then
      bashunit::main::watch_loop \
        "$filter" "$tag_filter" "$exclude_tag_filter" \
        ${args+"${args[@]}"}
    else
      if [ "$args_count" -gt 0 ]; then
        bashunit::main::exec_tests \
          "$filter" "$tag_filter" "$exclude_tag_filter" \
          "${args[@]}"
      else
        bashunit::main::exec_tests \
          "$filter" "$tag_filter" "$exclude_tag_filter"
      fi
    fi
  fi
}

#############################
# Subcommand: bench
#############################
function bashunit::main::cmd_bench() {
  local filter=""
  local IFS=$' \t\n'
  local -a raw_args=()
  local raw_args_count=0
  local -a args=()
  local args_count=0

  export BASHUNIT_BENCH_MODE=true

  # Parse bench-specific options
  while [ $# -gt 0 ]; do
    case "$1" in
    -f | --filter)
      filter="$2"
      shift
      ;;
    -s | --simple)
      export BASHUNIT_SIMPLE_OUTPUT=true
      ;;
    --detailed)
      export BASHUNIT_SIMPLE_OUTPUT=false
      ;;
    -e | --env | --boot)
      # Support: --env "bootstrap.sh arg1 arg2"
      local boot_file="${2%% *}"
      local boot_args="${2#* }"
      if [ "$boot_args" != "$2" ]; then
        export BASHUNIT_BOOTSTRAP_ARGS="$boot_args"
      fi
      # Export all variables from the env file so they're available in subshells
      # (e.g., process substitution used in load_test_files)
      set -o allexport
      # shellcheck disable=SC1090,SC2086
      source "$boot_file" ${BASHUNIT_BOOTSTRAP_ARGS:-}
      set +o allexport
      shift
      ;;
    -vvv | --verbose)
      export BASHUNIT_VERBOSE=true
      ;;
    --skip-env-file)
      export BASHUNIT_SKIP_ENV_FILE=true
      ;;
    -l | --login)
      export BASHUNIT_LOGIN_SHELL=true
      ;;
    --no-color)
      # shellcheck disable=SC2034
      BASHUNIT_NO_COLOR=true
      ;;
    -h | --help)
      bashunit::console_header::print_bench_help
      exit 0
      ;;
    *)
      raw_args[raw_args_count]="$1"
      raw_args_count=$((raw_args_count + 1))
      ;;
    esac
    shift
  done

  # Expand positional arguments
  if [ "$raw_args_count" -gt 0 ]; then
    local arg file
    for arg in "${raw_args[@]+"${raw_args[@]}"}"; do
      while IFS= read -r file; do
        args[args_count]="$file"
        args_count=$((args_count + 1))
      done < <(bashunit::helper::find_files_recursive "$arg" '*[bB]ench.sh')
    done
  fi

  # Optional bootstrap
  # shellcheck disable=SC1090,SC2086
  [ -f "${BASHUNIT_BOOTSTRAP:-}" ] && source "$BASHUNIT_BOOTSTRAP" ${BASHUNIT_BOOTSTRAP_ARGS:-}

  set +euo pipefail

  # Bash 3.0 compatible: only pass args if we have files
  if [ "$args_count" -gt 0 ]; then
    bashunit::main::exec_benchmarks "$filter" "${args[@]}"
  else
    bashunit::main::exec_benchmarks "$filter"
  fi
}

#############################
# Subcommand: doc
#############################
function bashunit::main::cmd_doc() {
  case "${1:-}" in
  -h | --help)
    bashunit::console_header::print_doc_help
    exit 0
    ;;
  esac

  bashunit::doc::print_asserts "${1:-}"
  exit 0
}

#############################
# Subcommand: init
#############################
function bashunit::main::cmd_init() {
  case "${1:-}" in
  -h | --help)
    bashunit::console_header::print_init_help
    exit 0
    ;;
  esac

  bashunit::init::project "${1:-}"
  exit 0
}

#############################
# Subcommand: learn
#############################
function bashunit::main::cmd_learn() {
  case "${1:-}" in
  -h | --help)
    bashunit::console_header::print_learn_help
    exit 0
    ;;
  esac

  bashunit::learn::start
  exit 0
}

#############################
# Subcommand: watch
#############################
function bashunit::main::cmd_watch() {
  case "${1:-}" in
  -h | --help)
    bashunit::console_header::print_watch_help
    exit 0
    ;;
  esac

  local path="${1:-.}"
  shift || true
  local -a extra_args=("$@")

  bashunit::watch::run "$path" "${extra_args[@]+\"${extra_args[@]}\"}"
}

#############################
# Subcommand: upgrade
#############################
function bashunit::main::cmd_upgrade() {
  case "${1:-}" in
  -h | --help)
    bashunit::console_header::print_upgrade_help
    exit 0
    ;;
  esac

  bashunit::upgrade::upgrade
  exit 0
}

#############################
# Subcommand: assert
#############################

# Check if a name corresponds to an assertion function (not a file or command)
function bashunit::main::is_assertion_function() {
  local name="$1"
  declare -F "assert_$name" &>/dev/null || declare -F "$name" &>/dev/null
}

# Check if assertion operates on exit codes
function bashunit::main::is_exit_code_assertion() {
  local name="$1"
  case "$name" in
  exit_code | successful_code | unsuccessful_code | general_error | command_not_found)
    return 0
    ;;
  *)
    return 1
    ;;
  esac
}

function bashunit::main::cmd_assert() {
  case "${1:-}" in
  -h | --help)
    bashunit::console_header::print_assert_help
    exit 0
    ;;
  esac

  local first_arg="${1:-}"
  if [ -z "$first_arg" ]; then
    printf "%sError: Assert function name or command is required.%s\n" \
      "${_BASHUNIT_COLOR_FAILED}" "${_BASHUNIT_COLOR_DEFAULT}"
    bashunit::console_header::print_assert_help
    exit 1
  fi

  # Disable strict mode for assert execution
  set +euo pipefail

  # Route to appropriate handler based on first argument
  if bashunit::main::is_assertion_function "$first_arg"; then
    # Old single-assertion syntax: bashunit assert <fn> <args...>
    local assert_fn="$first_arg"
    shift
    bashunit::main::exec_assert "$assert_fn" "$@"
  elif [ $# -ge 2 ] && bashunit::main::is_assertion_function "$2"; then
    # New multi-assertion syntax: bashunit assert "<cmd>" <assertion1> <arg1> ...
    # Detected by: first arg is not assertion, but second arg is an assertion name
    bashunit::main::exec_multi_assert "$@"
  else
    # Fallback: try as single assertion (may fail with function not found)
    bashunit::main::exec_assert "$@"
  fi
  exit $?
}

#############################
# Watch mode
#############################
function bashunit::main::watch_get_checksum() {
  local IFS=$' \t\n'
  local -a paths=("$@")

  local file checksum=""
  for file in "${paths[@]+"${paths[@]}"}"; do
    if [ -d "$file" ]; then
      local found
      found=$(find "$file" -name '*.sh' -type f \
        -exec stat -f '%m %N' {} + 2>/dev/null ||
        find "$file" -name '*.sh' -type f \
          -exec stat -c '%Y %n' {} + 2>/dev/null) || true
      checksum="${checksum}${found}"
    elif [ -f "$file" ]; then
      local mtime
      mtime=$(stat -f '%m' "$file" 2>/dev/null ||
        stat -c '%Y' "$file" 2>/dev/null) || true
      checksum="${checksum}${mtime} ${file}"
    fi
  done
  echo "$checksum"
}

function bashunit::main::watch_loop() {
  local filter="$1"
  local tag_filter="${2:-}"
  local exclude_tag_filter="${3:-}"
  shift 3

  local IFS=$' \t\n'
  local -a watch_paths=("$@")
  [ -d "src" ] && watch_paths[${#watch_paths[@]}]="src"

  trap 'printf "\n%sWatch mode stopped.%s\n" \
    "${_BASHUNIT_COLOR_SKIPPED}" "${_BASHUNIT_COLOR_DEFAULT}"; \
    exit 0' INT

  local last_checksum=""
  while true; do
    local current_checksum
    current_checksum=$(bashunit::main::watch_get_checksum \
      "${watch_paths[@]}")

    if [ "$current_checksum" != "$last_checksum" ]; then
      last_checksum="$current_checksum"
      printf '\033[2J\033[H'
      printf "%s[watch] Running tests...%s\n\n" \
        "${_BASHUNIT_COLOR_SKIPPED}" \
        "${_BASHUNIT_COLOR_DEFAULT}"

      (
        if [ $# -gt 0 ]; then
          bashunit::main::exec_tests \
            "$filter" "$tag_filter" \
            "$exclude_tag_filter" "$@"
        else
          bashunit::main::exec_tests \
            "$filter" "$tag_filter" \
            "$exclude_tag_filter"
        fi
      ) || true

      printf "\n%s[watch] Waiting for changes...%s\n" \
        "${_BASHUNIT_COLOR_SKIPPED}" \
        "${_BASHUNIT_COLOR_DEFAULT}"
    fi
    sleep 1
  done
}

#############################
# Test execution
#############################
function bashunit::main::exec_tests() {
  local filter=$1
  local tag_filter="${2:-}"
  local exclude_tag_filter="${3:-}"
  shift 3

  # Bash 3.0 compatible: collect files into array
  local test_files
  local test_files_count=0
  local _line
  while IFS= read -r _line; do
    [ -z "$_line" ] && continue
    test_files[test_files_count]="$_line"
    test_files_count=$((test_files_count + 1))
  done < <(bashunit::helper::load_test_files "$filter" "$@")

  bashunit::internal_log "exec_tests" "filter:$filter" "files:${test_files[*]:-}"

  if [ "$test_files_count" -eq 0 ]; then
    printf "%sError: At least one file path is required.%s\n" "${_BASHUNIT_COLOR_FAILED}" "${_BASHUNIT_COLOR_DEFAULT}"
    bashunit::console_header::print_help
    exit 1
  fi

  # Trap SIGINT (Ctrl-C) and call the cleanup function
  trap 'bashunit::main::cleanup' SIGINT
  trap '[ $? -eq $EXIT_CODE_STOP_ON_FAILURE ] && bashunit::main::handle_stop_on_failure_sync' EXIT

  if bashunit::env::is_parallel_run_enabled && ! bashunit::parallel::is_enabled; then
    printf "%sWarning: Parallel tests are supported on macOS, Ubuntu and Windows.\n" "${_BASHUNIT_COLOR_INCOMPLETE}"
    printf "For other OS (like Alpine), --parallel is not enabled due to inconsistent results,\n"
    printf "particularly involving race conditions.%s " "${_BASHUNIT_COLOR_DEFAULT}"
    printf "%sFallback using --no-parallel%s\n" "${_BASHUNIT_COLOR_SKIPPED}" "${_BASHUNIT_COLOR_DEFAULT}"
  fi

  if bashunit::parallel::is_enabled; then
    bashunit::parallel::init
  fi

  if bashunit::env::is_tap_output_enabled; then
    printf "TAP version 13\n"
  else
    bashunit::console_header::print_version_with_env "$filter" "${test_files[@]}"
  fi

  if bashunit::env::is_verbose_enabled; then
    if bashunit::env::is_simple_output_enabled; then
      echo ""
    fi
    printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '#'
    printf "%s\n" "Filter:      ${filter:-None}"
    printf "%s\n" "Total files: ${#test_files[@]}"
    printf "%s\n" "Test files:"
    printf -- "- %s\n" "${test_files[@]}"
    printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '.'
    bashunit::env::print_verbose
    printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '#'
  fi

  bashunit::runner::load_test_files "$filter" "$tag_filter" "$exclude_tag_filter" "${test_files[@]}"

  if bashunit::parallel::is_enabled; then
    wait
  fi

  if bashunit::parallel::is_enabled && bashunit::parallel::must_stop_on_failure; then
    printf "\r%sStop on failure enabled...%s\n" "${_BASHUNIT_COLOR_SKIPPED}" "${_BASHUNIT_COLOR_DEFAULT}"
  fi

  if ! bashunit::env::is_tap_output_enabled; then
    bashunit::console_results::print_failing_tests_and_reset
    bashunit::console_results::print_risky_tests_and_reset
    bashunit::console_results::print_incomplete_tests_and_reset
    bashunit::console_results::print_skipped_tests_and_reset
  fi
  bashunit::console_results::render_result
  exit_code=$?

  if [ -n "$BASHUNIT_LOG_JUNIT" ]; then
    bashunit::reports::generate_junit_xml "$BASHUNIT_LOG_JUNIT"
  fi

  if [ -n "$BASHUNIT_REPORT_HTML" ]; then
    bashunit::reports::generate_report_html "$BASHUNIT_REPORT_HTML"
  fi

  # Generate coverage report if enabled
  if bashunit::env::is_coverage_enabled; then
    # Aggregate per-process coverage data from parallel runs
    if bashunit::parallel::is_enabled; then
      bashunit::coverage::aggregate_parallel
    fi

    bashunit::coverage::report_text

    if [ -n "$BASHUNIT_COVERAGE_REPORT" ]; then
      bashunit::coverage::report_lcov "$BASHUNIT_COVERAGE_REPORT"
    fi

    if [ -n "$BASHUNIT_COVERAGE_REPORT_HTML" ]; then
      bashunit::coverage::report_html "$BASHUNIT_COVERAGE_REPORT_HTML"
    fi

    # Check minimum threshold
    if ! bashunit::coverage::check_threshold; then
      exit_code=1
    fi

    bashunit::coverage::cleanup
  fi

  if bashunit::parallel::is_enabled; then
    bashunit::parallel::cleanup
  fi

  bashunit::internal_log "Finished tests" "exit_code:$exit_code"
  exit $exit_code
}

function bashunit::main::exec_benchmarks() {
  local filter=$1
  shift

  # Bash 3.0 compatible: collect files into array
  local bench_files
  local bench_files_count=0
  local _line
  while IFS= read -r _line; do
    [ -z "$_line" ] && continue
    bench_files[bench_files_count]="$_line"
    bench_files_count=$((bench_files_count + 1))
  done < <(bashunit::helper::load_bench_files "$filter" "$@")

  bashunit::internal_log "exec_benchmarks" "filter:$filter" "files:${bench_files[*]:-}"

  if [ "$bench_files_count" -eq 0 ]; then
    printf "%sError: At least one file path is required.%s\n" "${_BASHUNIT_COLOR_FAILED}" "${_BASHUNIT_COLOR_DEFAULT}"
    bashunit::console_header::print_help
    exit 1
  fi

  bashunit::console_header::print_version_with_env "$filter" "${bench_files[@]}"

  bashunit::runner::load_bench_files "$filter" "${bench_files[@]}"

  bashunit::benchmark::print_results

  bashunit::internal_log "Finished benchmarks"
}

function bashunit::main::cleanup() {
  printf "%sCaught Ctrl-C, killing all child processes...%s\n" \
    "${_BASHUNIT_COLOR_SKIPPED}" "${_BASHUNIT_COLOR_DEFAULT}"
  # Kill all child processes of this script
  pkill -P $$
  bashunit::cleanup_script_temp_files
  if bashunit::parallel::is_enabled; then
    bashunit::parallel::cleanup
  fi
  exit 1
}

function bashunit::main::handle_stop_on_failure_sync() {
  printf "\n%sStop on failure enabled...%s\n" "${_BASHUNIT_COLOR_SKIPPED}" "${_BASHUNIT_COLOR_DEFAULT}"
  bashunit::console_results::print_failing_tests_and_reset
  bashunit::console_results::print_risky_tests_and_reset
  bashunit::console_results::print_incomplete_tests_and_reset
  bashunit::console_results::print_skipped_tests_and_reset
  bashunit::console_results::render_result
  bashunit::cleanup_script_temp_files
  if bashunit::parallel::is_enabled; then
    bashunit::parallel::cleanup
  fi
  exit 1
}

function bashunit::main::exec_assert() {
  local original_assert_fn=$1
  local -a args=()
  local args_count=$(($# - 1))
  [ $# -gt 1 ] && args=("${@:2}")

  local assert_fn=$original_assert_fn

  # Check if the function exists
  if ! type "$assert_fn" >/dev/null 2>&1; then
    assert_fn="assert_$assert_fn"
    if ! type "$assert_fn" >/dev/null 2>&1; then
      echo "Function $original_assert_fn does not exist." 1>&2
      exit 127
    fi
  fi

  # Get the last argument safely by calculating the array length
  local last_index=$((args_count - 1))
  local last_arg="${args[$last_index]}"
  local output=""
  local inner_exit_code=0
  local bashunit_exit_code=0

  # Handle different assert_* functions
  case "$assert_fn" in
  assert_exit_code)
    output=$(bashunit::main::handle_assert_exit_code "$last_arg")
    inner_exit_code=$?
    # Remove the last argument and append the exit code
    args=("${args[@]:0:last_index}")
    args[last_index]="$inner_exit_code"
    ;;
  *)
    # Add more cases here for other assert_* handlers if needed
    ;;
  esac

  if [ -n "$output" ]; then
    echo "$output" 1>&1
    assert_fn="assert_same"
  fi

  # Set a friendly test title for CLI assert command output
  bashunit::state::set_test_title "assert ${original_assert_fn#assert_}"

  # Run the assertion function and write into stderr
  "$assert_fn" "${args[@]}" 1>&2
  bashunit_exit_code=$?

  if [ "$(bashunit::state::get_tests_failed)" -gt 0 ] || [ "$(bashunit::state::get_assertions_failed)" -gt 0 ]; then
    return 1
  fi

  return "$bashunit_exit_code"
}

function bashunit::main::handle_assert_exit_code() {
  local cmd="$1"
  local output
  local inner_exit_code=0

  if command -v "${cmd%% *}" >/dev/null 2>&1; then
    output=$(eval "$cmd" 2>&1 || echo "inner_exit_code:$?")
    local last_line
    last_line=$(echo "$output" | tail -n 1)
    if [ "$(echo "$last_line" | "$GREP" -c 'inner_exit_code:[0-9]*' || true)" -gt 0 ]; then
      inner_exit_code=$(echo "$last_line" | grep -o 'inner_exit_code:[0-9]*' | cut -d':' -f2)
      local _re='^[0-9]+$'
      if [ "$(echo "$inner_exit_code" | "$GREP" -cE "$_re" || true)" -eq 0 ]; then
        inner_exit_code=1
      fi
      output=$(echo "$output" | sed '$d')
    fi
    echo "$output"
    return "$inner_exit_code"
  else
    echo "Command not found: $cmd" 1>&2
    return 127
  fi
}

# Execute multiple assertions on a single command output
# Usage: exec_multi_assert "command" assertion1 arg1 [assertion2 arg2 ...]
function bashunit::main::exec_multi_assert() {
  local cmd="$1"
  shift

  # Require at least one assertion
  if [ $# -lt 1 ]; then
    printf "%sError: Multi-assertion mode requires at least one assertion.%s\n" \
      "${_BASHUNIT_COLOR_FAILED}" "${_BASHUNIT_COLOR_DEFAULT}" 1>&2
    printf "Usage: bashunit assert \"<command>\" <assertion1> <arg1> [<assertion2> <arg2>...]\n" 1>&2
    return 1
  fi

  # Check that assertions come in pairs (assertion + arg)
  if [ $# -lt 2 ] || [ $(($# % 2)) -ne 0 ]; then
    local assertion_name="${1:-}"
    printf "%sError: Missing argument for assertion '%s'.%s\n" \
      "${_BASHUNIT_COLOR_FAILED}" "$assertion_name" "${_BASHUNIT_COLOR_DEFAULT}" 1>&2
    return 1
  fi

  # Execute command and capture output + exit code
  local stdout
  local cmd_exit_code
  stdout=$(eval "$cmd" 2>&1)
  cmd_exit_code=$?

  # Print stdout for user visibility
  if [ -n "$stdout" ]; then
    echo "$stdout" 1>&1
  fi

  # Parse and execute assertions in pairs
  local overall_result=0
  while [ $# -gt 0 ]; do
    local assertion_name="$1"
    local assertion_arg="${2:-}"

    if [ -z "$assertion_arg" ]; then
      printf "%sError: Missing argument for assertion '%s'.%s\n" \
        "${_BASHUNIT_COLOR_FAILED}" "$assertion_name" "${_BASHUNIT_COLOR_DEFAULT}" 1>&2
      return 1
    fi

    shift 2

    # Resolve assertion function name
    local assert_fn="$assertion_name"
    if ! type "$assert_fn" &>/dev/null; then
      assert_fn="assert_$assertion_name"
      if ! type "$assert_fn" &>/dev/null; then
        printf "%sError: Unknown assertion '%s'.%s\n" \
          "${_BASHUNIT_COLOR_FAILED}" "$assertion_name" "${_BASHUNIT_COLOR_DEFAULT}" 1>&2
        return 1
      fi
    fi

    # Set test title for this assertion
    bashunit::state::set_test_title "assert ${assertion_name#assert_}"

    # Execute assertion with appropriate argument
    if bashunit::main::is_exit_code_assertion "$assertion_name"; then
      # Exit code assertion: pass expected value and captured exit code
      "$assert_fn" "$assertion_arg" "" "$cmd_exit_code" 1>&2
    else
      # Output assertion: pass expected value and captured stdout
      "$assert_fn" "$assertion_arg" "$stdout" 1>&2
    fi

    if [ "$(bashunit::state::get_assertions_failed)" -gt 0 ]; then
      overall_result=1
    fi
  done

  return $overall_result
}

#!/usr/bin/env bash
set -euo pipefail

declare -r BASHUNIT_MIN_BASH_VERSION="3.0"

function _check_bash_version() {
  local current_version
  if [[ -n ${BASHUNIT_TEST_BASH_VERSION:-} ]]; then
    # Checks if BASHUNIT_TEST_BASH_VERSION is set (typically for testing purposes)
    current_version="${BASHUNIT_TEST_BASH_VERSION}"
  elif [[ -n ${BASH_VERSINFO+set} ]]; then
    # Checks if the special Bash array BASH_VERSINFO exists. This array is only defined in Bash.
    current_version="${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"
  else
    # If not in Bash (e.g., running from Zsh). The pipeline extracts just the major.minor version (e.g., 3.0).
    current_version="$(bash --version | head -n1 | cut -d' ' -f4 | cut -d. -f1,2)"
  fi

  local major
  IFS=. read -r major _ <<<"$current_version"

  if ((major < 3)); then
    printf 'Bashunit requires Bash >= %s. Current version: %s\n' "$BASHUNIT_MIN_BASH_VERSION" "$current_version" >&2
    exit 1
  fi
}

_check_bash_version

# shellcheck disable=SC2034
declare -r BASHUNIT_VERSION="0.34.1"

# shellcheck disable=SC2155
declare -r BASHUNIT_ROOT_DIR="$(dirname "${BASH_SOURCE[0]}")"
export BASHUNIT_ROOT_DIR

# Capture working directory at startup (before any test changes it)
declare -r BASHUNIT_WORKING_DIR="$PWD"
export BASHUNIT_WORKING_DIR

# Early scan for flags that must be set before loading env.sh
for arg in "$@"; do
  case "$arg" in
  --skip-env-file)
    export BASHUNIT_SKIP_ENV_FILE=true
    ;;
  -l | --login)
    export BASHUNIT_LOGIN_SHELL=true
    ;;
  --no-color)
    # shellcheck disable=SC2034
    BASHUNIT_NO_COLOR=true
    ;;
  esac
done


bashunit::check_os::init
bashunit::clock::init

# Subcommand detection
_SUBCOMMAND=""

case "${1:-}" in
  test | bench | doc | init | learn | upgrade | assert | watch)
    _SUBCOMMAND="$1"
    shift
    ;;
  -v | --version)
    bashunit::console_header::print_version
    exit 0
    ;;
  -h | --help)
    bashunit::console_header::print_help
    exit 0
    ;;
  -*)
    # Flag without subcommand → assume "test"
    _SUBCOMMAND="test"
    ;;
  "")
    # No arguments → assume "test" (uses BASHUNIT_DEFAULT_PATH)
    _SUBCOMMAND="test"
    ;;
  *)
    # Path argument → assume "test"
    _SUBCOMMAND="test"
    ;;
esac

# Route to subcommand handler
case "$_SUBCOMMAND" in
  test) bashunit::main::cmd_test "$@" ;;
  bench) bashunit::main::cmd_bench "$@" ;;
  doc) bashunit::main::cmd_doc "$@" ;;
  init) bashunit::main::cmd_init "$@" ;;
  learn) bashunit::main::cmd_learn "$@" ;;
  upgrade) bashunit::main::cmd_upgrade "$@" ;;
  assert) bashunit::main::cmd_assert "$@" ;;
  watch) bashunit::main::cmd_watch "$@" ;;
esac
