#!/bin/sh
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright © 2020–2024 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH
#                       Matthias Kretz <m.kretz@gsi.de>

type=float
abi=0
name=
srcdir="$(cd "${0%/*}" && pwd)/tests"
sim="$GCC_TEST_SIMULATOR"

# output_mode values:
# print only failures with minimal context
readonly really_quiet=0
# as above plus same-line output of last successful test
readonly same_line=1
# as above plus percentage
readonly percentage=2
# print one line per finished test with minimal context on failure
readonly verbose=3
# print one line per finished test with full output of the compiler and test
readonly really_verbose=4

exitcode=0

output_mode=$really_quiet
[ -t 1 ] && output_mode=$same_line

timeout=180
run_expensive=false
if [ -n "$GCC_TEST_RUN_EXPENSIVE" ]; then
  run_expensive=true
fi
keep_failed=false
only=

usage() {
  cat <<EOF
Usage: $0 [Options] <g++ invocation>

Options:
  -h, --help          Print this message and exit.
  -q, --quiet         Disable same-line progress output (default if stdout is
                      not a tty).
  -p, --percentage    Add percentage to default same-line progress output.
  -v, --verbose       Print one line per test and minimal extra information on
                      failure.
  -vv                 Print all compiler and test output.
  -t <type>, --type <type>
                      The value_type to test (default: $type).
  -a [0-9], --abi [0-9]
                      The ABI tag subset to test (default: $abi).
  -n <name>, --name <name>
                      The name of the test (required).
  -k, --keep-failed   Keep executables of failed tests.
  -f, --fail          Exit with non-zero value on failure.
  --srcdir <path>     The source directory of the tests (default: $srcdir).
  --sim <executable>  Path to an executable that is prepended to the test
                      execution binary (default: the value of
                      GCC_TEST_SIMULATOR).
  --timeout-factor <x>
                      Multiply the default timeout with x.
  -x, --run-expensive Compile and run tests marked as expensive (default:
                      true if GCC_TEST_RUN_EXPENSIVE is set, false otherwise).
  -o <pattern>, --only <pattern>
                      Compile and run only tests matching the given pattern.
EOF
}

while [ $# -gt 0 ]; do
  case "$1" in
  -h|--help)
    usage
    exit
    ;;
  -f|--fail)
    exitcode=2
    ;;
  -q|--quiet)
    output_mode=$really_quiet
    ;;
  -p|--percentage)
    output_mode=$percentage
    ;;
  -v|--verbose)
    if [ $output_mode -lt $verbose ]; then
      output_mode=$verbose
    else
      output_mode=$really_verbose
    fi
    ;;
  -x|--run-expensive)
    run_expensive=true
    ;;
  -k|--keep-failed)
    keep_failed=true
    ;;
  -o|--only)
    only="$2"
    shift
    ;;
  -t|--type)
    type="$2"
    shift
    ;;
  -a|--abi)
    abi="$2"
    shift
    ;;
  -n|--name)
    name="$2"
    shift
    ;;
  --srcdir)
    srcdir="$2"
    shift
    ;;
  --sim)
    sim="$2"
    shift
    ;;
  --timeout-factor)
    timeout=$(awk "BEGIN { print int($timeout * $2) }")
    shift
    ;;
  --)
    shift
    break
    ;;
  --*=*)
    opt="$1"
    shift
    value=${opt#*=}
    set -- ${opt%=$value} "$value" ${1+"$@"}
    continue
    ;;
  -[afhknopqtvx][afhknopqtvx]*)
    opt="$1"
    shift
    next=${opt#??}
    set -- ${opt%$next} "-$next" ${1+"$@"}
    continue
    ;;
  -*)
    echo "Error: Unrecognized option '$1'" >&2
    exit 1
    ;;
  *)
    break
    ;;
  esac
  shift
done

if [ $output_mode = $percentage ]; then
  inc_progress() {
    {
      flock -n 9
      n=$(($(cat .progress) + 1))
      echo $n >&9
      echo $n
    } 9<>.progress
  }
fi

CXX="$1"
shift
CXXFLAGS="$@"
src="${srcdir}/${name}.cc"
shorttype=$(echo $type|sed -e 's/long /l/' -e 's/unsigned /u/' -e 's/signed /s/')
testname="${name}-${shorttype}-${abi}"
case "$($CXX -dumpmachine)" in
  wasm*emscripten)
    exe="${testname}.js"
    if [ -z "$sim" ]; then
      sim=node
    fi
    ;;
  *)
    exe="${testname}.exe"
    ;;
esac
log="${testname}.log"
sum="${testname}.sum"
if [ -n "$only" ]; then
  if echo "$testname"|awk "{ exit /$only/ }"; then
    touch "$log" "$sum"
    [ $output_mode = $percentage ] && inc_progress >/dev/null
    exit 0
  fi
fi

if [ $abi -eq 0 ]; then
  abiflag=""
elif [ $abi -gt 0 -a $abi -lt 10 ]; then
  abiflag="-DEXTENDEDTESTS=$((abi-1))"
else
  echo "Error: The -a argument must be a value between 0 and 9 (inclusive)." >&2
  exit 1
fi

if [ $output_mode = $percentage ]; then
  show_progress() {
    n=$(inc_progress)
    read total < .progress_total
    total=${total}0
    printf "\e[1G\e[K[%3d %%] ${src##*/} $type $abiflag" \
      $((n * 1005 / total))
  }
  trap 'show_progress' EXIT
  prefix="\e[1G\e[K"
