Tests

This page bundles Flowlog’s regression tests and ISO conformance runners found under tests/.

Quick run (from the flowlog/ directory):

make test
make test-inria
make test-inria-wamvm-vmonly

Notes:

Files

Source (inline)

tests/and_par_user_pred.pl
% AND-par "flow analysis" regression: independent deterministic user predicates
% should run concurrently when flowlog_parallel_and is enabled.

:- set_prolog_flag(flowlog_parallel_and, on).

complex(I, O) :-
  $flowlog_sleep_ms(250),
  O is I + 1.

merge(A, B, O) :-
  O is A + B.

doubleComplex(I, O) :-
  complex(I, A),
  complex(I, B),
  merge(A, B, O).
tests/findall_non_ground.pl
% Regression: findall/3 must work with a non-ground template that becomes ground
% per-solution (ISO semantics).

p(1).
p(2).

q(L) :- findall(X, p(X), L).

ok :-
  q(L),
  L = [1,2].
tests/findall_vars.pl
% WAM findall/3: non-ground template instances should be copied with fresh,
% bindable variables (ISO semantics).

:- catch(set_prolog_flag(flowlog_engine, wam), _, true).

p(1).
p(2).

q(L) :- findall(pair(X,Y), p(X), L).

ok :-
  q(L),
  L = [pair(1,Y1), pair(2,Y2)],
  var(Y1),
  var(Y2),
  Y1 \== Y2,
  Y1 = a,
  Y2 = b,
  findall(X, (p(X), !), Lcut),
  Lcut = [1],
  findall(Z, p(3), Lnone),
  Lnone = [],
  findall(T, \+ true, Lneg),
  Lneg = [],
  X0 = outer,
  findall(W, p(W), _),
  X0 == outer.
tests/length_long.pl
% Regression test: `length/2` must handle lists longer than internal guards.

ok :-
  length(L, 70000),
  length(L, N),
  N =:= 70000.
tests/repl_ctrld_tty.c
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <libutil.h>
#else
#include <pty.h>
#endif

static bool wait_for_substr(int fd, const char *needle, int timeout_ms) {
  char buf[8192];
  size_t len = 0;
  buf[0] = '\0';

  const int step_ms = 20;
  int remaining = timeout_ms;

  while (remaining > 0) {
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);

    struct timeval tv;
    tv.tv_sec = step_ms / 1000;
    tv.tv_usec = (step_ms % 1000) * 1000;

    int rc = select(fd + 1, &rfds, NULL, NULL, &tv);
    if (rc < 0) {
      if (errno == EINTR) {
        continue;
      }
      return false;
    }
    if (rc == 0) {
      remaining -= step_ms;
      continue;
    }

    if (!FD_ISSET(fd, &rfds)) {
      remaining -= step_ms;
      continue;
    }

    ssize_t n = read(fd, buf + len, sizeof(buf) - 1 - len);
    if (n <= 0) {
      return false;
    }
    len += (size_t)n;
    buf[len] = '\0';

    if (strstr(buf, needle)) {
      return true;
    }

    if (len > sizeof(buf) / 2) {
      const size_t keep = sizeof(buf) / 3;
      memmove(buf, buf + (len - keep), keep);
      len = keep;
      buf[len] = '\0';
    }
  }

  return false;
}

static bool wait_for_exit(pid_t pid, int timeout_ms, int *status_out) {
  const int step_ms = 10;
  int remaining = timeout_ms;

  while (remaining > 0) {
    int st = 0;
    pid_t w = waitpid(pid, &st, WNOHANG);
    if (w == pid) {
      if (status_out) {
        *status_out = st;
      }
      return true;
    }
    if (w < 0) {
      if (errno == EINTR) {
        continue;
      }
      return false;
    }
    usleep(step_ms * 1000);
    remaining -= step_ms;
  }

  return false;
}

int main(int argc, char **argv) {
  if (argc < 2 || !argv[1] || !*argv[1]) {
    fprintf(stderr, "usage: %s /path/to/flowlog\n", argv[0]);
    return 2;
  }

  const char *bin = argv[1];

  int master = -1;
  pid_t pid = forkpty(&master, NULL, NULL, NULL);
  if (pid < 0) {
    perror("forkpty");
    return 2;
  }

  if (pid == 0) {
    execl(bin, bin, NULL);
    perror("exec");
    _exit(127);
  }

  if (master < 0) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "no pty master\n");
    return 2;
  }

  if (!wait_for_substr(master, "?- ", 2000)) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "did not see prompt\n");
    return 1;
  }

  const unsigned char ctrl_d = 0x04;
  ssize_t wr = write(master, &ctrl_d, 1);
  if (wr != 1) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "failed to write Ctrl-D\n");
    return 1;
  }

  int st = 0;
  if (!wait_for_exit(pid, 1000, &st)) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "timeout waiting for exit\n");
    return 1;
  }

  if (WIFEXITED(st) && WEXITSTATUS(st) == 0) {
    return 0;
  }
  if (WIFSIGNALED(st)) {
    fprintf(stderr, "exited via signal: %d\n", WTERMSIG(st));
  } else if (WIFEXITED(st)) {
    fprintf(stderr, "unexpected exit code: %d\n", WEXITSTATUS(st));
  } else {
    fprintf(stderr, "unexpected exit status: %d\n", st);
  }
  return 1;
}
tests/repl_siginfo_key_tty.c
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <libutil.h>
#else
#include <pty.h>
#endif

static bool wait_for_substr(int fd, const char *needle, int timeout_ms) {
  char buf[8192];
  size_t len = 0;
  buf[0] = '\0';

  const int step_ms = 20;
  int remaining = timeout_ms;

  while (remaining > 0) {
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);

    struct timeval tv;
    tv.tv_sec = step_ms / 1000;
    tv.tv_usec = (step_ms % 1000) * 1000;

    int rc = select(fd + 1, &rfds, NULL, NULL, &tv);
    if (rc < 0) {
      if (errno == EINTR) {
        continue;
      }
      return false;
    }
    if (rc == 0) {
      remaining -= step_ms;
      continue;
    }

    if (!FD_ISSET(fd, &rfds)) {
      remaining -= step_ms;
      continue;
    }

    ssize_t n = read(fd, buf + len, sizeof(buf) - 1 - len);
    if (n <= 0) {
      return false;
    }
    len += (size_t)n;
    buf[len] = '\0';

    if (strstr(buf, needle)) {
      return true;
    }

    if (len > sizeof(buf) / 2) {
      const size_t keep = sizeof(buf) / 3;
      memmove(buf, buf + (len - keep), keep);
      len = keep;
      buf[len] = '\0';
    }
  }

  return false;
}

static void drain_fd(int fd) {
  char tmp[4096];
  for (;;) {
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);

    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 0;

    int rc = select(fd + 1, &rfds, NULL, NULL, &tv);
    if (rc <= 0 || !FD_ISSET(fd, &rfds)) {
      return;
    }
    ssize_t n = read(fd, tmp, sizeof(tmp));
    if (n <= 0) {
      return;
    }
  }
}

static bool wait_for_exit(int fd, pid_t pid, int timeout_ms, int *status_out) {
  const int step_ms = 10;
  int remaining = timeout_ms;

  while (remaining > 0) {
    int st = 0;
    pid_t w = waitpid(pid, &st, WNOHANG);
    if (w == pid) {
      if (status_out) {
        *status_out = st;
      }
      return true;
    }
    if (w < 0) {
      if (errno == EINTR) {
        continue;
      }
      return false;
    }
    drain_fd(fd);
    usleep(step_ms * 1000);
    remaining -= step_ms;
  }

  return false;
}

