#!/bin/bash -e

############################################################
# Portable helpers (Linux + macOS)
############################################################

print_help() {
  cat <<EOF
NAME
  $(basename "$0") — run predefined jawm test workflows and compare their outputs to stored reference hashes. \
By default it uses your local jawm installation and your system Python, but it can optionally install and use \
specific Python versions via pyenv, as well as specific jawm versions, for reproducible multi-environment testing.

SYNOPSIS
  $(basename "$0") [options]

DESCRIPTION
  This script can:
    • Download and verify test input data (via a "downloads file"; MD5 checked, auto-extract .tar.gz/.tgz/.gz).
    • Optionally set up multiple Python versions using pyenv.
    • Install one or more jawm versions (from a local path or a Git ref) into those environments
      or into the system Python.
    • Determine module versions/tags to test (from the current repo, GitHub actions context, or explicit list).
    • Run jawm workflows listed in a tests file, compare output hashes, and optionally update them.

  Paths you pass are normalized to absolute paths. macOS and Linux are supported.

OPTIONS
  -r, --runner <local|github|download>
        Select runner mode (default: ${RUNNER:-local})
          local     → run from the current checkout.
          github    → use GitHub Actions environment variables to name versions.
          download  → only download & verify data, then exit.

  -d, --dispatch
        Treat the run as "dispatched" (affects how module tags are chosen).

  -y, --yaml <file...>
        One or more extra YAML files to pass to jawm as overrides (appended after -p).
        May be repeated or followed by multiple files until the next option.

  -o, --override
        When a test's generated hash differs from the stored hash, overwrite the stored hash.

      --no-override
        Opposite of --override (default behavior).

  -i, --ignore
        Do not stop on jawm failures or hash mismatches; continue tests and keep stored hashes
        unless --override is also set.

  -m, --module_versions <version...>
        Explicit list of module versions/tags to test (skips auto-detection).
        Examples: --module_versions current v1.2.3 4f3a2c1

  -p, --python_versions <ver...>
        Python versions to use. Use "system" to use the current system Python.
        Any non-"system" versions are managed with pyenv (installed if missing).
        Default: ${PYTHON_VERSIONS[*]:-system}

      --skip_python_versions <ver...>
        Remove specific versions from the -p list after parsing.

  -j, --jawm_versions <ver...>
        jawm versions to install/test. Use "local" to install from --jawm_repo if it's a path,
        or from git if it’s a repo URL. Default: ${JAWM_VERSIONS[*]:-local}

      --jawm_repo <path|host/org/repo.git>
        Local jawm source path or Git repository to install from.
        If a local directory is detected, jawm version is forced to "local".
        Default: ${JAWM_REPO}

  -t, --tests_file <file>
        Semicolon-separated tests list (see "TESTS FILE FORMAT").
        Default: ${TESTS_FILE}

      --downloads_file <file>
        Space-separated downloads list (see "DOWNLOADS FILE FORMAT").
        If the file exists, data is downloaded/verified before tests.
        Default: ${DOWNLOADS_FILE}

  -h, --help
        Show this help and exit.

BEHAVIOR OVERVIEW
  • Downloads:
      - Reads the downloads file if present. For each line "<md5> <filename> <url>":
          * Downloads with curl -L -o <filename> <url>
          * Extracts *.tar.gz / *.tgz, leaves *.fastq.gz compressed, gunzips other *.gz
          * Verifies MD5 using md5sum (Linux) or md5 (macOS)
      - If --runner=download, the script exits after downloads/verification.

  • Python/jawm setup:
      - If you request any Python version other than "system" OR any jawm version other than "local",
        pyenv is bootstrapped (if missing), versions are installed as needed, and virtualenvs named:
          py<python>-jawm.<jawm>
      - For system Python, jawm is installed with "pip --user" and the user bin dir is added to PATH.
      - For local jawm installs, --jawm_repo must be a directory.
      - For git-based jawm installs, it uses: git+ssh://git@<--jawm_repo>@<version>

  • Module version selection (when -m/--module_versions is not supplied):
      - local runner:
          * "current" code tag is always included.
          * If --dispatch is set, and a tag exists, the latest tag is also included.
      - github runner (expects GITHUB_REF_NAME / GITHUB_REF_TYPE):
          * Writes CODE_TAG and VERSION_TAG to \$GITHUB_ENV.
          * If --dispatch is set → test both CODE_TAG and VERSION_TAG.
          * If building a tag → test VERSION_TAG only.
          * Otherwise → test CODE_TAG only.

  • Tests execution:
      - For each Python × jawm × module version combo, runs each test via:
          jawm <module> <workflow> -l <logs_folder> -p <params> <yaml...>
      - Creates a fresh logs directory under "\$LOGS_FOLDER/run.XXXXXX" for each test.
      - Compares that run's generated hash from "<run_logs>/jawm_hashes/<module>.hash"
        with the stored hash in the tests file.
      - A missing or malformed generated hash fails the test.
      - With --override, mismatches update the tests file; otherwise mismatches fail
        (unless --ignore).

DEFAULT PATHS
  Tests file:      ${TESTS_FILE}
  Downloads file:  ${DOWNLOADS_FILE}
  Logs folder:     ${LOGS_FOLDER}
  Project root:    ${FULLPATH}

TESTS FILE FORMAT
  The file is semicolon-separated; the first line is preserved as-is (typically a header).
  Subsequent non-empty, non-comment lines are:

    <MODULE> ; <WORKFLOW> ; <PARAMS> ; "<NAME>" ; <HASH>

  • <MODULE>   : Python module path used by jawm (e.g., mymod.py)
  • <WORKFLOW> : Workflow name/alias expected by jawm
  • <PARAMS>   : Parameters passed after "-p" (exactly as a single field; may contain spaces)
  • "<NAME>"   : Descriptive name (kept quoted)
  • <HASH>     : Expected output hash (may be empty to accept and write first result)

DOWNLOADS FILE FORMAT
  Each non-empty, non-comment line:

    <MD5SUM> <FILENAME> <URL>

  Behavior:
    • *.tar.gz / *.tgz → extracted
    • *.fastq.gz       → left compressed
    • other *.gz       → gunzipped
    • All files are MD5-verified; mismatch causes exit 1.

EXIT STATUS
  0  Success
  1  Error (invalid options; download/MD5 failure; jawm failure; hash mismatch without --override;
     missing Python/jawm when required; or other runtime errors)

ENVIRONMENT / PREREQUISITES
  Required tools: curl, tar, gunzip, git, md5sum (Linux) or md5 (macOS).
  For pyenv-managed installs: build deps for CPython on your OS (e.g., openssl, zlib, readlines, etc.).
  GitHub runner mode uses: GITHUB_REF_NAME, GITHUB_REF_TYPE, GITHUB_ENV.

EXAMPLES
  # 1) Use system Python and local jawm from a local path, run tests:
  $(basename "$0") --jawm_repo ./jawm -p system

  # 2) Download-only (fetch data and verify, then exit):
  $(basename "$0") --runner download --downloads_file ./test/data.txt

  # 3) Test multiple Pythons via pyenv and two jawm versions (git tags/commits):
  $(basename "$0") -p 3.10.14 3.11.9 --skip_python_versions 3.10.14 \\
                   -j v1.2.0 4f3a2c1 --jawm_repo github.com/mpg-age-bioinformatics/jawm.git

  # 4) Supply extra YAML overrides:
  $(basename "$0") -y conf/base.yaml conf/override.yaml

  # 5) Accept new hashes on change:
  $(basename "$0") --override

  # 6) Keep going even if a test fails (do not update hashes):
  $(basename "$0") --ignore

NOTES
  • When --jawm_repo points to a local directory, jawm versions are forced to "local".
  • User site bin is added to PATH when using system Python so that the jawm binary is found.
  • Temporary file <tests_file>.tmp is used during hash updates; it replaces the original on success.
EOF
}