elif [ $output_mode = $same_line ]; then
  show_progress() {
    printf "\e[1G\e[K${src##*/} $type $abiflag"
  }
  trap 'show_progress' EXIT
  prefix="\e[1G\e[K"
else
  prefix=""
fi

fail() {
  printf "$prefix"
  echo "FAIL: $src $type $abiflag ($*)" | tee -a "$sum" "$log"
}

xpass() {
  printf "$prefix"
  echo "XPASS: $src $type $abiflag ($*)" | tee -a "$sum" "$log"
}

xfail() {
  [ $output_mode -ge $verbose ] && echo "XFAIL: $src $type $abiflag ($*)"
  echo "XFAIL: $src $type $abiflag ($*)" >> "$sum"
  echo "XFAIL: $src $type $abiflag ($*)" >> "$log"
}

pass() {
  [ $output_mode -ge $verbose ] && echo "PASS: $src $type $abiflag ($*)"
  echo "PASS: $src $type $abiflag ($*)" >> "$sum"
  echo "PASS: $src $type $abiflag ($*)" >> "$log"
}

unsupported() {
  [ $output_mode -ge $verbose ] && echo "UNSUPPORTED: $src $type $abiflag ($*)"
  echo "UNSUPPORTED: $src $type $abiflag ($*)" >> "$sum"
  echo "UNSUPPORTED: $src $type $abiflag ($*)" >> "$log"
}

write_log_and_verbose() {
  echo "$*" >> "$log"
  if [ $output_mode = $really_verbose ]; then
    if [ -z "$COLUMNS" ] || ! type fmt>/dev/null; then
      echo "$*"
    else
      echo "$*" | fmt -w $COLUMNS -s - || cat
    fi
  fi
}

matches() {
  eval "case '$1' in
    $2) return 0;; esac"
  return 1
}

test_selector() {
  string="$1"
  pat_type="${string%% *}"
  if matches "$shorttype" "$pat_type"; then
    string="${string#* }"
    pat_abi="${string%% *}"
    if matches "$abi" "$pat_abi"; then
      string="${string#* }"
      pat_triplet="${string%% *}"
      [ -z "$target_triplet" ] && target_triplet=$($CXX -dumpmachine)
      if matches "$target_triplet" "$pat_triplet"; then
        string="${string#* }"
        pat_flags="${string%% \'*\'}"
        if matches "$CXXFLAGS" "*$pat_flags*"; then
          if test "$string" = "$pat_flags"; then
            return 0;
          fi
          shelltest="${string#* \'}"
          shelltest="${shelltest%\'}"
          if [ -n "$shelltest" ]; then
            eval "$shelltest" && return 0
          else
            return 0
          fi;
        fi
      fi
    fi
  fi
  return 1
}

trap "rm -f '$log' '$sum' $exe; exit" INT
rm -f "$log" "$sum"
touch "$log" "$sum"