int main(int argc, char **argv) {
  if (argc < 2 || !argv[1] || !*argv[1]) {
    fprintf(stderr, "usage: %s /path/to/flowlog\n", argv[0]);
    return 2;
  }

  const char *bin = argv[1];

  int master = -1;
  pid_t pid = forkpty(&master, NULL, NULL, NULL);
  if (pid < 0) {
    perror("forkpty");
    return 2;
  }

  if (pid == 0) {
    execl(bin, bin, NULL);
    perror("exec");
    _exit(127);
  }

  if (master < 0) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "no pty master\n");
    return 2;
  }

  if (!wait_for_substr(master, "?- ", 5000)) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "did not see prompt\n");
    return 1;
  }

  // Ctrl-T: Flowlog SIGINFO keybinding (BSD SIGINFO equivalent).
  const unsigned char ctrl_t = 0x14;
  if (write(master, &ctrl_t, 1) != 1) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "failed to write Ctrl-T\n");
    return 1;
  }

  // In raw mode, the terminal must still translate '\n' to CRLF, otherwise
  // the status report "staircases" to the right.
  if (!wait_for_substr(master, "\r\nFlowlog SIGINFO (status)\r\n", 5000)) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "siginfo output missing CRLF framing\n");
    return 1;
  }

  // Exit via Ctrl-D at a fresh prompt.
  const unsigned char ctrl_d = 0x04;
  if (write(master, &ctrl_d, 1) != 1) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "failed to write Ctrl-D\n");
    return 1;
  }

  int st = 0;
  if (!wait_for_exit(master, pid, 3000, &st)) {
    kill(pid, SIGKILL);
    (void)waitpid(pid, NULL, 0);
    fprintf(stderr, "timeout waiting for exit\n");
    return 1;
  }

  if (WIFEXITED(st) && WEXITSTATUS(st) == 0) {
    return 0;
  }
  if (WIFSIGNALED(st)) {
    fprintf(stderr, "exited via signal: %d\n", WTERMSIG(st));
  } else if (WIFEXITED(st)) {
    fprintf(stderr, "unexpected exit code: %d\n", WEXITSTATUS(st));
  } else {
    fprintf(stderr, "unexpected exit status: %d\n", st);
  }
  return 1;
}
tests/run.sh
#!/usr/bin/env bash
set -euo pipefail

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

make -C "$ROOT" -s >/dev/null

mkdir -p "$ROOT/build"

BIN="$ROOT/flowlog"
PROG="$ROOT/tests/and_par_user_pred.pl"

fail() {
  echo "fail: $1" >&2
  exit 1
}

run_out() {
  "$BIN" --threads 2 -f "$PROG" -g "$1" | tr -d '\r'
}

run_prog_out() {
  local prog="$1"
  local goal="$2"
  "$BIN" --threads 2 -f "$prog" -g "$goal" | tr -d '\r'
}

time_real() {
  if command -v /usr/bin/time >/dev/null 2>&1; then
    (/usr/bin/time -p "$@" >/dev/null) 2>&1 | awk '/^real[[:space:]]+/{print $2; exit}'
    return 0
  fi

  # Fallback: coarse (seconds).
  local t0 t1
  t0="$(date +%s)"
  "$@" >/dev/null
  t1="$(date +%s)"
  echo "$((t1 - t0))"
}

out="$("$BIN" --threads 1 --engine interp -g '\+ (true -> fail).' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "parse_not_if_then"
fi

out="$(run_out 'doubleComplex(1,O).')"
out="${out%$'\n'}"
if [[ "$out" != "O = 4" ]]; then
  echo "--- expected ---" >&2
  echo "O = 4" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "and_par_user_pred_output"
fi

seq="$(time_real "$BIN" --threads 2 --engine interp -f "$PROG" -g 'set_prolog_flag(flowlog_parallel_and,off), doubleComplex(1,O).')"
par="$(time_real "$BIN" --threads 2 --engine interp -f "$PROG" -g 'set_prolog_flag(flowlog_parallel_and,on), doubleComplex(1,O).')"

awk -v seq="$seq" -v par="$par" 'BEGIN {
  if (seq + 0.0 <= 0.0 || par + 0.0 <= 0.0) exit 1;
  # Expect a noticeable wall-time reduction when AND-par is enabled.
  if ((seq / par) < 1.3) exit 2;
}' || fail "and_par_user_pred_timing (seq=$seq par=$par)"

seq_wam="$(time_real "$BIN" --threads 2 --engine wam -f "$PROG" -g 'current_prolog_flag(flowlog_engine_active,wam), set_prolog_flag(flowlog_parallel_and,off), doubleComplex(1,O).')"
par_wam="$(time_real "$BIN" --threads 2 --engine wam -f "$PROG" -g 'current_prolog_flag(flowlog_engine_active,wam), set_prolog_flag(flowlog_parallel_and,on), doubleComplex(1,O).')"

awk -v seq="$seq_wam" -v par="$par_wam" 'BEGIN {
  if (seq + 0.0 <= 0.0 || par + 0.0 <= 0.0) exit 1;
  # Expect a noticeable wall-time reduction when AND-par is enabled.
  if ((seq / par) < 1.3) exit 2;
}' || fail "and_par_user_pred_timing_wam (seq=$seq_wam par=$par_wam)"

bash "$ROOT/tests/run_sigint_repl.sh" >/dev/null
bash "$ROOT/tests/run_siginfo_repl.sh" >/dev/null
bash "$ROOT/tests/run_siginfo_key_repl.sh" >/dev/null
bash "$ROOT/tests/run_ctrld_repl.sh" >/dev/null
bash "$ROOT/tests/run_bracket_consult_repl.sh" >/dev/null
bash "$ROOT/tests/run_orpar_nesting.sh" >/dev/null
bash "$ROOT/tests/run_orpar_nesting_wam.sh" >/dev/null

out="$("$BIN" --threads 2 -f "$ROOT/tests/length_long.pl" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "length_long"
fi

WAM_PROG="$ROOT/tests/wam_engine_smoke.pl"
out="$(run_prog_out "$WAM_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_engine_ok"
fi

out="$(run_prog_out "$WAM_PROG" 'fact(5,F).')"
out="${out%$'\n'}"
if [[ "$out" != "F = 120" ]]; then
  echo "--- expected ---" >&2
  echo "F = 120" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_engine_fact"
fi

out="$(run_prog_out "$WAM_PROG" 'count(C).')"
out="${out%$'\n'}"
if [[ "$out" != "C = 2" ]]; then
  echo "--- expected ---" >&2
  echo "C = 2" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_engine_findall_length"
fi

WAM_TABLING_PROG="$ROOT/tests/wam_tabling.pl"
out="$(run_prog_out "$WAM_TABLING_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_tabling"
fi

FINDALL_PROG="$ROOT/tests/findall_non_ground.pl"
out="$(run_prog_out "$FINDALL_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "findall_non_ground_fallback"
fi

FINDALL_VARS_PROG="$ROOT/tests/findall_vars.pl"
out="$(run_prog_out "$FINDALL_VARS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "findall_vars_wam"
fi

WAM_CTRL_PROG="$ROOT/tests/wam_control_constructs.pl"
out="$(run_prog_out "$WAM_CTRL_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_control_constructs"
fi

WAM_TERM_OPS_PROG="$ROOT/tests/wam_term_ops.pl"
out="$(run_prog_out "$WAM_TERM_OPS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_term_ops"
fi

WAM_SORTING_PROG="$ROOT/tests/wam_sorting.pl"
out="$(run_prog_out "$WAM_SORTING_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_sorting"
fi

WAM_SUB_STRING_PROG="$ROOT/tests/wam_sub_string.pl"
out="$(run_prog_out "$WAM_SUB_STRING_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_sub_string"
fi

WAM_EXTRA_BUILTINS_PROG="$ROOT/tests/wam_extra_builtins.pl"
out="$(run_prog_out "$WAM_EXTRA_BUILTINS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_extra_builtins"
fi

WAM_EXC_PROG="$ROOT/tests/wam_exceptions.pl"
out="$(run_prog_out "$WAM_EXC_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_exceptions"
fi

WAM_COPY_VARS_PROG="$ROOT/tests/wam_copy_vars.pl"
out="$(run_prog_out "$WAM_COPY_VARS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_copy_vars"
fi

WAM_CONV_PROG="$ROOT/tests/wam_conversions.pl"
out="$(run_prog_out "$WAM_CONV_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_conversions"
fi

WAM_BAGOF_PROG="$ROOT/tests/wam_bagof_setof.pl"
out="$(run_prog_out "$WAM_BAGOF_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_bagof_setof"
fi

WAM_OPS_PROG="$ROOT/tests/wam_ops.pl"
out="$(run_prog_out "$WAM_OPS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_ops"
fi

WAM_FLAGS_PROG="$ROOT/tests/wam_flags.pl"
out="$(run_prog_out "$WAM_FLAGS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_flags"
fi

WAMVM_FLAGS_PROG="$ROOT/tests/wamvm_flags.pl"
out="$(run_prog_out "$WAMVM_FLAGS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wamvm_flags"
fi

WAMVM_VM_SMOKE_PROG="$ROOT/tests/wamvm_vm_smoke.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_VM_SMOKE_PROG" -g 'findall(X,p(X),Xs).' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "Xs = [1,2]" ]]; then
  echo "--- expected ---" >&2
  echo "Xs = [1,2]" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wamvm_vm_smoke"