# Portable absolute path resolver (avoids readlink -f)
abspath() {
  # usage: abspath <path>
  local target="$1"
  if [ -z "$target" ]; then
    pwd -P
    return
  fi
  if [ -d "$target" ]; then
    (cd "$target" && pwd -P)
  else
    local dir
    dir="$(dirname "$target")"
    local base
    base="$(basename "$target")"
    (cd "$dir" 2>/dev/null && printf "%s/%s\n" "$(pwd -P)" "$base") || printf "%s\n" "$target"
  fi
}

# Cross-platform MD5 of a file -> prints "hash"
md5_file() {
  # Uses md5sum (Linux) or md5 (macOS)
  if command -v md5sum >/dev/null 2>&1; then
    md5sum "$1" | awk '{print $1}'
  elif command -v md5 >/dev/null 2>&1; then
    md5 -q "$1"
  else
    echo "ERROR: neither md5sum nor md5 found" >&2
    return 1
  fi
}

array_is() {
  # $1 = expected value, rest = array elements
  local expected="$1"; shift
  local arr=("$@")

  # Must have exactly one element AND match
  [[ ${#arr[@]} -eq 1 && "${arr[0]}" == "$expected" ]]
}

############################################################
# Parse arguments
############################################################

# Default values
RUNNER="local"
DISPATCHED=false
OVERRIDE=false
IGNORE=false

# Declare arrays explicitly (works on macOS bash 3.2)
declare -a YAML_FILES MODULE_VERSIONS PYTHON_VERSIONS JAWM_VERSIONS SKIP_PYTHON_VERSIONS

# Single tests file (portable realpaths)
TESTS_FILE="$(abspath ./test/tests.txt)"
DOWNLOADS_FILE="$(abspath ./test/data.txt)"
LOGS_FOLDER="$(abspath ./test/logs)"
JAWM_REPO="github.com/mpg-age-bioinformatics/jawm.git"

# Parse arguments
while [[ $# -gt 0 ]]; do
  case $1 in
    -r|--runner) RUNNER="$2"; shift 2 ;;
    -d|--dispatch) DISPATCHED=true; shift ;;
    -y|--yaml)
      shift
      while [[ $# -gt 0 && $1 != -* ]]; do
        YAML_FILES+=("$(abspath "$1")")
        shift
      done
      ;;
    -o|--override) OVERRIDE=true; shift ;;
    --no-override) OVERRIDE=false; shift ;;
    -i|--ignore) IGNORE=true; shift ;;
    -m|--module_versions)
      shift
      while [[ $# -gt 0 && $1 != -* ]]; do MODULE_VERSIONS+=("$1"); shift; done
      ;;
    -p|--python_versions)
      shift
      while [[ $# -gt 0 && $1 != -* ]]; do PYTHON_VERSIONS+=("$1"); shift; done
      ;;
    --skip_python_versions)
      shift
      while [[ $# -gt 0 && $1 != -* ]]; do SKIP_PYTHON_VERSIONS+=("$1"); shift; done
      ;;
    -j|--jawm_versions)
      shift
      while [[ $# -gt 0 && $1 != -* ]]; do JAWM_VERSIONS+=("$1"); shift; done
      ;;
    --jawm_repo) JAWM_REPO="$2"; shift 2 ;;
    -t|--tests_file) TESTS_FILE="$2"; shift 2 ;;
    --downloads_file) DOWNLOADS_FILE="$(abspath "$2")"; shift 2 ;;
    -h|--help)
      print_help
      exit 0
      ;;
    *) echo "Unknown option: $1" >&2; exit 1 ;;
  esac
