Menu

verificationCases/runVerification.sh

runVerification.sh

#!/bin/bash
# runVerification.sh
#
# Build-and-run driver for the verification cases in this directory.
#
# Every case in verificationCases/ compares against an exact solution of
# the equations the solver implements and prints a bare PASS or FAIL line
# on stdout, exiting non-zero on failure. This script builds each case in
# its own build-<name>/ directory, runs it, and reports the measured
# errors the case printed.
#
# A case whose source mentions SKIP_EMBED_GUARDS also carries a negative
# control: the same source built with -DSKIP_EMBED_GUARDS must fail its
# metric (P1) or solid-fraction (P2) probe while the solution is finite.
# A passing control, a P3-only miss or a non-finite solution does not
# establish the effect of the embed guards.
#
# Usage:
#   bash verificationCases/runVerification.sh [case.c ...]
#
#   With no arguments, runs every .c in this directory.
#
# Environment:
#   VERIFICATION_THREADS  If set to N > 1, build with -fopenmp and run on N
#                         threads. Unset (the default) builds serial, which
#                         is bitwise reproducible; OpenMP reductions sum in
#                         a nondeterministic order and shift results at the
#                         1e-5 level. Use threads to iterate, serial to
#                         record a result. staticFilmTube is the expensive
#                         case: about 49 min serial, 10 min on 16 threads.
#
# Author: Vatsal Sanjay
# Organization: CoMPhy Lab, Durham University

set -euo pipefail

SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="\$(cd "\${SCRIPT_DIR}/.." && pwd)"

if [[ -f "\${REPO_ROOT}/.project_config" ]]; then
  # shellcheck disable=SC1091
  source "\${REPO_ROOT}/.project_config"
fi

if ! command -v qcc >/dev/null 2>&1; then
  echo "ERROR: qcc not found in PATH." >&2
  exit 1
fi

QCC_FLAGS=(-I"\${REPO_ROOT}/src-local" -O2 -Wall -disable-dimensions)

# Optional OpenMP. Serial is the default because a verification result
# should be reproducible; threaded reductions are not bitwise stable.
THREADS="\${VERIFICATION_THREADS:-1}"
if [[ ! "\$THREADS" =~ ^[0-9]+\$ ]] || [[ "\$THREADS" -lt 1 ]]; then
  echo "ERROR: VERIFICATION_THREADS must be a positive integer." >&2
  exit 1
fi
if [[ "\$THREADS" -gt 1 ]]; then
  QCC_FLAGS+=(-fopenmp)
  export OMP_NUM_THREADS="\$THREADS"
  echo "OpenMP build: \${THREADS} threads (results are not bitwise reproducible)"
  echo ""
fi

FAILED=0

# Build one case into its own directory and run it.
# Prints the case's own stdout, indented, on success.
build_and_run() {
  local src="\$1" name="\$2" build_dir="\$3"
  shift 3
  rm -rf "\$build_dir"
  mkdir -p "\$build_dir"
  cp "\$src" "\$build_dir/"
  (
    cd "\$build_dir"
    qcc "\${QCC_FLAGS[@]}" "\$@" "\$(basename "\$src")" -o "\$name" -lm
    ./"\$name" > out.log 2> err.log
  )
}

run_case() {
  local src="\$1"
  local name build_dir
  name="\$(basename "\$src" .c)"
  build_dir="\${SCRIPT_DIR}/build-\${name}"

  echo "-----------------------------------------"
  echo "Verification: \${name}"
  echo "-----------------------------------------"

  if build_and_run "\$src" "\$name" "\$build_dir" && \
     grep -q "^PASS\$" "\${build_dir}/out.log"; then
    echo "\${name}: PASS"
    # A case whose only stdout is the PASS line makes grep exit 1, which
    # would abort the driver under `set -e`.
    grep -v "^PASS\$" "\${build_dir}/out.log" | sed 's/^/  /' || true
  else
    echo "\${name}: FAIL" >&2
    sed 's/^/  /' "\${build_dir}/out.log" >&2 || true
    tail -5 "\${build_dir}/err.log" | sed 's/^/  /' >&2 || true
    FAILED=1
  fi

  # Machine-readable artefacts the case may have written.
  local csv
  while IFS= read -r csv; do
    echo "  raw errors: \${csv#"\${REPO_ROOT}/"}"
  done < <(find "\$build_dir" -maxdepth 1 -name '*.csv' -print | sort)

  # Negative control, when the case declares one.
  if grep -q "SKIP_EMBED_GUARDS" "\$src"; then
    local nc_dir="\${SCRIPT_DIR}/build-\${name}-noguards"
    echo "  negative control: rebuilding with -DSKIP_EMBED_GUARDS (must FAIL)"
    # Build status, exit status and output are three different things. A
    # qcc failure or a crash is not evidence that the guard assertion
    # fired, so accept the control only when the binary built, exited
    # non-zero, and reported a finite P1/P2 assertion failure.
    rm -rf "\$nc_dir"
    mkdir -p "\$nc_dir"
    cp "\$src" "\$nc_dir/"
    local nc_built=0 nc_status=0
    if ( cd "\$nc_dir" && qcc "\${QCC_FLAGS[@]}" -DSKIP_EMBED_GUARDS \
           "\$(basename "\$src")" -o "\$name" -lm ) 2>"\${nc_dir}/build.log"; then
      nc_built=1
      ( cd "\$nc_dir" && ./"\$name" > out.log 2> err.log ) || nc_status=\$?
    fi

    if [[ \$nc_built -eq 0 ]]; then
      echo "\${name} negative control: BUILD FAILED, so the control proves nothing" >&2
      tail -5 "\${nc_dir}/build.log" | sed 's/^/    /' >&2 || true
      FAILED=1
    elif [[ \$nc_status -eq 0 ]]; then
      echo "\${name} negative control: UNEXPECTED PASS without the embed guards" >&2
      echo "  the guard under test is not detecting the coupling it claims to" >&2
      FAILED=1
    elif ! grep -q "^FAIL\$" "\${nc_dir}/out.log"; then
      echo "\${name} negative control: exited \${nc_status} without printing FAIL" >&2
      echo "  it crashed rather than failing its own assertion" >&2
      tail -5 "\${nc_dir}/err.log" | sed 's/^/    /' >&2 || true
      FAILED=1
    elif grep -q "^divergence:" "\${nc_dir}/out.log" || \
         ! grep -Eq '^P[12] .* -> FAIL\$' "\${nc_dir}/out.log"; then
      echo "\${name} negative control: no finite P1/P2 failure was demonstrated" >&2
      sed 's/^/    /' "\${nc_dir}/out.log" >&2
      FAILED=1
    else
      echo "  negative control: FAILED as required (exit \${nc_status})"
      grep -iE "^(P[0-9]|probe|violation)" "\${nc_dir}/out.log" | sed 's/^/    /' || true
    fi
  fi

  echo ""
}

if [[ \$# -gt 0 ]]; then
  for src in "\$@"; do
    run_case "\$src"
  done
else
  shopt -s nullglob
  cases=("\${SCRIPT_DIR}"/*.c)
  shopt -u nullglob
  if [[ \${#cases[@]} -eq 0 ]]; then
    echo "ERROR: no verification cases found in \${SCRIPT_DIR}." >&2
    exit 1
  fi
  for src in "\${cases[@]}"; do
    run_case "\$src"
  done
fi

echo "========================================="
if [[ \$FAILED -eq 0 ]]; then
  echo "All verification cases passed."
else
  echo "Some verification cases FAILED." >&2
fi
echo "========================================="
exit \$FAILED