fi

WAMVM_FINDALL_VAR_GOAL_PROG="$ROOT/tests/wamvm_findall_var_goal_vm.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_FINDALL_VAR_GOAL_PROG" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wamvm_findall_var_goal_vm"
fi

WAMVM_SELECT1_VM_PROG="$ROOT/tests/wamvm_select1_vm.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_SELECT1_VM_PROG" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wamvm_select1_vm"
fi

WAMVM_TERM_OPS_VM_PROG="$ROOT/tests/wamvm_term_ops_vm.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_TERM_OPS_VM_PROG" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wamvm_term_ops_vm"
fi

WAMVM_EXCEPTIONS_VM_PROG="$ROOT/tests/wamvm_exceptions_vm.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_EXCEPTIONS_VM_PROG" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wamvm_exceptions_vm"
fi

# VM-only compatibility pass: ensure common WAM-era ISO tests run entirely in WAMVM mode.
for f in "$ROOT"/tests/wam_*.pl; do
  base="$(basename "$f")"
  case "$base" in
    wam_flags.pl|wam_tabling.pl) continue ;;
  esac
  out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm --parallel-profile off -f "$f" -g 'ok.' | tr -d '\r')"
  out="${out%$'\n'}"
  if [[ "$out" != "true" ]]; then
    echo "--- expected ---" >&2
    echo "true" >&2
    echo "--- got ---" >&2
    printf '%s\n' "$out" >&2
    fail "wamvm_vm_only_${base%.pl}"
  fi
done

WAM_REFL_PROG="$ROOT/tests/wam_reflective.pl"
out="$(run_prog_out "$WAM_REFL_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_reflective"
fi

WAM_PROP_PROG="$ROOT/tests/wam_properties.pl"
out="$(run_prog_out "$WAM_PROP_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_properties"
fi

WAM_DYN_DB_PROG="$ROOT/tests/wam_dynamic_db.pl"
out="$(run_prog_out "$WAM_DYN_DB_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_dynamic_db"
fi

WAM_IO_PROG="$ROOT/tests/wam_stream_io.pl"
out="$(run_prog_out "$WAM_IO_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
  echo "--- expected ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "wam_stream_io"
fi

echo "ok"
tests/run_ctrld_repl.sh
#!/usr/bin/env bash
set -euo pipefail

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

make -C "$ROOT" -s >/dev/null

BIN="$ROOT/flowlog"

fail() {
  echo "fail: $1" >&2
  exit 1
}

tmpdir="$(mktemp -d -t flowlog_ctrld_repl.XXXXXX)"
cleanup() {
  rm -rf "$tmpdir" >/dev/null 2>&1 || true
}
trap cleanup EXIT

tester="$tmpdir/ctrld_repl_tty"
cc -O2 -Wall -Wextra "$ROOT/tests/repl_ctrld_tty.c" -lutil -o "$tester" || fail "ctrld_repl_build"

"$tester" "$BIN" >/dev/null 2>&1 || fail "ctrld_repl_exit"

echo "ok"
tests/run_bracket_consult_repl.sh
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BIN="$ROOT/flowlog"

make -C "$ROOT" -s >/dev/null

out="$(
  printf '%s\n' \
    '[bench/nqueens_length].' \
    'queens_solutions(4,2).' \
    'halt.' \
  | "$BIN" --threads 1 \
  | tr -d '\r'
)"

if ! printf '%s\n' "$out" | grep -Eq '^true$'; then
  echo "--- expected output containing ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  exit 1
fi
tests/run_inria_suite.sh
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
FLOWLOG_BIN="${FLOWLOG_BIN:-$ROOT/flowlog}"
FLOWLOG_TIMEOUT="${FLOWLOG_TIMEOUT:-120s}"
FLOWLOG_ENGINE="${FLOWLOG_ENGINE:-interp}"
FLOWLOG_PARALLEL_PROFILE="${FLOWLOG_PARALLEL_PROFILE:-off}"
# By default, suppress per-test stderr noise (the INRIA suite intentionally
# triggers a couple of "unknown procedure" warnings when `unknown=warning`).
# Set to 1 to see stderr for every file.
FLOWLOG_INRIA_SHOW_STDERR="${FLOWLOG_INRIA_SHOW_STDERR:-0}"

if [[ ! -x "$FLOWLOG_BIN" ]]; then
  make -C "$ROOT" >/dev/null
fi

suite_dir="$ROOT/build/inria_suite/inriasuite"
suite_driver="$suite_dir/inriasuite.pl"
suite_wrapper="$ROOT/build/inria_suite/flowlog_inriasuite.pl"

if [[ ! -f "$suite_driver" || ! -f "$suite_wrapper" ]]; then
  "$ROOT/tests/setup_inria_suite.sh" >/dev/null
fi

if [[ ! -f "$suite_driver" || ! -f "$suite_wrapper" ]]; then
  echo "inria: suite not set up (missing: $suite_driver and/or $suite_wrapper)" >&2
  exit 1
fi

mapfile -t files < <(
  rg '^file\(' "$suite_driver" \
    | sed -E 's/^file\((.*)\)\..*/\1/' \
    | sed -E 's/[[:space:]]+$//'
)

failures=0
total_files=0

echo "INRIA suite file-by-file, timeout=$FLOWLOG_TIMEOUT"

err_tmp=""
if [[ "$FLOWLOG_INRIA_SHOW_STDERR" == "0" || "$FLOWLOG_INRIA_SHOW_STDERR" == "off" || "$FLOWLOG_INRIA_SHOW_STDERR" == "false" ]]; then
  err_tmp="$(mktemp "$ROOT/build/inria_suite/flowlog_inria_stderr.XXXXXX")"
  trap 'rm -f "$err_tmp"' EXIT
fi

for f in "${files[@]}"; do
  [[ -n "$f" ]] || continue
  total_files=$((total_files + 1))

  out=""
  st=0
  set +e
  if [[ -n "$err_tmp" ]]; then
    : >"$err_tmp"
    out="$(
      timeout "$FLOWLOG_TIMEOUT" \
        "$FLOWLOG_BIN" --engine "$FLOWLOG_ENGINE" --parallel-profile "$FLOWLOG_PARALLEL_PROFILE" \
          "$suite_wrapper" -g "run_tests_score($f,T,W)." 2>"$err_tmp"
    )"
  else
    out="$(
      timeout "$FLOWLOG_TIMEOUT" \
        "$FLOWLOG_BIN" --engine "$FLOWLOG_ENGINE" --parallel-profile "$FLOWLOG_PARALLEL_PROFILE" \
          "$suite_wrapper" -g "run_tests_score($f,T,W)."
    )"
  fi
  st=$?
  set -e

  if [[ "$st" -ne 0 ]]; then
    echo "  $f: exit=$st" >&2
    if [[ -n "$err_tmp" && -s "$err_tmp" ]]; then
      sed -e 's/^/    /' "$err_tmp" >&2
    fi
    failures=$((failures + 1))
    continue
  fi

  t="$(printf '%s' "$out" | sed -nE 's/.*T = ([0-9]+).*/\1/p' | head -n 1 || true)"
  w="$(printf '%s' "$out" | sed -nE 's/.*W = ([0-9]+).*/\1/p' | head -n 1 || true)"

  if [[ -z "$t" || -z "$w" ]]; then
    echo "  $f: unable to parse output: $out" >&2
    if [[ -n "$err_tmp" && -s "$err_tmp" ]]; then
      sed -e 's/^/    /' "$err_tmp" >&2
    fi
    failures=$((failures + 1))
    continue
  fi

  if [[ "$w" != "0" ]]; then
    echo "  $f: wrong=$w/$t" >&2
    if [[ -n "$err_tmp" && -s "$err_tmp" ]]; then
      sed -e 's/^/    /' "$err_tmp" >&2
    fi
    failures=$((failures + 1))
  fi
done

if [[ "$failures" -eq 0 ]]; then
  echo "OK: $total_files files, 0 failing"
else
  echo "FAIL: $failures/$total_files files failing" >&2
  exit 1
fi
tests/run_orpar_nesting.sh
#!/usr/bin/env bash
set -euo pipefail

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

make -C "$ROOT" -s >/dev/null

BIN="$ROOT/flowlog"

fail() {
  echo "fail: $1" >&2
  exit 1
}

tmpdir="$(mktemp -d -t flowlog_orpar_nesting.XXXXXX)"
cleanup() {
  rm -rf "$tmpdir" >/dev/null 2>&1 || true
}
trap cleanup EXIT

