#!/bin/bash
# Assert that a program can be derived from the ASG as expected.
#
# See `./README.md` for more information.

set -euo pipefail

mypath=$(dirname "$0")
. "$mypath/../../conf.sh"

# Performing this check within `<()` below won't cause a failure.
: "${P_XMLLINT?}" # conf.sh

run-test() {
  local name="${1?Missing test name}"
  local dir="${2?Missing dir}"
  shift 2

  rm -f "$dir/"*.log

  header "$dir" "$name"

  "test-$name" "$dir" "$@" || {
    local ret=$?
    echo ' [FAIL]'
    return "$ret"
  }

  echo ' [ OK ]'
}

timed-tamec() {
  local dir="${1?Missing directory name}"
  local in="${2?Missing input filename}"
  local out="${3?Missing output filename}"

  dir="${dir%/}" # strip trailing slash, if any (just to style output)

  local -i ret=0

  # /usr/bin/time does not have high enough precision
  # This won't be wholly accurate because time is spent in shell,
  #   but it'll be close enough.
  local -i start_ns=$(date +%s%N)

  local objty=xmlo
  if [ -f "$dir/is-experimental" ]; then
      objty=xmlo-experimental
  fi

  command time -f "%F/%Rfault %I/%Oio %Mrss %c/%wctx \n%C" -o "$dir/time.log" \
    "${TAMER_PATH_TAMEC?}" -o "$dir/$out" --emit "$objty" "$dir/$in" \
    &> "$dir/tamec-$out.log" \
    || ret=$?

  local -i end_ns=$(date +%s%N)
  local -i elapsed_ms=$(( (end_ns - start_ns) / 1000000 ))

  # First line will be "Command exited with non-zero status N" on failure.
  # The last line will be '%C' above.
  # So, we want the second-to-last line.
  #
  # Newline omission is intentional.
  printf "<%3dms %s" "$elapsed_ms" "$(tail -n2 "$dir/time.log" | head -n1)"

  return "$ret"
}

header() {
  # allocate enough space based on the path we'll output
  local -i mypath_len=${#mypath}
  local -i dirlen=$((mypath_len + 14))

  # newline intentionally omitted
  printf "%-${dirlen}s %-20s " "$@"
}

# Derive a program from `src.xml` and verify that it meets our expectations.
#
# This test is inherently fragile, as it will break any time we perform
# certain types of optimizations or change internal representations.  _But
# that is intended._  We want to be well aware of such changes in derivation
# so that we can judge whether it needs adjustment.
test-derive-from-src() {
  local dir="${1?Missing directory name}"

  timed-tamec "$dir" src.xml out.xmli || return

  diff <("$P_XMLLINT" --format "$dir/expected.xml" || echo 'ERR expected.xml') \
       <("$P_XMLLINT" --format "$dir/out.xmli" || echo 'ERR out.xmli') \
       &> "$dir/diff.log"
}


# Having taken `A` and derived `B`, we should be able to take `B` and derive
# `C`, where `B` and `C` are equivalent programs.
#
# That is, this derivation should be transitively equivalent and reach a
# fixpoint on the second derivation.  This serves as a sanity check to
# ensure that the program we generated makes sense to our own system.
#
# Note that, in the future, we'll have to strip handoff metadata
# (`preproc:*` data) from the output so that it will be accepted by TAMER.
test-fixpoint() {
  local dir="${1?Missing directory name}"

  if [ -f "$dir/no-fixpoint" ]; then
    echo -n '!!!WARNING!!! test skipped: `no-fixpoint` file '
    return
  fi

  timed-tamec "$dir" out.xmli out-2.xmli || return

  diff <("$P_XMLLINT" --format "$dir/expected.xml" || echo 'ERR expected.xml') \
       <("$P_XMLLINT" --format "$dir/out-2.xmli" || echo 'ERR out.xmli') \
       &> "$dir/diff.log"
}


main() {
  local -a fail=()

  for dir in "$mypath"/*/; do
    run-test derive-from-src "$dir" && run-test fixpoint "$dir" \
      || fail=("${fail[@]}" "$dir")
  done

  test -z "${fail[@]}" || {
      cat << EOF
!!! TEST FAILED
tamec: $TAMER_PATH_TAMEC

note: The compiler output and diff between the expected and given data
      are below.  Both files are formatted with \`xmllint\` automatically.
EOF

      for dir in "${fail[@]}"; do
        echo
        echo ",=====[ $dir logs ]======"
        echo "|"
        cat "$dir/"*.log | sed 's/^/| /'
        echo "|"
        echo "\`====[ end $dir logs ]===="
      done

      exit 1
    }
}

main "$@"

