name: "Extended tests"
cases:
  - name: "Extended test with redirect"
    stdin: |
      [[ $(echo hello >&2) ]] 2>/dev/null
      echo "Result: $?"

  - name: "File extended tests"
    stdin: |
      [[ -a /tmp ]] && echo "-a correctly checked /tmp"
      [[ -a /some/non/existent/path ]] || echo "-a correctly checked non-existent path"

      [[ -c /dev/null ]] && echo "-c correctly checked /dev/null"
      [[ -c /tmp ]] || echo "-c correctly checked /tmp"

  - name: "Existence tests"
    stdin: |
      [[ -e '' ]] || echo "-e correctly identified empty string as non-existent"

      [[ -e non-existent ]] || echo "-e correctly identified non-existent path"
      [[ ! -e non-existent ]] && echo "! -e correctly identified non-existent path"

      touch test-file
      [[ -e test-file ]] && echo "-e correctly identified existing non-dir"

      mkdir test-dir
      [[ -e test-dir ]] && echo "-d correctly identified existing dir"

  - name: "Directory tests"
    stdin: |
      [[ -d '' ]] || echo "-d correctly identified empty string as non-existent"

      [[ -d non-existent ]] || echo "-d correctly identified non-existent path"
      [[ ! -d non-existent ]] && echo "! -d correctly identified non-existent path"

      touch test-file
      [[ -d test-file ]] || echo "-d correctly identified non-dir file"

      mkdir test-dir
      [[ -d test-dir ]] && echo "-d correctly identified existing dir"

  - name: "File regular tests"
    stdin: |
      [[ -f '' ]] || echo "-f correctly identified empty string as non-existent"

      [[ -f non-existent ]] || echo "-f correctly identified non-existent path"
      [[ ! -f non-existent ]] && echo "! -f correctly identified non-existent path"

      touch test-file
      [[ -f test-file ]] && echo "-f correctly identified regular file"

      mkdir test-dir
      [[ -f test-dir ]] || echo "-f correctly identified directory as non-regular file"

  - name: "File symbolic link tests"
    stdin: |
      [[ -L non-existent ]] || echo "-L correctly identified non-existent path"

      touch test-file
      [[ -L test-file ]] || echo "-L correctly identified non-link file"

      ln -s test-file valid-link
      [[ -L valid-link ]] && echo "-L correctly identified valid symbolic link"

      ln -s non-existent-target dangling-link
      [[ -L dangling-link ]] && echo "-L correctly identified dangling symbolic link"

  - name: "File sticky bit tests"
    stdin: |
      touch test-file
      [[ -k test-file ]] || echo "-k correctly identified file without sticky bit"

      chmod 1600 test-file
      [[ -k test-file ]] && echo "-k correctly identified file with sticky bit"

  - name: "Fifo test"
    stdin: |
      touch test-file
      [[ -p test-file ]] || echo "-p correctly identified non-fifo file"

      mkfifo fifo-file
      [[ -p test-file ]] && echo "-p correctly identified fifo file"

  - name: "File executable tests"
    stdin: |
      [[ -x non-existent ]] || echo "-x correctly identified non-existent path"

      touch test-file
      ln -sf link test-file
      [[ -x test-file ]] || echo "-x correctly identified non-executable file"
      [[ -x link ]] || echo "-x correctly identified link to non-executable file"

      chmod o+x test-file
      [[ -x test-file ]] || echo "-x correctly identified other-only-executable file"
      [[ -x link ]] || echo "-x correctly identified link to other-only-executable file"

      chmod a+x test-file
      [[ -x test-file ]] && echo "-x correctly identified executable file"
      [[ -x link ]] && echo "-x correctly identified link to executable file"

  - name: "File writable tests"
    stdin: |
      [[ -w non-existent ]] || echo "-w correctly identified non-existent path"

      touch test-file
      ln -sf link test-file
      [[ -w test-file ]] || echo "-w correctly identified non-writable file"
      [[ -w link ]] || echo "-w correctly identified link to non-writable file"

      chmod o+x test-file
      [[ -w test-file ]] || echo "-w correctly identified other-only-writable file"
      [[ -w link ]] || echo "-w correctly identified link to other-only-writable file"

      chmod a+x test-file
      [[ -w test-file ]] && echo "-w correctly identified writable file"
      [[ -w link ]] && echo "-w correctly identified link to writable file"

  - name: "Variable set and nameref tests"
    stdin: |
      foo="bar"
      declare -n bang=foo
      [[ -R bang ]] && echo "-R correctly identified nameref variable that is set"

      declare -n somevar
      [[ -R somevar ]] || echo "-R correctly identified nameref variable that isn't set"

  - name: "Files refer to same device and inode tests"
    stdin: |
      [[ /bin/sh -ef /bin/sh ]] && echo "-ef correctly identified device and inode numbers"

      [[ ! /etc/os-release -ef /bin/sh ]] && echo "-ef correctly identified device and inode numbers that do not match"

  - name: "File is newer"
    stdin: |
      touch -d "2 hours ago" bar
      touch foo

      [[ foo -nt bar ]] && echo "-nt correctly identified newer file"
      [[ foo -nt foo ]] && echo "-nt incorrectly identified file as newer than itself"
      [[ foo -nt file_no_exists ]] && echo "-nt correctly identified when file2 does not exist"

  - name: "File is older"
    stdin: |
      touch -d "2 hours ago" foo
      touch bar

      [[ foo -ot bar ]] && echo "-ot correctly identified older file"
      [[ foo -ot foo ]] && echo "-ot incorrectly identified file as older than itself"
      [[ file_no_exists -ot foo ]] && echo "-ot correctly identified when file1 does not exist"

  - name: "Unary string extended tests"
    stdin: |
      [[ -z "" ]] && echo "-z: Pass"
      [[ -z "something" ]] && echo "-z: Fail"

      [[ -n "something" ]] && echo "-n: Pass"
      [[ -n "" ]] && echo "-n: Fail"

  - name: "Shell option extended tests"
    stdin: |
      set -o emacs
      [[ -o emacs ]] && echo "1: option enabled"

      set +o emacs
      [[ -o emacs ]] && echo "2: option enabled"

  - name: "Binary string extended tests"
    stdin: |
      [[ "" == "" ]] && echo "1. Pass"
      [[ "" = "" ]] && echo "2. Pass"
      [[ "" != "" ]] && echo "3. Fail"

      [[ "a" != "b" ]] && echo "4. Pass"
      [[ "a" = "b" ]] && echo "5. Fail"
      [[ "a" == "b" ]] && echo "6. Fail"

      [[ "a" < "b" ]] && echo "7. Pass"
      [[ "a" < "a" ]] && echo "8. Fail"
      [[ "b" < "a" ]] && echo "9. Fail"
      [[ "a" > "b" ]] && echo "10. Fail"
      [[ "a" > "a" ]] && echo "11. Fail"
      [[ "b" > "a" ]] && echo "12. Pass"

  - name: "Binary string matching"
    stdin: |
      [[ "abc" == a* ]] && echo "1. Pass"
      [[ "abc" != a* ]] && echo "2. Fail"
      [[ a* != "abc" ]] && echo "3. Pass"

  - name: "Binary string matching with expansion"
    stdin: |
      exclude="0123456789"
      [[ "clue" == +([$exclude]) ]] && echo "1. Fail"
      [[ "8675309" == +([$exclude]) ]] && echo "2. Pass"

  - name: "Quoted pattern binary string matching"
    stdin: |
      [[ "abc" == "a*" ]] && echo "1. Matches"
      [[ "abc" != "a*" ]] && echo "2. Matches"

  - name: "Tilde binary string matching"
    stdin: |
      x='~/'
      [[ $x == ~* ]] && echo "1. Matches"

  - name: "Arithmetic extended tests"
    stdin: |
      [[ 0 -eq 0 ]] && echo "1. Pass"
      [[ 0 -ne 0 ]] && echo "2. Fail"
      [[ 0 -lt 0 ]] && echo "3. Fail"
      [[ 0 -le 0 ]] && echo "4. Pass"
      [[ 0 -gt 0 ]] && echo "5. Fail"
      [[ 0 -ge 0 ]] && echo "6. Pass"

      [[ 0 -eq 1 ]] && echo "7. Fail"
      [[ 0 -ne 1 ]] && echo "8. Pass"
      [[ 0 -lt 1 ]] && echo "9. Pass"
      [[ 0 -le 1 ]] && echo "10. Pass"
      [[ 0 -gt 1 ]] && echo "11. Fail"
      [[ 0 -ge 1 ]] && echo "12. Fail"

      [[ 1 -eq 0 ]] && echo "13. Fail"
      [[ 1 -ne 0 ]] && echo "14. Pass"
      [[ 1 -lt 0 ]] && echo "15. Fail"
      [[ 1 -le 0 ]] && echo "16. Fail"
      [[ 1 -gt 0 ]] && echo "17. Pass"
      [[ 1 -ge 0 ]] && echo "18. Pass"

  - name: "Regex"
    stdin: |
      [[ "a" =~ ^a$ ]]   && echo "1. Pass"
      [[ "abc" =~ a* ]]  && echo "2. Pass"
      [[ a =~ ^(a)$ ]]   && echo "3. Pass"
      [[ a =~ ^(a|b)$ ]] && echo "4. Pass"
      [[ a =~ c ]]       && echo "5. Pass"

  - name: "Regex with case insensitivity"
    stdin: |
      shopt -u nocasematch
      [[ "a" =~ A ]] && echo "1. Pass"

      shopt -s nocasematch
      [[ "a" =~ A ]] && echo "1. Pass"

  - name: "Regex with capture"
    stdin: |
      pattern='(Hello), ([a-z]+)\.'
      if [[ "Hello, world." =~ ${pattern} ]]; then
          echo "Match found!"
          for i in "${!BASH_REMATCH[@]}"; do
              echo "$i: '${BASH_REMATCH[$i]}'"
          done
      fi

  - name: "Regex with capture including optional matches"
    stdin: |
      pattern='(Hello)(,?) ([a-z]+)\.'
      if [[ "Hello world." =~ ${pattern} ]]; then
          echo "Match found!"
          for i in "${!BASH_REMATCH[@]}"; do
              echo "$i: '${BASH_REMATCH[$i]}'"
          done
      fi

  - name: "Regex with quoting"
    stdin: |
      regex="^$"

      # TODO(test): The commented out lines appear differ in behavior between versions of bash.
      # [[ "" =~ ''         ]] && echo "1. Matched"
      [[ "" =~ '^$'       ]] && echo "2. Matched"
      # [[ "" =~ ""         ]] && echo "3. Matched"
      [[ "" =~ "^$"       ]] && echo "4. Matched"
      [[ "" =~ ^$         ]] && echo "5. Matched"
      [[ "" =~ ${regex}   ]] && echo "6. Matched"
      [[ "" =~ "${regex}" ]] && echo "7. Matched"

  - name: "Regex with escaping"
    stdin: |
      [[ '' =~ ^\$$ ]] && echo "1. Matched"

  - name: "Regex with double parens"
    stdin: |
      if [[ xy =~ x+((y)) ]]; then
        echo "Matches"
      fi

  - name: "Regex with special chars in parens"
    stdin: |
      [[ "<" =~ (<) ]] && echo "1. Matched"
      [[ ">" =~ (<) ]] && echo "2. Matched"

  - name: "Regex with spaces inside parens"
    stdin: |
      [[ "x" =~ (a| *) ]]   && echo "1. Matched"
      [[ "a b" =~ ^(a |b) ]] && echo "2. Matched: [${BASH_REMATCH[0]}]"
      [[ "a b" =~ ^(a |b)$ ]] || echo "3. No match"
      [[ " " =~ ^( )$ ]]    && echo "4. Matched: [${BASH_REMATCH[1]}]"

  - name: "Regex with unescaped open bracket in character class"
    stdin: |
      [[ "[" =~ ^([x[]) ]] && echo "Matched"

  - name: "Empty and space checks"
    stdin: |
      check() {
        var="$1"
        [[ ${var} && ! ${var//[[:space:]]/} ]]
      }

      check ""    && echo "1. Only space"
      check " "   && echo "2. Only space"
      check $'\t' && echo "3. Only space"
      check " a " && echo "4. Only space"

  - name: "Newlines in test expression"
    stdin: |
      [[
        "a" == "a"
        &&
        "b" == "b"
      ]] && echo "Succeeded"

  - name: "Variable set checks"
    stdin: |
      declare set_but_no_value
      [[ -v set_but_no_value ]] && echo "1. Set but no value"

      declare set_with_value=xyz
      [[ -v set_with_value ]] && echo "2. Set with value"

      [[ -v not_set ]] || echo "3. Not set"

  - name: "Variables in extended tests"
    stdin: |
      var=10

      [[ $var -eq 10 ]] && echo "1. Pass"
      [[ var -eq 10 ]] && echo "2. Pass"

  - name: "Regex with min/max counts"
    stdin: |
      [[ z =~ ^z{2,6}$ ]] && echo "1. Matches"
      [[ zzzz =~ ^z{2,6}$ ]] && echo "2. Matches"
      [[ zzzzzzzzz =~ ^z{2,6}$ ]] && echo "3. Matches"

  - name: "Regex with newline"
    stdin: |
      [[ $'\n' =~ . ]] && echo "1. Matches"

  - name: "Arithmetic comparisons with whitespace in operands"
    stdin: |
      # Leading/trailing whitespace should be trimmed before comparison
      [[ 0 -eq ' 0' ]] && echo "1. leading space"
      [[ 0 -eq '0 ' ]] && echo "2. trailing space"
      [[ 0 -eq ' 0 ' ]] && echo "3. both spaces"

      # Tabs
      [[ 0 -eq '	0' ]] && echo "4. leading tab"
      [[ 0 -eq '0	' ]] && echo "5. trailing tab"

      # Newlines
      [[ 0 -eq ' 0
      ' ]] && echo "6. newline"

      # Other operators
      [[ 5 -lt ' 10' ]] && echo "7. -lt with space"
      [[ 10 -gt ' 5' ]] && echo "8. -gt with space"
      [[ 5 -ne ' 6' ]] && echo "9. -ne with space"
      [[ 5 -le ' 5' ]] && echo "10. -le with space"
      [[ 5 -ge ' 5' ]] && echo "11. -ge with space"

      # Negative numbers with whitespace
      [[ -5 -eq ' -5' ]] && echo "12. negative with space"

  - name: "Terminal fd check with whitespace"
    stdin: |
      # -t operator should trim whitespace before parsing fd number
      # These should all behave identically (false since not a terminal)
      [[ -t 0 ]]
      r1=$?
      [[ -t ' 0' ]]
      r2=$?
      [[ -t '0 ' ]]
      r3=$?

      # All should have the same result
      [[ $r1 -eq $r2 && $r2 -eq $r3 ]] && echo "All -t results match"

  - name: "Nameref -R with attribute removal"
    stdin: |
      target="value"
      declare -n ref=target
      [[ -R ref ]] && echo "ref is a nameref"
      [[ -R target ]] && echo "target is a nameref" || echo "target is not a nameref"
      declare +n ref
      [[ -R ref ]] && echo "still nameref" || echo "no longer a nameref"

  - name: "Nameref -v follows nameref"
    known_failure: true
    stdin: |
      target="value"
      declare -n ref=target
      [[ -v ref ]] && echo "ref is set (via target)"
      unset target
      [[ -v ref ]] && echo "ref still set" || echo "ref not set (target gone)"

  - name: "Nameref -v to array element treats resolved name literally"
    known_failure: true
    stdin: |
      arr=(a b c d)
      declare -n ref='arr[2]'
      [[ -v ref ]] && echo "set" || echo "unset"

  - name: "Nameref -v with explicit subscript on nameref to whole array"
    # TODO(nameref): brush doesn't yet handle [[ -v "ref[N]" ]] where ref is
    # a nameref to a whole array. Bash resolves ref→arr and then checks arr[2],
    # but brush currently treats the subscript as part of the nameref target
    # string rather than applying it after resolution. The fix belongs in
    # extendedtests.rs (ShellVariableIsSetAndAssigned) — the operand "ref[2]"
    # needs to be split into name="ref" + subscript="2", the nameref resolved
    # on the name portion, then the subscript applied to the resolved target.
    known_failure: true
    stdin: |
      arr=(a b c d)
      declare -n ref2=arr
      [[ -v "ref2[2]" ]] && echo "set" || echo "unset"

  - name: "Nameref -v to nonexistent array index"
    known_failure: true
    stdin: |
      arr=(a b c)
      declare -n ref='arr[5]'
      [[ -v ref ]] && echo "set" || echo "unset"