log="$tmpdir/orpar.log"

# Use a small deterministic goal that triggers nested select/3 OR-par:
# - outer select_: 9 alternatives (spawns OR-par)
# - inner select_: 8 alternatives within an OR-par chunk (requires nesting)
out="$(
  FLOWLOG_DEBUG_ORPAR=all \
  FLOWLOG_DEBUG_ORPAR_LOG="$log" \
  "$BIN" --threads 10 -g \
    'set_prolog_flag(flowlog_parallel_profile, fast), set_prolog_flag(flowlog_parallel_or_nesting, 1), countall((select_(X,[1,2,3,4,5,6,7,8,9],R), select_(Y,R,_)), C).' \
    | tr -d '\r'
)"

out="${out%$'\n'}"
if [[ "$out" != "C = 72" ]]; then
  echo "--- expected ---" >&2
  echo "C = 72" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "orpar_nesting_output"
fi

starts="$(grep -c '^flowlog: ORPAR start' "$log" 2>/dev/null || true)"
if [[ "${starts:-0}" -lt 2 ]]; then
  echo "--- expected ORPAR starts >= 2 ---" >&2
  echo "--- got log ---" >&2
  cat "$log" >&2 || true
  fail "orpar_nesting_missing_nested_start"
fi

echo "ok"
tests/run_orpar_nesting_wam.sh
#!/usr/bin/env bash
set -euo pipefail

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

make -C "$ROOT" -s >/dev/null

BIN="$ROOT/flowlog"

fail() {
  echo "fail: $1" >&2
  exit 1
}

tmpdir="$(mktemp -d -t flowlog_orpar_nesting_wam.XXXXXX)"
cleanup() {
  rm -rf "$tmpdir" >/dev/null 2>&1 || true
}
trap cleanup EXIT

log="$tmpdir/orpar.log"

# Use a small deterministic goal that triggers nested select/3 OR-par:
# - outer select_: 9 alternatives (spawns OR-par)
# - inner select_: 8 alternatives within an OR-par chunk (requires nesting)
out="$(
  FLOWLOG_DEBUG_ORPAR=all \
  FLOWLOG_DEBUG_ORPAR_LOG="$log" \
  "$BIN" --threads 10 --engine wam --parallel-profile fast -g \
    'set_prolog_flag(flowlog_parallel_or_nesting, 1), countall((select_(X,[1,2,3,4,5,6,7,8,9],R), select_(Y,R,_)), C).' \
    | tr -d '\r'
)"

out="${out%$'\n'}"
if [[ "$out" != "C = 72" ]]; then
  echo "--- expected ---" >&2
  echo "C = 72" >&2
  echo "--- got ---" >&2
  printf '%s\n' "$out" >&2
  fail "orpar_nesting_output_wam"
fi

starts="$(grep -c '^flowlog: ORPAR start' "$log" 2>/dev/null || true)"
if [[ "${starts:-0}" -lt 2 ]]; then
  echo "--- expected ORPAR starts >= 2 ---" >&2
  echo "--- got log ---" >&2
  cat "$log" >&2 || true
  fail "orpar_nesting_missing_nested_start_wam"
fi

echo "ok"
tests/run_siginfo_key_repl.sh
#!/usr/bin/env bash
set -euo pipefail

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

make -C "$ROOT" -s >/dev/null

BIN="$ROOT/flowlog"

fail() {
  echo "fail: $1" >&2
  exit 1
}

tmpdir="$(mktemp -d -t flowlog_siginfo_key.XXXXXX)"
cleanup() {
  rm -rf "$tmpdir" >/dev/null 2>&1 || true
}
trap cleanup EXIT

tester="$tmpdir/siginfo_key_tty"
cc -O2 -Wall -Wextra "$ROOT/tests/repl_siginfo_key_tty.c" -lutil -o "$tester" || fail "siginfo_key_build"

"$tester" "$BIN" >/dev/null 2>&1 || fail "siginfo_key_output"

echo "ok"
tests/run_siginfo_repl.sh
#!/usr/bin/env bash
set -euo pipefail

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

make -C "$ROOT" -s >/dev/null

BIN="$ROOT/flowlog"

fail() {
  echo "fail: $1" >&2
  exit 1
}

if ! kill -l INFO >/dev/null 2>&1; then
  echo "skip (no SIGINFO)"
  exit 0
fi

out_file="$(mktemp -t flowlog_siginfo_repl.XXXXXX)"
cleanup() {
  rm -f "$out_file"
}
trap cleanup EXIT

coproc "$BIN" --threads 2 2>&1
pid="$COPROC_PID"
infd="${COPROC[1]}"
outfd="${COPROC[0]}"

printf 'write(start), nl, flush_output, repeat, fail.\n' >&"$infd"

seen_start=0
for ((i = 0; i < 200; i++)); do
  if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
    line="${line%$'\r'}"
    printf '%s\n' "$line" >>"$out_file"
    if [[ "$line" == "start" ]]; then
      seen_start=1
      break
    fi
  fi
done

if [[ "$seen_start" -ne 1 ]]; then
  kill -KILL "$pid" 2>/dev/null || true
  fail "siginfo_repl_no_start"
fi

kill -INFO "$pid" 2>/dev/null || kill -SIGINFO "$pid" 2>/dev/null || true

seen_info=0
seen_pred=0
timeouts_after_info=0
for ((i = 0; i < 400; i++)); do
  if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
    line="${line%$'\r'}"
    printf '%s\n' "$line" >>"$out_file"
    timeouts_after_info=0
    if [[ "$line" == "Flowlog SIGINFO (status)" ]]; then
      seen_info=1
    elif [[ "$line" == "  predicate:"* ]]; then
      seen_pred=1
    fi
    continue
  else
    rc=$?
    if [[ "$rc" -eq 142 ]]; then
      if [[ "$seen_info" -eq 1 ]]; then
        timeouts_after_info=$((timeouts_after_info + 1))
        if [[ "$timeouts_after_info" -ge 5 ]]; then
          break
        fi
      fi
      continue
    fi
    break
  fi
done

if [[ "$seen_info" -ne 1 ]]; then
  kill -KILL "$pid" 2>/dev/null || true
  fail "siginfo_repl_no_status"
fi
if [[ "$seen_pred" -ne 1 ]]; then
  kill -KILL "$pid" 2>/dev/null || true
  fail "siginfo_repl_no_predicate"
fi

# Cancel the running query and ensure the REPL continues to accept input.
kill -INT "$pid"
printf 'true.\n' >&"$infd"

# Wait until the REPL produces output for the next query before closing stdin.
seen_true=0
for ((i = 0; i < 400; i++)); do
  if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
    line="${line%$'\r'}"
    printf '%s\n' "$line" >>"$out_file"
    if [[ "$line" == "true" ]]; then
      seen_true=1
      break
    fi
    continue
  else
    rc=$?
    if [[ "$rc" -eq 142 ]]; then
      continue
    fi
    break
  fi
done

if [[ "$seen_true" -ne 1 ]]; then
  kill -KILL "$pid" 2>/dev/null || true
  echo "--- expected output containing ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  cat "$out_file" >&2
  fail "siginfo_repl_missing_true"
fi

printf 'halt.\n' >&"$infd"
exec {infd}>&-

seen_eof=0
for ((i = 0; i < 400; i++)); do
  if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
    line="${line%$'\r'}"
    printf '%s\n' "$line" >>"$out_file"
    continue
  else
    rc=$?
    if [[ "$rc" -eq 142 ]]; then
      continue
    fi
    seen_eof=1
    break
  fi
done

if [[ "$seen_eof" -ne 1 ]]; then
  kill -KILL "$pid" 2>/dev/null || true
  fail "siginfo_repl_timeout"
fi

wait "$pid" 2>/dev/null || true
{ exec {outfd}<&-; } 2>/dev/null || true

echo "ok"
tests/run_sigint_repl.sh
#!/usr/bin/env bash
set -euo pipefail

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

make -C "$ROOT" -s >/dev/null

BIN="$ROOT/flowlog"

fail() {
  echo "fail: $1" >&2
  exit 1
}

out_file="$(mktemp -t flowlog_sigint_repl.XXXXXX)"
cleanup() {
  rm -f "$out_file"
}
trap cleanup EXIT

coproc "$BIN" --threads 2 2>&1
pid="$COPROC_PID"
infd="${COPROC[1]}"
outfd="${COPROC[0]}"

# An infinite loop query (ISO: repeat/0 + fail/0) gives us time to deliver SIGINT.
# Print and flush a sentinel so we know the query is executing (not still reading input).
printf 'write(start), nl, flush_output, repeat, fail.\n' >&"$infd"

