name: "Builtins: trap"
cases:
  #
  # trap -l signal listing
  #
  - name: "trap -l - lists all signal names"
    stdin: |
      # Just verify it outputs something reasonable - check common signals exist
      trap -l | grep -qE 'HUP|SIGHUP' && echo "has HUP"
      trap -l | grep -qE 'INT|SIGINT' && echo "has INT"
      trap -l | grep -qE 'TERM|SIGTERM' && echo "has TERM"

  #
  # Basic trap registration
  #
  - name: "trap registration"
    stdin: |
      trap "echo 1" SIGINT
      trap "echo 2" SIGINT
      trap -p INT

      trap "echo 3" int
      trap -p INT

      trap "echo 4" 2
      trap -p INT

  - name: "trap unregistering"
    stdin: |
      echo "[Case 1]"
      trap "echo 1" SIGINT
      trap SIGINT
      trap -p INT

      echo "[Case 2]"
      trap "echo 2" SIGINT
      trap - SIGINT
      trap -p INT

  - name: "trap EXIT"
    stdin: |
      trap 'echo "[exit]"' EXIT
      trap -p EXIT

  - name: "trap EXIT: status code handling"
    stdin: |
      trap 'echo "[exit]: \$?: $?"' EXIT
      trap -p EXIT
      false

  - name: "trap DEBUG"
    stdin: |
      trap 'echo [command: ${BASH_COMMAND}]' DEBUG
      trap -p DEBUG

      echo First
      echo Second
      false

  - name: "trap ERR - registration only"
    stdin: |
      trap "echo [err]" ERR
      trap -p ERR

  - name: "trap ERR - basic invocation"
    stdin: |
      trap 'echo "ERR: $?"' ERR
      false
      echo "continues"
      false
      echo "continues again"

  - name: "trap ERR - not triggered in conditional"
    stdin: |
      trap 'echo "ERR trapped"' ERR
      if false; then
        echo "then"
      else
        echo "else"
      fi
      echo "after"

  - name: "trap ERR - not triggered with && short-circuit"
    stdin: |
      trap 'echo "ERR trapped"' ERR
      false && echo "not printed"
      echo "after"

  - name: "trap ERR - not triggered with ||"
    stdin: |
      trap 'echo "ERR trapped"' ERR
      false || echo "fallback"
      echo "after"

  - name: "trap ERR - not triggered with negation"
    stdin: |
      trap 'echo "ERR trapped"' ERR
      ! false
      echo "after"

  - name: "trap ERR - not triggered in while condition"
    stdin: |
      trap 'echo "ERR"' ERR
      while false; do
        echo "loop body"
      done
      echo "after while"

  - name: "trap ERR - not triggered in until condition"
    stdin: |
      trap 'echo "ERR"' ERR
      count=0
      until false; do
        ((count++))
        if ((count > 2)); then break; fi
      done
      echo "after until"

  - name: "trap ERR - not triggered as part of || list"
    stdin: |
      trap 'echo "ERR"' ERR
      false || true || false
      echo "after"

  - name: "trap ERR - not triggered for negated command"
    stdin: |
      trap 'echo "ERR"' ERR
      if ! true; then
        echo "then"
      fi
      ! false
      echo "after"

  - name: "trap ERR - triggered after && chain completes then fails"
    stdin: |
      trap 'echo "ERR trapped"' ERR
      true && true
      false
      echo "after"

  - name: "trap ERR - execution doesn't affect control flow"
    stdin: |
      trap 'echo "ERR"' ERR
      result=0
      false || result=$?
      echo "result: $result"

  - name: "trap ERR - multiple failures trigger multiple traps"
    stdin: |
      count=0
      trap '((count++)); echo "ERR $count"' ERR
      false
      false
      false
      echo "total: $count"

  - name: "trap ERR - has access to exit status"
    stdin: |
      trap 'echo "ERR with status: $?"' ERR
      (exit 42)
      echo "after"

  - name: "trap ERR - has access to BASH_COMMAND"
    known_failure: true # TODO(traps): ERR trap basic execution not implemented
    stdin: |
      trap 'echo "ERR for: $BASH_COMMAND"' ERR
      nonexistent_command_xyz 2>/dev/null
      echo "after"

  - name: "trap ERR - has access to LINENO"
    known_failure: true # TODO(traps): ERR trap basic execution not implemented
    stdin: |
      trap 'echo "ERR at line: $LINENO"' ERR
      false
      echo "after"

  - name: "return in EXIT trap within function"
    known_failure: true # TODO(traps): return in trap handler behavior differs
    stdin: |
      myfunc() {
        trap 'return 0' EXIT
        false
        echo "should not print in func"
      }
      myfunc
      echo "after func"

  - name: "subshell inherits signal traps (for trap -p display)"
    stdin: |
      trap 'echo "[parent int]"' INT
      trap 'echo "[parent term]"' TERM
      trap -p INT
      echo "---"
      (trap -p INT; trap -p TERM)

  - name: "subshell can override inherited trap"
    stdin: |
      trap 'echo "[parent int]"' INT
      trap -p INT
      echo "---"
      (trap 'echo "[subshell int]"' INT; trap -p INT)

  - name: "subshell inherits EXIT trap"
    stdin: |
      trap 'echo "[exit]"' EXIT
      trap -p EXIT
      echo "---"
      (trap -p EXIT)

  - name: "subshell inherits DEBUG trap"
    stdin: |
      trap 'echo "[debug]"' DEBUG
      trap -p DEBUG
      echo "---"
      (trap -p DEBUG)

  - name: "subshell does not execute inherited DEBUG trap"
    stdin: |
      trap 'echo "[debug]"' DEBUG
      echo "parent"
      echo "---"
      (echo "subshell")
      echo "back in parent"

  - name: "subshell can set its own EXIT trap"
    known_failure: true # TODO(traps): EXIT trap in subshell
    stdin: |
      trap 'echo "[parent exit]"' EXIT
      echo "parent"
      (trap 'echo "[subshell exit]"' EXIT; echo "subshell")
      echo "back in parent"

  - name: "subshell inherited EXIT trap does not fire"
    stdin: |
      trap 'echo "[exit]"' EXIT
      echo "parent"
      (echo "subshell")
      echo "back in parent"

  #
  # Subshell trap inheritance in command substitution
  #
  - name: "command substitution inherits signal trap (trap -p display)"
    stdin: |
      trap 'echo "[int]"' INT
      result=$(trap -p INT)
      echo "$result"

  - name: "command substitution does not fire inherited signal trap"
    known_failure: true # TODO(signals): signal handling in command substitution
    stdin: |
      trap 'echo "[int]" >&2' INT
      result=$(kill -INT $$; echo "after kill")
      echo "result: $result"

  - name: "command substitution can set its own trap"
    stdin: |
      trap 'echo "[parent int]"' INT
      result=$(trap 'echo "[subshell int]"' INT; trap -p INT)
      echo "$result"

  - name: "command substitution EXIT trap not inherited from parent"
    stdin: |
      trap 'echo "[parent exit]"' EXIT
      result=$(trap -p EXIT; echo "inside")
      echo "result: $result"

  - name: "command substitution can have its own EXIT trap"
    known_failure: true # TODO: EXIT trap in command substitution subshell
    stdin: |
      trap 'echo "[parent exit]"' EXIT
      result=$(trap 'echo "[sub exit]"' EXIT; echo "inside")
      echo "result: $result"

  - name: "nested command substitution trap inheritance"
    stdin: |
      trap 'echo "[int]"' INT
      outer=$(inner=$(trap -p INT); echo "inner: $inner")
      echo "outer: $outer"

  #
  # Subshell trap inheritance in pipelines
  #
  - name: "pipeline component inherits signal trap (trap -p display)"
    stdin: |
      trap 'echo "[int]"' INT
      trap -p INT | cat

  - name: "pipeline component can override trap"
    stdin: |
      trap 'echo "[parent int]"' INT
      { trap 'echo "[pipe int]"' INT; trap -p INT; } | cat

  - name: "pipeline EXIT trap behavior"
    stdin: |
      trap 'echo "[exit]"' EXIT
      { trap -p EXIT; echo "in pipe"; } | cat
      echo "after pipe"

  - name: "multiple pipeline stages trap inheritance"
    stdin: |
      trap 'echo "[int]"' INT
      trap -p INT | cat | cat

  #
  # Subshell trap inheritance in process substitution
  #
  - name: "process substitution inherits signal trap"
    known_failure: true # TODO: process substitution trap inheritance
    stdin: |
      trap 'echo "[int]"' INT
      cat <(trap -p INT)

  - name: "process substitution output trap inheritance"
    skip: true # TODO: process substitution trap inheritance / inconsistent across distros
    stdin: |
      trap 'echo "[int]"' INT
      echo "data" > >(trap -p INT; cat > /dev/null)
      # Give time for the process substitution to complete
      wait

  - name: "process substitution can set own EXIT trap"
    known_failure: true # TODO: EXIT trap in process substitution
    stdin: |
      trap 'echo "[parent exit]"' EXIT
      cat <(trap 'echo "[sub exit]"' EXIT; echo "inside")
      echo "after"

  #
  # Subshell trap inheritance in background jobs
  #
  - name: "background job inherits signal trap (trap -p display)"
    stdin: |
      trap 'echo "[int]"' INT
      trap -p INT &
      wait

  - name: "background job does not fire inherited signal trap"
    known_failure: true # TODO(signals): background job signal handling
    stdin: |
      trap 'echo "[int]" >&2' INT
      { kill -INT $$; echo "after kill"; } &
      wait
      echo "main done"

  - name: "background job EXIT trap behavior"
    stdin: |
      trap 'echo "[exit]"' EXIT
      { trap -p EXIT; echo "bg job"; } &
      wait
      echo "main continues"

  - name: "background job can set own traps"
    stdin: |
      trap 'echo "[parent int]"' INT
      { trap 'echo "[bg int]"' INT; trap -p INT; } &
      wait
      trap -p INT

  #
  # Coproc trap inheritance (all coproc tests consolidated here)
  #
  # Note: "coproc - inherits signal trap" test is skipped due to bash version
  # differences. Older bash resets traps in coproc, newer bash (5.3.9) inherits them.
  # This causes inconsistent test results across CI runners.
  - name: "coproc - EXIT trap behavior"
    skip: true # TODO: this test is too inconsistent
    min_oracle_version: "5.3.0"
    incompatible_os:
      - fedora
      - arch
    stdin: |
      trap 'echo "[exit]"' EXIT
      coproc { trap -p EXIT; echo "coproc"; }
      cat <&${COPROC[0]}
      wait
      echo "main continues"

  - name: "coproc - DEBUG trap inheritance"
    skip: true # TODO: this test is too inconsistent
    min_oracle_version: "5.3.0"
    incompatible_os:
      - fedora
    stdin: |
      trap 'echo "[debug]"' DEBUG
      coproc { trap -p DEBUG; echo "coproc"; }
      cat <&${COPROC[0]}
      wait

  - name: "coproc - ERR trap with errtrace"
    skip: true # TODO: this test is too inconsistent
    min_oracle_version: "5.3.0"
    incompatible_os:
      - fedora
      - arch
    stdin: |
      set -E
      trap 'echo "[err]"' ERR
      coproc { trap -p ERR; false; echo "after false"; }
      cat <&${COPROC[0]}
      wait

  #
  # RETURN trap - basic behavior
  #
  - name: "RETURN trap - fires on function return"
    stdin: |
      trap 'echo "[return]"' RETURN
      myfunc() {
        echo "in func"
      }
      myfunc
      echo "after func"

  - name: "RETURN trap - fires with return value"
    stdin: |
      trap 'echo "[return: $?]"' RETURN
      myfunc() {
        echo "in func"
        return 42
      }
      myfunc
      echo "after func"

  - name: "RETURN trap - fires on sourced script completion"
    known_failure: true # TODO(traps): RETURN trap not firing for sourced scripts
    test_files:
      - path: "sourced.sh"
        contents: |
          echo "in sourced"
    stdin: |
      trap 'echo "[return]"' RETURN
      . ./sourced.sh
      echo "after source"

  - name: "RETURN trap - not inherited in function without functrace"
    stdin: |
      set +T
      trap 'echo "[return]"' RETURN
      outer() {
        inner() {
          echo "in inner"
        }
        inner
        echo "after inner"
      }
      outer
      echo "done"

  - name: "RETURN trap - inherited with functrace"
    known_failure: true # TODO(traps): RETURN trap inheritance with functrace not implemented
    stdin: |
      set -T
      trap 'echo "[return: ${FUNCNAME[0]:-main}]"' RETURN
      outer() {
        inner() {
          echo "in inner"
        }
        inner
        echo "after inner"
      }
      outer
      echo "done"

  - name: "RETURN trap - in explicit subshell"
    stdin: |
      trap 'echo "[return]"' RETURN
      (
        myfunc() { echo "in func"; }
        myfunc
      )
      echo "after subshell"

  - name: "RETURN trap - does not fire for regular commands"
    stdin: |
      trap 'echo "[return]"' RETURN
      echo "cmd1"
      echo "cmd2"
      true
      echo "done"

  - name: "RETURN trap - does not fire for subshell completion"
    stdin: |
      trap 'echo "[return]"' RETURN
      (echo "in subshell")
      echo "after subshell"

  #
  # Signal trap execution verification
  #
  - name: "trap SIGUSR1"
    known_failure: true # TODO(signals): signal handling
    stdin: |
      trap 'echo "[usr1 received]"' USR1
      trap -p USR1
      kill -USR1 $$
      echo "after signal"

  - name: "trap SIGUSR2"
    known_failure: true # TODO(signals): signal handling
    stdin: |
      trap 'echo "[usr2 received]"' SIGUSR2
      trap -p USR2
      kill -USR2 $$
      echo "after signal"

  - name: "trap multiple signals with same handler"
    known_failure: true # TODO(signals): signal handling
    stdin: |
      trap 'echo "[signal received]"' USR1 USR2
      kill -USR1 $$
      kill -USR2 $$
      echo "done"

  - name: "trap handler can inspect signal"
    known_failure: true # TODO(signals): signal handling
    stdin: |
      handler() {
        echo "handler called"
      }
      trap handler USR1
      kill -USR1 $$
      echo "after"

  #
  # Trap reset and unset
  #
  - name: "trap - clears trap"
    stdin: |
      trap 'echo "[int]"' INT
      trap -p INT
      trap - INT
      trap -p INT

  - name: "trap '' ignores signal"
    known_failure: true # TODO(signals): signal handling
    stdin: |
      trap '' INT
      trap -p INT
      kill -INT $$
      echo "survived"

  - name: "trap with empty string can be cleared"
    stdin: |
      trap '' INT
      trap -p INT
      trap - INT
      trap -p INT

  #
  # trap -p edge cases
  #
  - name: "trap -p - with no args shows all traps"
    stdin: |
      trap 'echo a' INT
      trap 'echo b' TERM
      trap 'echo c' EXIT
      trap -p | sort

  - name: "trap -p - with specific signal"
    stdin: |
      trap 'echo "[int]"' INT
      trap 'echo "[term]"' TERM
      trap -p INT
      trap -p TERM

  - name: "trap -p - with multiple signals"
    stdin: |
      trap 'echo "[int]"' INT
      trap 'echo "[term]"' TERM
      trap 'echo "[usr1]"' USR1
      trap -p INT TERM USR1

  - name: "trap -p - for unset trap shows nothing"
    stdin: |
      trap -p INT
      echo "done"

  - name: "trap -p - output is re-executable"
    stdin: |
      trap 'echo "hello world"' INT
      eval "$(trap -p INT)"
      trap -p INT

  #
  # Trap handler quoting
  #
  - name: "trap handler - single quotes preserved"
    known_failure: true # TODO(traps): quote escaping in trap -p output differs
    stdin: |
      trap 'echo '\''quoted'\''' INT
      trap -p INT

  - name: "trap handler - double quotes in handler"
    stdin: |
      trap 'echo "double quoted"' INT
      trap -p INT

  - name: "trap handler - special characters"
    stdin: |
      trap 'echo $HOME; echo "done"' INT
      trap -p INT

  - name: "trap handler - newlines in handler"
    stdin: |
      trap 'echo line1
      echo line2' INT
      trap -p INT

  #
  # DEBUG trap - basic behavior
  #
  - name: "DEBUG trap - sees BASH_COMMAND"
    known_failure: true # TODO(BASH_COMMAND): not showing assignment, quotes differ
    stdin: |
      trap 'echo "cmd: $BASH_COMMAND"' DEBUG
      x=1
      echo "x is $x"

  - name: "DEBUG trap - sees LINENO"
    known_failure: true # TODO(traps): LINENO in DEBUG trap not accurate
    stdin: |
      trap 'echo "line: $LINENO"' DEBUG
      echo first
      echo second

  - name: "DEBUG trap - fires before each simple command"
    known_failure: true # TODO(traps): DEBUG trap not firing for trap command itself
    stdin: |
      count=0
      trap '((count++))' DEBUG
      echo a
      echo b
      echo c
      echo "count: $count"

  - name: "DEBUG trap - in loop"
    known_failure: true # TODO(BASH_COMMAND): quotes differ
    stdin: |
      trap 'echo "[debug: $BASH_COMMAND]"' DEBUG
      for i in 1 2; do
        echo "iter $i"
      done

  #
  # EXIT trap - advanced cases
  #
  - name: "EXIT trap - has access to exit status"
    stdin: |
      trap 'echo "exiting with: $?"' EXIT
      exit 42

  - name: "EXIT trap - runs even after errexit"
    stdin: |
      set -e
      trap 'echo "[exit trap ran]"' EXIT
      false
      echo "not reached"

  - name: "EXIT trap - can modify exit status"
    known_failure: true # TODO(traps): EXIT trap exit doesn't modify status
    stdin: |
      trap 'exit 0' EXIT
      exit 99

  - name: "EXIT trap - multiple assignments last one wins"
    stdin: |
      trap 'echo "first"' EXIT
      trap 'echo "second"' EXIT
      echo "done"

  #
  # EXIT trap in -c mode
  #
  - name: "EXIT trap fires in -c mode"
    args:
      - "-c"
      - "trap 'echo exiting' EXIT; echo hello"
    expected_stdout: |
      hello
      exiting

  - name: "EXIT trap in -c mode has access to exit status"
    args:
      - "-c"
      - "trap 'echo \"status: $?\"' EXIT; exit 42"
    expected_stdout: "status: 42\n"

  - name: "EXIT trap in -c mode runs after errexit"
    args:
      - "-c"
      - "set -e; trap 'echo trapped' EXIT; false"
    expected_stdout: "trapped\n"

  - name: "EXIT trap in -c mode with multiple assignments last one wins"
    args:
      - "-c"
      - "trap 'echo first' EXIT; trap 'echo second' EXIT; echo done"
    expected_stdout: |
      done
      second

  #
  # Trap with multiple signals
  #
  - name: "trap - clear multiple signals at once"
    stdin: |
      trap 'echo "[handler]"' INT TERM USR1
      trap -p INT
      trap -p TERM
      trap -p USR1
      echo "---"
      trap - INT TERM USR1
      trap -p INT
      trap -p TERM
      trap -p USR1
      echo "done"

  - name: "trap - ignore multiple signals"
    stdin: |
      trap '' INT TERM
      trap -p INT
      trap -p TERM
      echo "done"
