#!/bin/sh
# Copyright (C) 2023 bkil.hu
# Refer to the GNU GPL v2 in LICENSE for terms

# environment variables:
# * ISSMALLBIN: 1 = size optimization experiments
# * ISQUICK: 1 = skip valgrind and run tests natively
# * ISSIMPLE: 1 = reduce complexity, never deallocate memory

test_coverage() {
  if ! [ -f "vm.gcda" ]; then
    [ "$PHASERES" = 0 ] &&
      echo "error: missing vm.gcda" >&2
    return 1
  fi

  gcov \
    --all-blocks \
    --relative-only \
    *.gcda > /dev/null || return 1

  grep -H '' vm.c.gcov vm-free.c.inc.gcov 2>/dev/null |
  sed '
    /\/\* coverage:no \*\//,/\/\* \/coverage:no \*\//d
    /\/\* coverage:unreachable \*\//,/\/\* \/coverage:unreachable \*\//d
    ' |
  if [ "$PHASE" = "smoke" ]; then
    sed -n '
      /\/\* coverage:stderr \*\//,/\/\* \/coverage:stderr \*\//p
      /\/\* coverage:smoke \*\//,/\/\* \/coverage:smoke \*\//p
      /\/\* coverage:smokesystem \*\//,/\/\* \/coverage:smokesystem \*\//p
      /\/\* coverage:file \*\//,/\/\* \/coverage:file \*\//p
      /\/\* coverage:netsmoke \*\//,/\/\* \/coverage:netsmoke \*\//p
      '
  else
    sed '
      /\/\* coverage:stderr \*\//,/\/\* \/coverage:stderr \*\//d
      /\/\* coverage:smoke \*\//,/\/\* \/coverage:smoke \*\//d
      /\/\* coverage:file \*\//,/\/\* \/coverage:file \*\//d
      /\/\* coverage:netsmoke \*\//,/\/\* \/coverage:netsmoke \*\//d
      '
  fi |

  if [ "$PHASE" = "system" ]; then
    sed -n '
      /\/\* coverage:stdin \*\//,/\/\* \/coverage:stdin \*\//p
      /\/\* coverage:stdout \*\//,/\/\* \/coverage:stdout \*\//p
      /\/\* coverage:net \*\//,/\/\* \/coverage:net \*\//p
      '
  else
    sed '
      /\/\* coverage:stdin \*\//,/\/\* \/coverage:stdin \*\//d
      /\/\* coverage:stdout \*\//,/\/\* \/coverage:stdout \*\//d
      /\/\* coverage:net \*\//,/\/\* \/coverage:net \*\//d
      '
  fi |

  if  [ "$PHASE" = "smoke" ] || [ "$PHASE" = "system" ]; then
    cat
  else
    sed '
      /\/\* coverage:smokesystem \*\//,/\/\* \/coverage:smokesystem \*\//d
      '
  fi |

  {
    ! grep -B1 '^[^:]*:\s*[#$]'
  }
}

phase() {
  local PHASE SRC BIN PHASERES VALGRINDOPTS
  readonly PHASE="$1"
  PHASERES=0

  case "$PHASE" in
    module)
      SRC="test-vm.c testutil.c"
      BIN="module.out"
      ;;
    system)
      SRC="run.c"
      BIN="js0"
      ;;
    smoke)
      SRC="test-smoke.c testutil.c"
      BIN="smoke.out"
      ;;
    *)
      echo "unknown phase $PHASE" >&2
      return 1
  esac

  echo "Testing phase $PHASE" >&2
  rm "$BIN" *.gcda *.gcov 2>/dev/null
  compile -flto vm.o -o "$BIN" $SRC || return 1

  readonly VALGRINDOPTS="
    --error-exitcode=2 \
    --leak-check=full \
    --show-leak-kinds=all \
    --num-callers=30 \
    --malloc-fill=0xaa \
    --free-fill=0x55 \
    --redzone-size=256 \
    --expensive-definedness-checks=yes \
    --merge-recursive-frames=10 \
    --partial-loads-ok=no \
  "

  if [ "$PHASE" = "system" ] && [ -n "$ISQUICK" ]; then
    ./systest.sh "./$BIN"
  elif [ "$PHASE" = "system" ]; then
    ./systest.sh \
      valgrind \
        $VALGRINDOPTS \
        ./"$BIN"
  elif [ -n "$ISQUICK" ]; then
    ./"$BIN" </dev/null
  else
    valgrind \
      $VALGRINDOPTS \
      --read-var-info=yes \
      --track-origins=yes \
      ./"$BIN" </dev/null
      #-fsanitize=address -fsanitize=leak -fsanitize=undefined
  fi
  PHASERES=$(($?|$PHASERES))

  if [ -z "$ISSMALLBIN" ] && [ -z "$ISSIMPLE" ]; then
    test_coverage
    PHASERES=$(($?|$PHASERES))
  fi

  return $PHASERES
}

tests() {
  local TESTRES
  TESTRES=0

  rm *.gcno 2>/dev/null
  compile -c vm.c || return 1

  if [ -n "$ISSMALLBIN" ]; then
    strip \
    -S \
    --strip-unneeded \
    --remove-section=.note.gnu.gold-version \
    --remove-section=.comment \
    --remove-section=.note \
    --remove-section=.note.gnu.build-id \
    --remove-section=.note.ABI-tag \
    vm.o ||
    return 1
    # -s
  fi

  phase module
  TESTRES=$(($?|$TESTRES))

  phase smoke
  TESTRES=$(($?|$TESTRES))

  phase system
  TESTRES=$(($?|$TESTRES))
  return $TESTRES
}

main() {
  local MAINRES
  tests
  MAINRES=$?
  echo $MAINRES
  return $MAINRES
}

compile() {
  local ADD=""
  if [ -n "$ISSMALLBIN" ]; then
  : \
    -fshort-double \
    -ffunction-sections \
    -fdata-sections \
    -fno-jump-tables \
  #

    ADD="$ADD \
    -Oz \
    -Wno-error=suggest-attribute=pure \
    -Wno-error=suggest-attribute=const \
    -Wno-error=unused-function \
    -fno-asynchronous-unwind-tables \
    -fno-unwind-tables \
    -fira-region=mixed \
    -fno-stack-protector \
    -fno-unroll-loops \
    -fno-ident \
    -mfpmath=387 \
    -mfancy-math-387 \
    -fsingle-precision-constant \
    -Wl,-z,norelro \
    -Wl,--build-id=none \
    -U_FORTIFY_SOURCE \
    -DSMALLBIN \
    -DNDEBUG \
    -s\
    "
    ./gcc-warn.sh $ADD "$@" # -D'__attribute__(_)=' (TODO useful for putsn())
  elif [ -n "$ISSIMPLE" ]; then
    ADD="$ADD -O2 -g -DNEEDLEAK"
    ./gcc-warn.sh $ADD "$@"
  else
    ADD="$ADD -Og -g --coverage"
    ./gcc-warn.sh $ADD "$@"
  fi
}

main "$@"