seen_start=0
for ((i = 0; i < 200; i++)); do
  if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
    line="${line%$'\r'}"
    printf '%s\n' "$line" >>"$out_file"
    if [[ "$line" == "start" ]]; then
      seen_start=1
      break
    fi
  fi
done

if [[ "$seen_start" -ne 1 ]]; then
  kill -KILL "$pid" 2>/dev/null || true
  fail "sigint_repl_no_start"
fi

kill -INT "$pid"

# The REPL should stay alive, keep the loaded program, and accept more queries.
printf 'true.\n' >&"$infd"

# Wait until the REPL produces output for the next query before closing stdin.
seen_true=0
for ((i = 0; i < 400; i++)); do
  if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
    line="${line%$'\r'}"
    printf '%s\n' "$line" >>"$out_file"
    if [[ "$line" == "true" ]]; then
      seen_true=1
      break
    fi
    continue
  else
    rc=$?
    if [[ "$rc" -eq 142 ]]; then
      continue
    fi
    break
  fi
done

if [[ "$seen_true" -ne 1 ]]; then
  kill -KILL "$pid" 2>/dev/null || true
  echo "--- expected output containing ---" >&2
  echo "true" >&2
  echo "--- got ---" >&2
  cat "$out_file" >&2
  fail "sigint_repl_missing_true"
fi

printf 'halt.\n' >&"$infd"
exec {infd}>&-

# Read output until EOF (process exit), with an overall timeout.
seen_eof=0
for ((i = 0; i < 400; i++)); do
  if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
    line="${line%$'\r'}"
    printf '%s\n' "$line" >>"$out_file"
    continue
  else
    rc=$?
    if [[ "$rc" -eq 142 ]]; then
      continue
    fi
    seen_eof=1
    break
  fi
done

if [[ "$seen_eof" -ne 1 ]]; then
  kill -KILL "$pid" 2>/dev/null || true
  fail "sigint_repl_timeout"
fi

wait "$pid" 2>/dev/null || true
{ exec {outfd}<&-; } 2>/dev/null || true

echo "ok"
tests/setup_inria_suite.sh
#!/usr/bin/env bash
set -euo pipefail

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

SUITE_URL="${FLOWLOG_INRIA_SUITE_URL:-http://pauillac.inria.fr/~deransar/prolog/inriasuite.tar.gz}"
DEST_DIR="$ROOT/build/inria_suite"
TARBALL="$DEST_DIR/inriasuite.tar.gz"
SUITE_DIR="$DEST_DIR/inriasuite"
DRIVER="$SUITE_DIR/inriasuite.pl"
WRAPPER="$DEST_DIR/flowlog_inriasuite.pl"

force=0
case "${1:-}" in
  "" ) ;;
  --force ) force=1 ;;
  * )
    echo "usage: $0 [--force]" >&2
    exit 2
    ;;
esac

mkdir -p "$DEST_DIR"

if [[ "$force" -eq 1 || ! -f "$TARBALL" ]]; then
  echo "inria: fetching: $SUITE_URL" >&2
  if command -v fetch >/dev/null 2>&1; then
    fetch -o "$TARBALL" "$SUITE_URL"
  elif command -v curl >/dev/null 2>&1; then
    curl -fsSL -o "$TARBALL" "$SUITE_URL"
  elif command -v wget >/dev/null 2>&1; then
    wget -qO "$TARBALL" "$SUITE_URL"
  else
    echo "inria: cannot fetch suite (need one of: fetch, curl, wget)" >&2
    exit 1
  fi
fi

if [[ "$force" -eq 1 || ! -d "$SUITE_DIR" || ! -f "$DRIVER" ]]; then
  rm -rf "$SUITE_DIR" >/dev/null 2>&1 || true
  tar -xzf "$TARBALL" -C "$DEST_DIR"
fi

if [[ ! -f "$DRIVER" ]]; then
  echo "inria: driver not found after extract: $DRIVER" >&2
  exit 1
fi

# Known INRIA-suite expectations that diverge from modern ISO implementations
# (confirmed with Trealla and Scryer). Patch these locally so `tests/run_inria_suite.sh`
# can be used as a regression suite for Flowlog's ISO behavior.
patch_inria_suite() {
  local tmp

  if [[ -f "$SUITE_DIR/catch-and-throw" ]]; then
    tmp="$(mktemp "$DEST_DIR/.catch-and-throw.XXXXXX")"
    awk -f - "$SUITE_DIR/catch-and-throw" >"$tmp" <<'AWK'
{
  if ($0 ~ /throw\(blabla\)/ && $0 ~ /system_error/) {
    gsub(/system_error/, "unexpected_ball(blabla)")
  }
  print
}
AWK
    mv "$tmp" "$SUITE_DIR/catch-and-throw"
  fi

  if [[ -f "$SUITE_DIR/sub_atom" ]]; then
    tmp="$(mktemp "$DEST_DIR/.sub_atom.XXXXXX")"
    awk -f - "$SUITE_DIR/sub_atom" >"$tmp" <<'AWK'
BEGIN { in_block = 0 }
in_block == 0 && $0 ~ /^[[:space:]]*\[sub_atom\('ab', Before, Length, After, Sub_atom\),/ {
  print "[sub_atom('ab', Before, Length, After, Sub_atom),"
  print "\t[[Before <-- 0, Length <-- 0, Sub_atom <-- ''],"
  print "\t [Before <-- 0, Length <-- 1, Sub_atom <-- 'a'],"
  print "\t [Before <-- 0, Length <-- 2, Sub_atom <-- 'ab'],"
  print "\t [Before <-- 1, Length <-- 0, Sub_atom <-- ''],"
  print "\t [Before <-- 1, Length <-- 1, Sub_atom <-- 'b'],"
  print "\t [Before <-- 2, Length <-- 0, Sub_atom <-- '']]]."
  in_block = 1
  next
}
in_block == 1 {
  if ($0 ~ /\]\]\]\./) {
    in_block = 0
  }
  next
}
{ print }
AWK
    mv "$tmp" "$SUITE_DIR/sub_atom"
  fi
}

patch_inria_suite

suite_abs="$(cd "$SUITE_DIR" && pwd)"
suite_abs_slash="${suite_abs%/}/"

{
  printf '%% Auto-generated by tests/setup_inria_suite.sh. Do not edit.\n'
  printf '%% Source: %s\n\n' "$SUITE_URL"
  cat <<'EOF'
% The INRIA suite driver is intended to be “vanilla ISO Prolog”, but its harness
% uses a couple of common library predicates that are not ISO/IEC 13211-1 core.
% Provide tiny reference definitions so the suite can run without depending on
% system-specific libraries/modules.

append([], Ys, Ys).
append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).

member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).

% Flowlog-specific harness overrides
%
% The upstream INRIA harness uses recursion for `loop_through/2`, which can
% tickle edge cases in some embedded runtimes. Use a repeat/fail loop instead.
%
% It also collects substitutions for *all* goal variables, but the INRIA test
% expectations typically mention only the variables of interest (via `<--/2`).
% Derive the variable set from the Expected substitutions to match the suite.

loop_through(F, S) :-
  repeat,
    catch(read(S, X), B, X = B),
    ( X = end_of_file ->
        !
    ; reset_flags,
      ( test(F, X) -> true ; update_score(F, non_null, non_null) ),
      fail
    ).

test(F, [G, Expected]) :-
  !,
  result_expected(G, Expected, R),
  compare_subst_lists(R, Expected, Extra, Missing),
  write_if_wrong(F, G, Expected, Extra, Missing),
  update_score(F, Missing, Extra).
test(F, [G, ProgFile, Expected]) :-
  !,
  [ProgFile],
  result_expected(G, Expected, R),
  compare_subst_lists(R, Expected, Extra, Missing),
  write_if_wrong(F, G, Expected, Extra, Missing),
  update_score(F, Missing, Extra).

result_expected(G, Expected, Res) :-
  expected_lhs_vars(Expected, Want),
  vars_in_term(G, GoalVars),
  filter_vars_eq(GoalVars, Want, Vars),
  get_all_subs_vars(G, Vars, Subs),
  special_ans_forms(Subs, Res).

expected_lhs_vars(Expected, Vars) :-
  ( Expected = [_|_] ; Expected == [] ),
  !,
  expected_lhs_vars_list(Expected, [], Vars).
expected_lhs_vars(_Expected, []).