read_src_option() {
  local key tmp var
  key="$1"
  var="$2"
  [ -z "$var" ] && var="$1"
  local tmp="$(head -n25 "$src" | grep "^//\\s*${key}: ")"
  if [ -n "$tmp" ]; then
    tmp="$(echo "${tmp#//*${key}: }" | sed -e 's/ \+/ /g' -e 's/^ //' -e 's/$//')"
    eval "$var=\"$tmp\""
  else
    return 1
  fi
}

if read_src_option skip; then
  if test_selector "$skip"; then
    # silently skip this test
    exit 0
  fi
fi
if read_src_option only; then
  if ! test_selector "$only"; then
    # silently skip this test
    exit 0
  fi
fi

if ! $run_expensive; then
  if read_src_option expensive; then
    if test_selector "$expensive"; then
      unsupported "skip expensive tests"
      exit 0
    fi
  fi
fi

if read_src_option xfail; then
  if test_selector "${xfail#* }"; then
    xfail="${xfail%% *}"
  else
    unset xfail
  fi
fi

read_src_option timeout

if read_src_option timeout-factor factor; then
  timeout=$(awk "BEGIN { print int($timeout * $factor) }")
fi

log_output() {
  if [ $output_mode = $really_verbose ]; then
    maxcol=${1:-1024}
    awk "
BEGIN { count = 0 }
/^###exitstatus### [0-9]+$/ { exit \$2 }
{
  print >> \"$log\"
  if (count >= 1000) {
    print \"Aborting: too much output\" >> \"$log\"
    print \"Aborting: too much output\"
    exit 125
  }
  ++count
  if (length(\$0) > $maxcol) {
    i = 1
    while (i + $maxcol <= length(\$0)) {
      len = $maxcol
      line = substr(\$0, i, len)
      len = match(line, / [^ ]*$/)
      if (len <= 0) {
        len = match(substr(\$0, i), / [^ ]/)
        if (len <= 0) len = $maxcol
      }
      print substr(\$0, i, len)
      i += len
    }
    print substr(\$0, i)
  } else {
    print
  }
}
END { close(\"$log\") }
"
  else
    awk "
BEGIN { count = 0 }
/^###exitstatus### [0-9]+$/ { exit \$2 }
{
  print >> \"$log\"
  if (count >= 1000) {
    print \"Aborting: too much output\" >> \"$log\"
    print \"Aborting: too much output\"
    exit 125
  }
  ++count
}
END { close(\"$log\") }
"
  fi
}

verify_compilation() {
  log_output $COLUMNS
  exitstatus=$?
  if [ $exitstatus -eq 0 ]; then
    warnings=$(grep -ic 'warning:' "$log")
    if [ $warnings -gt 0 ]; then
      fail "excess warnings:" $warnings
      if [ $output_mode = $verbose ]; then
        grep -i 'warning:' "$log" | head -n5
      fi
    elif [ "$xfail" = "compile" ]; then
      xpass "test for excess errors"
    else
      pass "test for excess errors"
    fi
    return 0
  else
    if [ $exitstatus -eq 124 ]; then
      fail "timeout: test for excess errors"
    else
      errors=$(grep -ic 'error:' "$log")
      if [ "$xfail" = "compile" ]; then
        xfail "excess errors:" $errors
        exit 0
      else
        fail "excess errors:" $errors
      fi
    fi
    if [ $output_mode = $verbose ]; then
      grep -i 'error:' "$log" | head -n5
    fi
    return 1
  fi
}

verify_test() {
  log_output $COLUMNS
  exitstatus=$?
  if [ $exitstatus -eq 0 ]; then
    if [ "$xfail" = "run" ]; then
      $keep_failed || rm "$exe"
      xpass "execution test"
    else
      rm "$exe"
      pass "execution test"
    fi
    return 0
  else
    $keep_failed || rm "$exe"
    if [ $output_mode = $verbose ]; then
      grep -i fail "$log" | head -n5
    fi
    if [ $exitstatus -eq 124 ]; then
      fail "timeout: execution test"
    elif [ "$xfail" = "run" ]; then
      xfail "execution test"
      exit 0
    else
      fail "execution test"
    fi
    return 1
  fi
}

write_log_and_verbose "$CXX $src $@ -D_GLIBCXX_SIMD_TESTTYPE=$type $abiflag -o $exe"
{
  # Custom timeout mechanism for killing $CXX together with its children while
  # also handling SIGINT on the driver script correctly. (timeout --foreground
  # does fails to kill the cc1plus child, thrashing the machine.)
  sh -s -- "$@" <<EOF
    pid=\$\$
    (
      t=$timeout
      while [ \$t -gt 0 ]; do
        sleep 1s
        kill -0 \$pid || exit 0
        t=\$((t-1))
      done
      kill \$pid
      echo "###exitstatus### 124"
    ) 2>/dev/null &
    "$CXX" "$src" "\$@" "-D_GLIBCXX_SIMD_TESTTYPE=$type" $abiflag -c -o "$exe.o" 2>&1 <&-
    "$CXX" "$exe.o" "\$@" -o "$exe" 2>&1 <&-
    rm "$exe.o" 2>&1 <&-
EOF
  printf "###exitstatus### %d\n" $?
} | verify_compilation || exit $exitcode
if [ -n "$sim" ]; then
  write_log_and_verbose "$sim ./$exe"
else
  write_log_and_verbose "./$exe"
  timeout=$(awk "BEGIN { print int($timeout / 2) }")
fi
{
  timeout --foreground $timeout $sim "./$exe" 2>&1 <&-
  printf "###exitstatus### %d\n" $?
} | verify_test || exit $exitcode

# vim: sw=2 et cc=81 si