done

# Normalize paths after possible overrides
TESTS_FILE="$(abspath "$TESTS_FILE")"
DOWNLOADS_FILE="$(abspath "$DOWNLOADS_FILE")"
LOGS_FOLDER="$(abspath "$LOGS_FOLDER")"

# Validate runner
if [[ -z "$RUNNER" || ! "$RUNNER" =~ ^(local|github|download)$ ]]; then
  echo "Error: --runner must be 'local', 'github', or 'download'." >&2
  exit 1
fi

# If local folder given for JAWM_REPO, force JAWM_VERSIONS=local
if [[ -d "$JAWM_REPO" ]]; then
  echo "WARNING: Local jawm source detected at '$JAWM_REPO'. Forcing jawm version to 'local'."
  JAWM_VERSIONS=("local")
fi

# Defaults if not provided
if [ ${#JAWM_VERSIONS[@]} -eq 0 ]; then JAWM_VERSIONS=("local"); fi
if [ ${#PYTHON_VERSIONS[@]} -eq 0 ]; then PYTHON_VERSIONS=("system"); fi

# Example usage
echo "Runner: $RUNNER"
echo "Dispatched: $DISPATCHED"
echo "Extra yaml files: ${YAML_FILES[*]:-None}"
echo "Override existing hashes: $OVERRIDE"
echo "Ignore failed tests: $IGNORE"
echo "Test file: ${TESTS_FILE:-None}"
echo "Downloads file: ${DOWNLOADS_FILE:-None}"
echo "jawm repo/folder: ${JAWM_REPO}"
echo "Specified module Versions: ${MODULE_VERSIONS[*]:-None}"
echo "Specified Python Versions: ${PYTHON_VERSIONS[*]:-None}"
echo "Skip Python Versions: ${SKIP_PYTHON_VERSIONS[*]:-None}"
echo "Specified jawm Versions: ${JAWM_VERSIONS[*]:-None}"

############################################################
# set paths
############################################################
FULLPATH="$(abspath .)"
DIRPATH="$FULLPATH"
TESTPATH="${FULLPATH}/test"

############################################################
# download data
############################################################
if [ -f "$DOWNLOADS_FILE" ]; then
  mkdir -p "${TESTPATH}/test-input"
  cd "${TESTPATH}/test-input" || exit 1

  # Read 3 columns: <md5> <filename> <url>
  while IFS= read -r line || [ -n "$line" ]; do
    # Skip empty or commented lines
    [[ -z "$line" || "$line" == \#* ]] && continue
    set -- $line
    hash_expected="$1"
    filename="$2"
    url="$3"
    echo "$filename $url"
    if [ ! -f "$filename" ]; then
      # wget -O "$filename" "$url"
      curl -L -o "$filename" "$url"
      # If it ends with .tar.gz or .tgz, extract it

      case "$filename" in
        *.tar.gz|*.tgz)
          echo "Extracting tar archive $filename..."
          tar -xzf "$filename"
          ;;
        *.fastq.gz)
          echo "Leaving FASTQ gzipped: $filename"
          ;;
        *.gz)
          echo "Decompressing $filename..."
          gunzip -k "$filename"
          ;;
      esac
    fi
    # Verify MD5
    hash_actual="$(md5_file "$filename")" || { echo "Checksum tool not found."; exit 1; }
    if [ "$hash_actual" != "$hash_expected" ]; then
      echo "ERROR: MD5 mismatch for $filename"
      echo " expected: $hash_expected"
      echo "   actual: $hash_actual"
      exit 1
    fi
  done < "$DOWNLOADS_FILE"
fi

if [[ "$RUNNER" == "download" ]]; then
  exit 0
fi

############################################################
# preparing pyenv
############################################################

if ! array_is "system" "${PYTHON_VERSIONS[@]}" || \
   ! array_is "local"  "${JAWM_VERSIONS[@]}" ; then
  
  export PATH="$HOME/.pyenv/bin:$PATH"
  if command -v pyenv >/dev/null 2>&1; then
    echo "pyenv is already installed"
  else
    echo "pyenv not found, installing..."
    curl https://pyenv.run | bash
    echo "pyenv installed successfully."
    export PATH="$HOME/.pyenv/bin:$PATH"
  fi

  # Initialize pyenv for this process (no need to source shell rc files)
  eval "$(pyenv init --path)"
  eval "$(pyenv init -)"
  if command -v pyenv-virtualenv-init >/dev/null 2>&1; then
    eval "$(pyenv virtualenv-init -)"
  fi

fi

############################################################
# installing different python versions
############################################################

# Filter out skipped Python versions portably
if [ ${#SKIP_PYTHON_VERSIONS[@]} -gt 0 ]; then
  filtered=()
  for ver in "${PYTHON_VERSIONS[@]}"; do
    skip_it=false
    for s in "${SKIP_PYTHON_VERSIONS[@]}"; do
      if [ "$ver" = "$s" ]; then skip_it=true; break; fi
    done
    $skip_it || filtered+=("$ver")
  done
  PYTHON_VERSIONS=("${filtered[@]}")
fi

# Install requested pyenv Pythons (except 'system')
for PYTHON_VERSION in "${PYTHON_VERSIONS[@]}"; do
  if [[ "$PYTHON_VERSION" == "system" ]]; then continue; fi
  if pyenv versions --bare | grep -qx "$PYTHON_VERSION"; then
    echo "Python $PYTHON_VERSION is already installed"
  else
    echo "Installing Python $PYTHON_VERSION..."
    if ! pyenv install "$PYTHON_VERSION"; then
      echo "WARN: Python $PYTHON_VERSION could not be installed; skipping it."
      # remove from list
      tmp=()
      for pv in "${PYTHON_VERSIONS[@]}"; do
        [ "$pv" = "$PYTHON_VERSION" ] || tmp+=("$pv")
      done
      PYTHON_VERSIONS=("${tmp[@]}")
    fi
  fi
done

if ! array_is "system" "${PYTHON_VERSIONS[@]}" || \
   ! array_is "local"  "${JAWM_VERSIONS[@]}" ; then

  echo "Your python versions and environments can be found under: ~/.pyenv/versions/"

fi

############################################################
# get latest jawm tag and latest commit (only if needed)
############################################################
# If JAWM_VERSIONS was explicitly set (non-empty array), keep it as-is.
# Otherwise we defaulted earlier to ("local"), which is already portable.

############################################################
# install different jawm versions
############################################################

if ! array_is "system" "${PYTHON_VERSIONS[@]}" || \
   ! array_is "local"  "${JAWM_VERSIONS[@]}" ; then

  for PYTHON_VERSION in "${PYTHON_VERSIONS[@]}"; do
    for JAWM_VERSION in "${JAWM_VERSIONS[@]}"; do

      if [[ "$PYTHON_VERSION" == "system" ]]; then
        echo "Using system Python for jawm@${JAWM_VERSION}"
        PYTHON_BIN="$(command -v python3 || command -v python || true)"
        if [[ -z "$PYTHON_BIN" ]]; then
          echo "❌ No system Python found! Please install python3."
          exit 1
        fi
        # Ensure user-site bin on PATH for pip --user
        USER_BIN="$($PYTHON_BIN -c 'import site,sys; sys.stdout.write(site.USER_BASE + "/bin\n")' 2>/dev/null || true)"
        if [ -n "$USER_BIN" ]; then export PATH="$USER_BIN:$PATH"; fi

        if [[ "$JAWM_VERSION" == "local" ]]; then
          if [[ -d "$JAWM_REPO" ]]; then
            echo "(Re)Installing local jawm from $JAWM_REPO (system Python)"
            pip install --user --force-reinstall "$JAWM_REPO"
            echo "✅ System jawm install complete."
          else
            echo "INFO: No jawm path '$JAWM_REPO' was given." >&2
            # exit 1
          fi
        else
          # Install from Git
          echo "Installing 'jawm@${JAWM_VERSION}' (system Python)"
          pip install --user "git+ssh://git@${JAWM_REPO}@${JAWM_VERSION}"
          echo "✅ System jawm install complete."
        fi
        continue
      fi

      # pyenv-based Pythons
      ENV_NAME="py${PYTHON_VERSION}-jawm.${JAWM_VERSION}"
      if pyenv virtualenvs --bare | grep -qx "$ENV_NAME"; then
        echo "Virtual environment '$ENV_NAME' already exists."
      else
        echo "Creating virtual environment '$ENV_NAME'"
        pyenv virtualenv "$PYTHON_VERSION" "$ENV_NAME"
      fi

      # Activate
      # shellcheck disable=SC1090
      pyenv activate "$ENV_NAME"
      export PATH="$HOME/.pyenv/versions/${ENV_NAME}/bin/:$PATH"

      if [[ "$JAWM_VERSION" == "local" ]]; then
        if [[ -d "$JAWM_REPO" ]]; then
          echo "Installing jawm from $JAWM_REPO in $ENV_NAME."
          pip install --force-reinstall "$JAWM_REPO"
        else
          echo "ERROR: Local jawm path '$JAWM_REPO' not found." >&2
          pyenv deactivate
          exit 1
        fi
      else
        if pyenv exec pip show jawm >/dev/null 2>&1; then
          echo "'jawm' already installed in $ENV_NAME; reinstalling pinned rev."
        fi
        echo "Installing 'jawm@${JAWM_VERSION}' in $ENV_NAME."
        pip install "git+ssh://git@${JAWM_REPO}@${JAWM_VERSION}"
      fi

      echo "Installed jawm binary: $(command -v jawm || true)"
      pyenv deactivate
    done
  done
fi

############################################################
# set module tag(s)
############################################################
if [ ${#MODULE_VERSIONS[@]} -eq 0 ]; then
  if [[ "$RUNNER" == "local" ]]; then
    cd "$DIRPATH"
    CODE_TAG="current"
    if [[ "$DISPATCHED" == true ]]; then
      VERSION_TAG="$(git describe --tags --abbrev=0 2>/dev/null || echo "")"
    fi
    MODULE_VERSIONS=($CODE_TAG)
    [ -n "$VERSION_TAG" ] && MODULE_VERSIONS+=("$VERSION_TAG")
  elif [[ "$RUNNER" == "github" ]]; then
    VERSION_TAG="${GITHUB_REF_NAME}"
    echo "VERSION_TAG=${GITHUB_REF_NAME}" >> "$GITHUB_ENV"
    CODE_TAG="$(git rev-parse --short HEAD)"
    echo "CODE_TAG=${CODE_TAG}" >> "$GITHUB_ENV"
    if [[ "$DISPATCHED" == true ]]; then
      MODULE_VERSIONS=("$CODE_TAG" "$VERSION_TAG")
    elif [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
      MODULE_VERSIONS=("$VERSION_TAG")
    else
      MODULE_VERSIONS=("$CODE_TAG")
    fi
  fi
fi

############################################################
# run tests
############################################################
for PYTHON_VERSION in "${PYTHON_VERSIONS[@]}"; do
  for JAWM_VERSION in "${JAWM_VERSIONS[@]}"; do

    if [[ "$PYTHON_VERSION" == "system" ]]; then
      echo "Skipping pyenv activation for system Python (${JAWM_VERSION})"
      PY_BIN="$(command -v python3 || command -v python)"
      USER_BIN="$($PY_BIN -c 'import site,sys; sys.stdout.write(site.USER_BASE + "/bin\n")' 2>/dev/null || true)"
      if [ -n "$USER_BIN" ]; then export PATH="$USER_BIN:$PATH"; fi
    else
      ENV_NAME="py${PYTHON_VERSION}-jawm.${JAWM_VERSION}"
      pyenv activate "$ENV_NAME"
      export PATH="$HOME/.pyenv/versions/${ENV_NAME}/bin/:$PATH"
    fi

    for MODULE_VERSION in "${MODULE_VERSIONS[@]}"; do
      cd "$DIRPATH"
      if [ "$MODULE_VERSION" != "current" ]; then git checkout "$MODULE_VERSION"; fi

      rm -f "${TESTS_FILE}.tmp"
      head -n 1 "$TESTS_FILE" > "${TESTS_FILE}.tmp"

      while IFS=';' read -r MOD WORKFLOW PARAM NAME STORED_HASH || [ -n "$MOD" ]; do
        [[ -z "$MOD" || "$MOD" == \#* ]] && continue

        NAME="${NAME%\"}"; NAME="${NAME#\"}"

        # A preserved baseline from an earlier invocation is not its current result.
        # Keep each invocation's evidence separate, also for repeated module names.
        mkdir -p "$LOGS_FOLDER"
        RUN_LOGS_FOLDER="$(mktemp -d "${LOGS_FOLDER}/run.XXXXXX")"

        echo "MODULE: $MOD"
        echo "WORKFLOW: $WORKFLOW"
        echo "PARAMETERS: $PARAM"
        echo "NAME: $NAME"
        echo "STORED HASH: ${STORED_HASH:-None}"
        echo "PYTHON VERSION: ${PYTHON_VERSION}"
        echo "JAWM VERSION: ${JAWM_VERSION}"
        echo "MODULE VERSION: ${MODULE_VERSION}"
        echo "PWD: $(pwd)"
        echo "JAWM COMMAND: jawm ${MOD} ${WORKFLOW} -l ${RUN_LOGS_FOLDER} -p $PARAM ${YAML_FILES[*]}"
        echo "------"

        FAILED=false
        if [[ $IGNORE == false ]]; then
          jawm "$MOD" "$WORKFLOW" -l "$RUN_LOGS_FOLDER" -p $PARAM "${YAML_FILES[@]}" \
            || { echo 'Error: jawm failed. Test failed.'; rm -f "${TESTS_FILE}.tmp"; exit 1; }
        else
          jawm "$MOD" "$WORKFLOW" -l "$RUN_LOGS_FOLDER" -p $PARAM "${YAML_FILES[@]}" \
            || { echo 'Warning: jawm failed. Test failed.'; FAILED=true; }
        fi

        if [[ $FAILED == false ]]; then
          HASH_FILE="${RUN_LOGS_FOLDER}/jawm_hashes/$(basename "${MOD%.py}").hash"
          if ! NEW_HASH="$(cat "$HASH_FILE")" || [[ ! "$NEW_HASH" =~ ^[[:xdigit:]]{64}$ ]]; then
            echo "Error: missing or malformed current-run SHA-256 hash: $HASH_FILE"
            if [[ $IGNORE == false ]]; then
              rm -f "${TESTS_FILE}.tmp"
              exit 1
            fi
            FAILED=true
          fi
        fi

        if [[ $FAILED == false ]]; then
          echo "Stored HASH:    ${STORED_HASH}"
          echo "Generated HASH: ${NEW_HASH}"

          if [[ -z "$STORED_HASH" ]]; then
            echo "$MOD;$WORKFLOW;$PARAM;\"$NAME\";${NEW_HASH}" >> "${TESTS_FILE}.tmp"
          else
            if [[ "$NEW_HASH" != "$STORED_HASH" ]]; then
              echo "Warning: Hashes do not match!"
              if [[ $OVERRIDE == true ]]; then
                echo "Overwriting existing HASH."
                echo "$MOD;$WORKFLOW;$PARAM;\"$NAME\";${NEW_HASH}" >> "${TESTS_FILE}.tmp"
              else
                if [[ $IGNORE == false ]]; then
                  echo "Error: Hashes do not match! Test failed."
                  rm -f "${TESTS_FILE}.tmp"
                  exit 1
                else
                  echo "Keeping stored HASH."
                  echo "$MOD;$WORKFLOW;$PARAM;\"$NAME\";${STORED_HASH}" >> "${TESTS_FILE}.tmp"
                fi
              fi
            else
              echo "$MOD;$WORKFLOW;$PARAM;\"$NAME\";${STORED_HASH}" >> "${TESTS_FILE}.tmp"
            fi
          fi
        else
          # --ignore continues, but must not remove failed tests from the suite.
          echo "$MOD;$WORKFLOW;$PARAM;\"$NAME\";${STORED_HASH}" >> "${TESTS_FILE}.tmp"
        fi
      done < "$TESTS_FILE"

      mv "${TESTS_FILE}.tmp" "$TESTS_FILE"
    done

    if [[ "$PYTHON_VERSION" != "system" ]]; then
      pyenv deactivate
    fi
  done
done