expected_lhs_vars_list([], Vars, Vars).
expected_lhs_vars_list([Set|Rest], Vars0, Vars) :-
  expected_lhs_vars_set(Set, Vars0, Vars1),
  expected_lhs_vars_list(Rest, Vars1, Vars).

expected_lhs_vars_set([], Vars, Vars).
expected_lhs_vars_set([Sub|Rest], Vars0, Vars) :-
  ( Sub = <--(V, Val) ->
      vars_in_term(V, Vars0, Vars1),
      vars_in_term(Val, Vars1, Vars2)
  ; Vars2 = Vars0
  ),
  expected_lhs_vars_set(Rest, Vars2, Vars).

var_member_eq(V, [H|_]) :- V == H, !.
var_member_eq(V, [_|T]) :- var_member_eq(V, T).

filter_vars_eq([], _Set, []).
filter_vars_eq([V|Vs], Set, [V|Out]) :-
  var_member_eq(V, Set),
  !,
  filter_vars_eq(Vs, Set, Out).
filter_vars_eq([_|Vs], Set, Out) :- filter_vars_eq(Vs, Set, Out).

get_all_subs_vars(G, Vars, AllSubs) :-
  copy_term(Vars-G, Vars2-G2),
  findall(Ans, protect_call_vars(G2, Vars2, Ans), AnsList),
  list_make_subs_list(Vars, AnsList, AllSubs).

protect_call_vars(G, Vars2, R) :-
  catch((call(G), R = Vars2), B, extract_error(B, R)).
EOF
  printf '\n\n'
  cat "$DRIVER"
  printf '\n\n'
  printf "inria_suite_dir('%s').\n\n" "$suite_abs_slash"
  cat <<'EOF'
run_tests_score(Test, T, W) :-
  inria_suite_dir(Dir),
  atom_concat(Dir, Test, Path),
  run_tests(Path),
  score(Path, total(T), wrong(W)).
EOF
} >"$WRAPPER"

echo "inria: ready: $WRAPPER" >&2
tests/wam_bagof_setof.pl
:- set_prolog_flag(flowlog_engine, wam).

member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).

ok :-
    bagof(X, member(X, [b,a]), Bag0),
    Bag0 == [b,a],

    setof(X, member(X, [b,a,a]), Set0),
    Set0 == [a,b],

    Ps = [p(1,a), p(1,b), p(2,c)],
    findall(Y-Bag, bagof(X, member(p(Y,X), Ps), Bag), Groups),
    Groups == [1-[a,b], 2-[c]],

    bagof(X, Y^member(p(Y,X), Ps), Bag1),
    Bag1 == [a,b,c],

    \+ bagof(_, member(_, []), _),
    \+ setof(_, member(_, []), _).
tests/wam_control_constructs.pl
% WAM engine: ISO control constructs (;)/2 and (->)/2.

:- catch(set_prolog_flag(flowlog_engine, wam), _, true).

p(X) :- (X = 1 ; X = 2).

p_cut(X) :- (X = 1, ! ; X = 2).

p_ite(X, Y) :- (X = 1 -> Y = a ; Y = b).

p_ite_fail(Y) :- (fail -> Y = a ; Y = b).

p_if_then_only(Y) :- ((1 = 1) -> Y = a).

p_call(X) :- call(p(X)).

p_once(X) :- once(p(X)).

ok :-
  findall(X, p(X), L1),
  L1 == [1, 2],
  findall(X, p_cut(X), L2),
  L2 == [1],
  findall(pair(X,Y), p_ite(X, Y), L3),
  L3 == [pair(1, a)],
  p_ite_fail(Y4),
  Y4 == b,
  p_if_then_only(Y5),
  Y5 == a,
  findall(X, p_call(X), L4),
  L4 == [1, 2],
  findall(X, p_once(X), L5),
  L5 == [1].
tests/wam_conversions.pl
:- set_prolog_flag(flowlog_engine, wam).

len([], 0).
len([_|T], N) :- len(T, N0), N is N0 + 1.

ok :-
    % char_code/2
    char_code('a', 97),
    char_code('a', C1),
    C1 == 97,
    char_code(Ch, 97),
    Ch == 'a',
    \+ char_code('a', 98),
    catch(char_code(_,_), Ecc0, true),
    Ecc0 == error(instantiation_error, char_code/2),
    catch(char_code(97,_), Ecc1, true),
    Ecc1 == error(type_error(character, 97), char_code/2),
    catch(char_code(_, 256), Ecc2, true),
    Ecc2 == error(representation_error(character_code), char_code/2),

    % atom_length/2
    atom_length(hello, 5),
    atom_length(hello, L0),
    L0 == 5,
    \+ atom_length(hello, 6),
    catch(atom_length(_, _), Eal0, true),
    Eal0 == error(instantiation_error, atom),

    % atom_concat/3 (both directions)
    atom_concat(he, llo, hello),
    atom_concat(he, llo, C2),
    C2 == hello,
    findall(A-B, atom_concat(A, B, ab), Splits),
    Splits == [''-ab, a-b, ab-''],
    catch(atom_concat(_, _, _), Eac0, true),
    Eac0 == error(instantiation_error, atom_concat/3),

    % sub_atom/5
    sub_atom(abc, 0, 1, 2, a),
    sub_atom(abc, 1, 1, 1, b),
    sub_atom(abc, 2, 1, 0, c),
    findall(S, sub_atom(ab, _, _, _, S), Subs),
    len(Subs, 6),

    % atom_chars/2 and atom_codes/2
    atom_chars(abc, [a,b,c]),
    atom_chars(abc, Chs0),
    Chs0 == [a,b,c],
    atom_chars(A3, [a,b]),
    A3 == ab,
    catch(atom_chars(_, [_|_]), Echars0, true),
    Echars0 == error(instantiation_error, atom_chars/2),
    catch(atom_chars(abc, [97]), Echars1, true),
    Echars1 == error(type_error(character, 97), atom_chars/2),

    atom_codes(abc, [97,98,99]),
    atom_codes(abc, Codes0),
    Codes0 == [97,98,99],
    atom_codes(A4, [97,98]),
    A4 == ab,
    catch(atom_codes(abc, [a,98,99]), Ecodes0, true),
    Ecodes0 == error(type_error(integer, a), atom_codes/2),
    catch(atom_codes(_, [-1]), Ecodes1, true),
    Ecodes1 == error(representation_error(character_code), atom_codes/2),

    % number_chars/2 and number_codes/2
    number_chars(123, ['1','2','3']),
    number_chars(123, NChs0),
    NChs0 == ['1','2','3'],
    number_chars(N1, ['4','2']),
    N1 == 42,
    catch(number_chars(_, _), Enc0, true),
    Enc0 == error(instantiation_error, number_chars/2),
    catch(number_chars(a, _), Enc1, true),
    Enc1 == error(type_error(number, a), number_chars/2),
    catch(number_chars(_, [x]), Enc2, true),
    Enc2 == error(syntax_error(illegal_number), number_chars/2),

    number_codes(42, [52,50]),
    number_codes(42, NCodes0),
    NCodes0 == [52,50],
    number_codes(N2, [45,49]),
    N2 == -1.
tests/wam_copy_vars.pl
:- set_prolog_flag(flowlog_engine, wam).

ok :-
    % copy_term/2 preserves sharing and makes fresh variables.
    copy_term(foo(X,Y), T1),
    T1 = foo(A,B),
    A \== B,
    copy_term(foo(X,X), T2),
    T2 = foo(C,D),
    C == D,
    copy_term(X, Y),
    X \== Y,
    X = foo(Z),
    copy_term(X, Y2),
    Y2 = foo(A2),
    var(A2),
    A2 \== Z,

    % term_variables/2 follows bindings and returns unique vars in order.
    X3 = foo(U,V),
    term_variables(X3, Vars0),
    Vars0 == [U,V],
    X4 = foo(W),
    X5 = X4,
    term_variables(X5, Vars1),
    Vars1 == [W],

    % numbervars/3 binds vars to '$VAR'(N) terms.
    numbervars(foo(N1,N2), 0, End),
    N1 == '$VAR'(0),
    N2 == '$VAR'(1),
    End == 2,

    % unify_with_occurs_check/2 rejects cyclic bindings.
    \+ unify_with_occurs_check(Q, f(Q)),
    unify_with_occurs_check(P, R),
    P == R,

    % subsumes_term/2 uses rigid spec variables (Spec must be an instance of Gen).
    subsumes_term(foo(S,1), foo(2,1)),
    \+ subsumes_term(foo(_S,1), foo(2,2)),
    subsumes_term(foo(S2,S2), foo(1,1)),
    \+ subsumes_term(foo(S3,S3), foo(1,2)),
    \+ subsumes_term(f(_), X6).
tests/wam_dynamic_db.pl
% WAM-lite dynamic database predicates (assert*/retract*/abolish/clause) smoke tests.

:- catch(set_prolog_flag(flowlog_engine, wam), _, true).

ok :-
    set_prolog_flag(unknown, fail),

    % Start from a clean slate (undefined predicates are ok for retractall/1).
    retractall(p(_)),
    retractall(q(_)),

    asserta(p(a)),
    assertz(p(b)),
    findall(X, p(X), Xs),
    Xs = [a, b],

    retract(p(a)),
    findall(X, p(X), Xs2),
    Xs2 = [b],

    assertz((q(b) :- p(b))),
    clause(q(b), Body),
    Body = p(b),

    abolish(q/1),
    \+ current_predicate(q/1),
    !.
tests/wam_engine_smoke.pl
% Smoke tests for the WAM-lite execution engine.
% Standalone Flowlog defaults to WAM; the directive below keeps this file stable
% if defaults change in the future.

:- catch(set_prolog_flag(flowlog_engine, wam), _, true).

fact(0, 1).
fact(N, F) :-
  N > 0,
  N1 is N - 1,
  fact(N1, F1),
  F is N * F1.

p(1).
p(2).

count(Count) :-
  findall(1, p(_), Ones),
  length(Ones, Count).

ok :-
  % length/2 should be able to instantiate an open list tail when N is fixed.
  length([a|T], 3),
  T = [_,_],
  fact(5, F),
  F = 120,
  count(C),
  C = 2.
tests/wam_exceptions.pl
:- set_prolog_flag(flowlog_engine, wam).

ok :-
    % Basic throw/catch.
    catch(throw(foo(a)), foo(X), X == a),

    % throw/1 argument must be nonvar -> instantiation_error.
    catch(throw(_), E1, E1 = error(instantiation_error, nonvar)),

    % Catcher can bind to a thrown term containing fresh variables.
    catch(throw(foo(_X)), E2, (E2 = foo(V), var(V))),

    % Inner catch does not catch mismatched exception; it must propagate.
    catch(catch(throw(bar), foo(_), true), bar, true),

    % Recover goal exceptions are not caught by the original catch/3.
    catch(catch(throw(foo), foo, throw(baz)), baz, true).
tests/wam_extra_builtins.pl
:- set_prolog_flag(flowlog_engine, wam).

p.

ok :-
    bounds_possible([1, 2, 3, 4], 2, 3),
    \+ bounds_possible([1, 2, 3, 4], 2, 100),

    current_rule(true/0),
    current_rule(p/0),
    \+ current_rule(no_such_pred/0),

    flowlog_cpuproc_id(CP), integer(CP),
    flowlog_n_cpuproc(NCP), integer(NCP),
    flowlog_in_cpuproc(In), (In == true ; In == false),
    flowlog_taskjob_id(TJ), integer(TJ),
    flowlog_n_taskjob(NTJ), integer(NTJ),
    flowlog_last_tc_cp_mask(Mask), integer(Mask),
    flowlog_last_tc_cp_count(Cnt), integer(Cnt),
    flowlog_last_tc_worker_count(WC), integer(WC),
    flowlog_last_tc_start_ok(Ok), integer(Ok),
    flowlog_last_tc_start_error_code(EC), integer(EC),
    flowlog_last_tc_start_error_text(ET), atom(ET),
    flowlog_db_epochs(CE, IE), integer(CE), integer(IE),

    flowlog_and_safe(p/0, Safe0), (Safe0 == true ; Safe0 == false),
    flowlog_and_safe_goal(p, SafeG0), (SafeG0 == true ; SafeG0 == false),

    current_output(S0),
    open('/dev/null', write, S),
    set_output(S),
    listing,
    listing(p),
    set_output(S0),
    close(S).
tests/wam_flags.pl
% WAM-lite ISO prolog_flag + char_conversion coverage.

:- catch(set_prolog_flag(flowlog_engine, wam), _, true).
:- catch(set_prolog_flag(occurs_check, true), _, true).

ok :-
    current_prolog_flag(flowlog_engine_active, Active),
    Active == wam,

    current_prolog_flag(occurs_check, OC0),
    OC0 == true,
    \+ (X = f(X)),

    current_prolog_flag(max_arity, A),
    A == 255,

    set_prolog_flag(unknown, fail),
    current_prolog_flag(unknown, U),
    U == fail,
    set_prolog_flag(unknown, error),

    set_prolog_flag(char_conversion, on),
    current_prolog_flag(char_conversion, CC),
    CC == on,
    char_conversion(a, b),
    current_char_conversion(a, b),

    current_prolog_flag(Name1, _),
    Name1 == unknown,
    !,
    current_prolog_flag(Name2, _),
    Name2 == char_conversion,
    !.
tests/wam_ops.pl
:- set_prolog_flag(flowlog_engine, wam).

ok :-
    op(500, xfx, foo),
    current_op(500, xfx, foo),
    findall(P, current_op(P, xfx, foo), Ps),
    Ps == [500],

    catch(op(500, xfx, ','), E0, true),
    E0 == error(permission_error(modify, operator, ','), op/3),

    catch(op(500, xfx, []), E1, true),
    E1 == error(permission_error(create, operator, []), op/3),

    catch(op(500, xfx, '|'), E2, true),
    E2 == error(permission_error(create, operator, '|'), op/3),

    catch(op(1201, xfx, bar), E3, true),
    E3 == error(domain_error(operator_priority, 1201), op/3),

    catch(op(500, zzz, bar), E4, true),
    E4 == error(domain_error(operator_specifier, zzz), op/3).
tests/wam_properties.pl
% WAM-lite predicate_property/2 + evaluable_property/2 smoke tests.

:- catch(set_prolog_flag(flowlog_engine, wam), _, true).

p(a).

ok :-
    predicate_property(true, built_in),
    predicate_property(true, static),
    \+ predicate_property(true, dynamic),

    predicate_property(p(_), static),
    dynamic(foo/1),
    predicate_property(foo(_), dynamic),

    predicate_property(write(_), template(Tw)),
    functor(Tw, write, 1),

    evaluable_property(+(1, 2), template(T, R)),
    functor(T, '+', 2),
    arg(1, T, A), A == number,
    arg(2, T, B), B == number,
    R == number.
tests/wam_reflective.pl
% WAM-lite reflective predicates (dynamic/current_predicate) smoke tests.

:- catch(set_prolog_flag(flowlog_engine, wam), _, true).

p(a).
q(a, b).

ok :-
    dynamic(foo/1),
    current_predicate(foo/1),
    current_predicate(p/1),
    current_predicate(q/2),
    current_predicate(/(Name, 1)),
    Name == p,
    !.
tests/wam_sorting.pl
:- set_prolog_flag(flowlog_engine, wam).

ok :-
    sort([2, 1, 1], S),
    S == [1, 2],

    msort([2, 1, 1], MS),
    MS == [1, 1, 2],

    keysort([2-b, 1-a, 1-c], KS),
    KS == [1-a, 1-c, 2-b].
tests/wam_stream_io.pl
% WAM-lite stream/I/O smoke tests.

ok :-
    current_output(S0),
    set_output(S0),
    current_output(S1),
    S0 == S1,

    File = 'build/tmp_wam_io_test.txt',
    open(File, write, S),
    tab(S, 3),
    write(S, hello),
    nl(S),
    flush_output(S),
    close(S),

    open(File, read, S2),
    stream_property(S2, file_name(FN)),
    FN == File,

    set_stream(S2, alias(foo)),
    stream_property(foo, alias(foo)),
    close(S2),

    Empty = 'build/tmp_wam_io_empty.txt',
    open(Empty, write, S3),
    close(S3),
    open(Empty, read, S4),
    at_end_of_stream(S4),
    set_input(S4),
    at_end_of_stream,
    close(S4),

    Txt = 'build/tmp_wam_io_text.txt',
    open(Txt, write, TW),
    put_code(TW, 65),
    put_char(TW, b),
    nl(TW),
    close(TW),
    open(Txt, read, TR),
    peek_code(TR, C0), C0 == 65,
    get_code(TR, C1), C1 == 65,
    get_char(TR, Ch0), Ch0 == b,
    unget_char(TR, b),
    peek_char(TR, Ch1), Ch1 == b,
    get_char(TR, Ch2), Ch2 == b,
    get_code(TR, NLCode), NLCode == 10,
    get_char(TR, EOF), EOF == end_of_file,
    close(TR),

    Bin = 'build/tmp_wam_io_bin.dat',
    open(Bin, write, BW, [type(binary)]),
    put_byte(BW, 255),
    close(BW),
    open(Bin, read, BR, [type(binary)]),
    peek_byte(BR, B0), B0 == 255,
    get_byte(BR, B1), B1 == 255,
    unget_byte(BR, 255),
    get_byte(BR, B2), B2 == 255,
    get_byte(BR, B3), B3 == -1,
    close(BR),

    ReadFile = 'build/tmp_wam_read_term.pl',
    open(ReadFile, write, RW),
    write(RW, 'foo(a).'), nl(RW),
    write(RW, 'bar(X,_).'), nl(RW),
    close(RW),
    open(ReadFile, read, RR),
    read_term(RR, T1, []), T1 = foo(a),
    read_term(RR, T2, [variables(Vs), variable_names(VNs), singletons(Ss)]),
    functor(T2, bar, 2),
    arg(1, T2, A1), var(A1),
    arg(2, T2, A2), var(A2),
    A1 \== A2,
    Vs = [V1, V2], V1 == A1, V2 == A2,
    VNs = [Name=Var], Name == 'X', Var == A1,
    Ss = [Name2=Var2], Name2 == 'X', Var2 == A1,
    read_term(RR, EOF2, []), EOF2 == end_of_file,
    close(RR).
tests/wam_sub_string.pl
:- set_prolog_flag(flowlog_engine, wam).

ok :-
    sub_string(hello, 1, 3, 1, Sub),
    Sub == ell,

    findall(S, sub_string(ab, _, _, _, S), Ss),
    sort(Ss, U),
    U == ['', a, ab, b].
tests/wam_tabling.pl
% WAM tabling smoke tests (call-by-variant).

:- catch(set_prolog_flag(flowlog_engine, wam), _, true).

:- table(path/2).
:- table(p/1).

edge(a,b).
edge(b,c).
edge(c,a).
edge(c,d).

path(X,Y) :- edge(X,Y).
path(X,Y) :- edge(X,Z), path(Z,Y).

% Left recursion: should terminate under tabling.
p(X) :- p(X).
p(1).

ok :-
  current_prolog_flag(flowlog_engine_active, wam),
  setof(X, path(a,X), Xs),
  Xs = [a,b,c,d],
  setof(Y, p(Y), Ys),
  Ys = [1].
tests/wam_term_ops.pl
:- set_prolog_flag(flowlog_engine, wam).

ok :-
    functor(foo(a,b), N, A),
    N == foo,
    A == 2,

    functor(T, foo, 2),
    arg(1, T, X),
    arg(2, T, Y),
    var(X),
    var(Y),
    X \== Y,

    T =.. [foo, X, Y],
    foo(a,b) =.. L,
    L = [foo, a, b],
    5 =.. [5],
    foo =.. [foo],

    U =.. [foo, a, b],
    U == foo(a,b),

    compare('<', a, b),
    compare('>', b, a),
    compare('=', a, a),
    compare(C, a, b),
    C == '<',
    \+ compare('>', a, b),
    \+ arg(0, foo(a,b), _),
    functor(T2, 5, 0),
    T2 == 5.
tests/wamvm_exceptions_vm.pl
% WAMVM exception/catch handling regression tests.
%
% Run with:
%   FLOWLOG_WAMVM_REQUIRE_VM=1 ./flowlog --engine wamvm -f tests/wamvm_exceptions_vm.pl -g ok.
%
% This covers a subtle VM bug where catching an exception thrown inside a
% conjunction could corrupt the exception term (due to premature release of
% query-temporary allocations).

:- catch(set_prolog_flag(flowlog_engine, wamvm), _, true).

ok :-
  % Catch an exception thrown from within a conjunction: the caught term must
  % remain valid and matchable by the catcher pattern.
  catch((call(asserta(_)), R0 = []), error(instantiation_error, _), R0 = instantiation_error),
  R0 == instantiation_error,

  % Type errors must preserve their structure/culprit.
  catch(call(asserta((foo :- 4))), error(type_error(callable, 4), _), true),

  % Database op errors should be catchable and stable too.
  catch(abolish(foo/a), error(type_error(integer, a), _), true),
  catch(abolish(foo/ -1), error(domain_error(not_less_than_zero, -1), _), true).
tests/wamvm_findall_var_goal_vm.pl
% WAMVM VM test: `findall/3` where the goal is passed via a variable.

p(1).
p(2).
p(3).

count_solutions(Goal, Count) :-
    findall(1, Goal, Ones),
    length(Ones, Count).

ok :-
    count_solutions(p(_), Count),
    Count == 3.
tests/wamvm_flags.pl
% Engine selection smoke test for `flowlog_engine=wamvm`.

:- catch(set_prolog_flag(flowlog_engine, wamvm), _, true).

ok :-
    current_prolog_flag(flowlog_engine_active, Active),
    Active == wamvm,
    X is 1+2,
    X == 3,
    sort([2, 1, 1], S),
    S == [1, 2].
tests/wamvm_select1_vm.pl
% WAMVM VM test: built-in `select_/3` (choicepoint + OR-par split target).
% The check is order-insensitive so it remains valid under unordered OR-par.

sum_list([], 0).
sum_list([X|Xs], S) :-
    sum_list(Xs, S0),
    S is S0 + X.

sum_squares([], 0).
sum_squares([X|Xs], S) :-
    sum_squares(Xs, S0),
    S is S0 + X*X.

ok :-
    findall(X, select_(X, [1,2,3,4,5,6,7,8], _), Xs),
    length(Xs, 8),
    sum_list(Xs, 36),
    sum_squares(Xs, 204).
tests/wamvm_term_ops_vm.pl
% WAMVM VM-required term ops + control constructs smoke tests.
%
% Run with:
%   FLOWLOG_WAMVM_REQUIRE_VM=1 ./flowlog --engine wamvm -f tests/wamvm_term_ops_vm.pl -g ok.

p(1).
p(2).

q(1).
q(2).

add1(X, Y) :-
  Y is X + 1.

ok :-
  % once/1 commits to the first solution.
  findall(X, once(p(X)), XsOnce),
  XsOnce = [1],

  % if-then commits to the first success of If.
  ( q(X) -> true ),
  X = 1,

  % if-then-else: Else is not considered once If succeeds.
  ( q(Y) -> true ; Y = 99 ),
  Y = 1,

  % Else branch runs when If fails.
  ( fail -> Z = then ; Z = else ),
  Z = else,

  % If succeeds but Then fails: do NOT run Else.
  ( (true -> fail ; true) -> fail ; true ),

  % call/N (closure expansion).
  call(add1, 1, R),
  R = 2,

  % Module qualification is accepted (Flowlog ignores modules).
  :(m, true),

  % =../2 (univ) round-trips.
  foo(a, b) =.. L1,
  L1 = [foo, a, b],
  T2 =.. [foo, a, b],
  T2 = foo(a, b),

  % functor/3 deconstruct + construct.
  functor(foo(a, b), F, N),
  F = foo,
  N = 2,
  functor(T3, foo, 2),
  T3 = foo(_, _),

  % arg/3.
  arg(2, foo(a, b, c), A2),
  A2 = b,

  % compare/3.
  compare(C1, 1, 2),
  C1 = '<',
  compare('>', 2, 1),
  compare('=', 1, 1),

  % term_variables/2.
  term_variables(f(Xv, Yv, Xv, 1), Vars),
  Vars = [Xv, Yv],

  % copy_term/2 makes fresh variables.
  copy_term(f(Xc), f(Yc)),
  Xc = 1,
  var(Yc),
  Yc = 2,

  % unify_with_occurs_check/2.
  ( unify_with_occurs_check(U, f(U)) -> fail ; var(U) ),

  % acyclic_term/1 detects cycles (rational trees).
  V = f(V),
  \+ acyclic_term(V),

  % ground/1.
  ground(f(1, 2)),
  \+ ground(f(G)),
  var(G).
tests/wamvm_vm_smoke.pl
% Smoke test that forces the WAMVM bytecode VM path (no fallback).
%
% The test harness runs Flowlog with:
%   FLOWLOG_WAMVM_REQUIRE_VM=1 --engine wamvm
%
% so any fallback to the WAM-lite solver is treated as a failure.

q(1).
q(2).

r(1).
r(2).

p(X) :-
    q(X),
    r(X).