|
5 | 5 | # VGI worker, using a prebuilt standalone `haybarn-unittest` and the signed |
6 | 6 | # community `vgi` extension — no C++ build from source. See ci/README.md. |
7 | 7 | # |
| 8 | +# The SAME suite is exercised over three VGI transports, selected by $TRANSPORT. |
| 9 | +# The vgi extension picks the transport from the LOCATION string the .test files |
| 10 | +# ATTACH (`${VGI_RERANK_WORKER}`): |
| 11 | +# |
| 12 | +# subprocess : a bare stdio command (`.venv/bin/python rerank_worker.py`) — the |
| 13 | +# extension spawns the worker per query and talks Arrow IPC over |
| 14 | +# stdin/stdout. Default; current behavior. |
| 15 | +# http : the worker is started out-of-band in `--http` mode on an auto |
| 16 | +# port; LOCATION becomes `http://127.0.0.1:<port>`. |
| 17 | +# unix : the worker is started out-of-band on an AF_UNIX socket; |
| 18 | +# LOCATION becomes `unix:///path/to.sock`. |
| 19 | +# |
8 | 20 | # Required environment: |
9 | | -# HAYBARN_UNITTEST path to the haybarn-unittest binary |
10 | | -# VGI_RERANK_WORKER worker LOCATION the .test files ATTACH (a stdio command |
11 | | -# such as `uv run rerank_worker.py`, or an http:// URL) |
| 21 | +# HAYBARN_UNITTEST path to the haybarn-unittest binary |
| 22 | +# TRANSPORT subprocess | http | unix (default: subprocess) |
| 23 | +# WORKER_CMD the stdio command that runs the worker. Used directly |
| 24 | +# as the LOCATION for subprocess, and as the process to |
| 25 | +# boot the server for http/unix. Defaults to |
| 26 | +# `uv run --python 3.13 <repo>/rerank_worker.py`. |
12 | 27 | # Optional: |
13 | | -# STAGE scratch dir for the preprocessed test tree (default: mktemp) |
| 28 | +# STAGE scratch dir for the preprocessed test tree (default: mktemp) |
14 | 29 | set -euo pipefail |
15 | 30 |
|
16 | 31 | : "${HAYBARN_UNITTEST:?path to the haybarn-unittest binary}" |
17 | | -: "${VGI_RERANK_WORKER:?worker LOCATION (stdio command or http:// URL)}" |
18 | 32 |
|
19 | 33 | HERE="$(cd "$(dirname "$0")" && pwd)" |
20 | 34 | REPO="$(cd "$HERE/.." && pwd)" |
21 | 35 | STAGE="${STAGE:-$(mktemp -d)}" |
| 36 | +TRANSPORT="${TRANSPORT:-subprocess}" |
| 37 | +WORKER_CMD="${WORKER_CMD:-uv run --python 3.13 $REPO/rerank_worker.py}" |
22 | 38 |
|
23 | 39 | echo "Staging preprocessed tests into $STAGE ..." |
24 | 40 | mkdir -p "$STAGE/test/sql" |
25 | 41 | for f in "$REPO"/test/sql/*.test; do |
26 | 42 | awk -f "$HERE/preprocess-require.awk" "$f" > "$STAGE/test/sql/$(basename "$f")" |
27 | 43 | done |
28 | 44 |
|
| 45 | +# --------------------------------------------------------------------------- |
| 46 | +# Per-transport: resolve VGI_RERANK_WORKER (the LOCATION) and, for the |
| 47 | +# out-of-band transports, boot the worker server + arrange trap-cleanup. |
| 48 | +# --------------------------------------------------------------------------- |
| 49 | +SERVER_PID="" |
| 50 | +SOCK="" |
| 51 | +PORT_FILE="" |
| 52 | + |
| 53 | +cleanup() { |
| 54 | + # Capture the real exit status FIRST: an EXIT trap whose last command returns |
| 55 | + # non-zero (e.g. a short-circuited `[[ -n "" ]] && …` on the subprocess/unix |
| 56 | + # legs where nothing needs cleaning) would otherwise become the script's exit |
| 57 | + # status under `set -e` and fail an already-passing run. |
| 58 | + local rc=$? |
| 59 | + if [[ -n "$SERVER_PID" ]]; then |
| 60 | + kill "$SERVER_PID" 2>/dev/null || true |
| 61 | + wait "$SERVER_PID" 2>/dev/null || true |
| 62 | + fi |
| 63 | + if [[ -n "$SOCK" ]]; then rm -f "$SOCK"; fi |
| 64 | + if [[ -n "$PORT_FILE" ]]; then rm -f "$PORT_FILE"; fi |
| 65 | + return "$rc" |
| 66 | +} |
| 67 | +trap cleanup EXIT |
| 68 | + |
| 69 | +case "$TRANSPORT" in |
| 70 | + subprocess) |
| 71 | + export VGI_RERANK_WORKER="$WORKER_CMD" |
| 72 | + ;; |
| 73 | + |
| 74 | + http) |
| 75 | + # The vgi extension's HTTP transport is implemented on top of DuckDB's |
| 76 | + # httpfs extension, so an `http://` ATTACH binds with |
| 77 | + # "Binder Error: VGI HTTP transport requires the httpfs extension." |
| 78 | + # unless httpfs is loaded first. (The haybarn sqllogictest runner's default |
| 79 | + # skip list swallows any error containing "HTTP", so without this the whole |
| 80 | + # suite would silently SKIP rather than fail — a fake pass.) The .test files |
| 81 | + # are transport-agnostic; inject a signed `INSTALL httpfs FROM core; LOAD |
| 82 | + # httpfs;` right after the awk-injected `LOAD vgi;` in each staged file, so |
| 83 | + # httpfs is present only when we actually run over HTTP. |
| 84 | + echo "Injecting httpfs load into staged tests (HTTP transport needs it) ..." |
| 85 | + for sf in "$STAGE"/test/sql/*.test; do |
| 86 | + awk ' |
| 87 | + { print } |
| 88 | + /^LOAD[ \t]+vgi[ \t]*;[ \t]*$/ && !done { |
| 89 | + print ""; |
| 90 | + print "statement ok"; |
| 91 | + print "INSTALL httpfs FROM core;"; |
| 92 | + print ""; |
| 93 | + print "statement ok"; |
| 94 | + print "LOAD httpfs;"; |
| 95 | + done = 1 |
| 96 | + } |
| 97 | + ' "$sf" > "$sf.tmp" && mv "$sf.tmp" "$sf" |
| 98 | + done |
| 99 | + |
| 100 | + # Boot the worker in HTTP mode on an auto-selected port. The worker writes |
| 101 | + # the chosen port to --port-file atomically (tmp + rename), so we watch for |
| 102 | + # the file to appear rather than parsing stdout. HTTP mode needs the `http` |
| 103 | + # extra (waitress); WORKER_CMD must resolve it — CI runs |
| 104 | + # `uv sync --extra http` and the PEP 723 header lists `vgi-python[http]`. |
| 105 | + PORT_FILE="$(mktemp -u "${TMPDIR:-/tmp}/rerank-port.XXXXXX")" |
| 106 | + LOG_FILE="${TMPDIR:-/tmp}/rerank-http-server.log" |
| 107 | + echo "Starting HTTP worker: $WORKER_CMD --http --port 0 --port-file $PORT_FILE" |
| 108 | + # shellcheck disable=SC2086 |
| 109 | + ( cd "$REPO" && exec $WORKER_CMD --http --port 0 --port-file "$PORT_FILE" ) > "$LOG_FILE" 2>&1 & |
| 110 | + SERVER_PID=$! |
| 111 | + |
| 112 | + PORT="" |
| 113 | + for _ in $(seq 1 240); do |
| 114 | + if ! kill -0 "$SERVER_PID" 2>/dev/null; then |
| 115 | + echo "ERROR: HTTP worker exited before reporting a port. Log:" >&2 |
| 116 | + cat "$LOG_FILE" >&2 |
| 117 | + exit 1 |
| 118 | + fi |
| 119 | + if [[ -s "$PORT_FILE" ]]; then |
| 120 | + PORT="$(tr -d '[:space:]' < "$PORT_FILE")" |
| 121 | + [[ -n "$PORT" ]] && break |
| 122 | + fi |
| 123 | + sleep 0.5 |
| 124 | + done |
| 125 | + if [[ -z "$PORT" ]]; then |
| 126 | + echo "ERROR: timed out waiting for HTTP worker port-file. Log:" >&2 |
| 127 | + cat "$LOG_FILE" >&2 |
| 128 | + exit 1 |
| 129 | + fi |
| 130 | + echo "HTTP worker ready on port $PORT (pid $SERVER_PID)" |
| 131 | + export VGI_RERANK_WORKER="http://127.0.0.1:$PORT" |
| 132 | + ;; |
| 133 | + |
| 134 | + unix) |
| 135 | + # Boot the worker bound to an AF_UNIX socket. The worker prints |
| 136 | + # `UNIX:<abs-path>` once bound; we poll for the socket file to appear. |
| 137 | + SOCK="${TMPDIR:-/tmp}/rerank-$$.sock" |
| 138 | + rm -f "$SOCK" |
| 139 | + LOG_FILE="${TMPDIR:-/tmp}/rerank-unix-server.log" |
| 140 | + echo "Starting unix worker: $WORKER_CMD --unix $SOCK" |
| 141 | + # shellcheck disable=SC2086 |
| 142 | + ( cd "$REPO" && exec $WORKER_CMD --unix "$SOCK" ) > "$LOG_FILE" 2>&1 & |
| 143 | + SERVER_PID=$! |
| 144 | + |
| 145 | + READY="" |
| 146 | + for _ in $(seq 1 240); do |
| 147 | + if ! kill -0 "$SERVER_PID" 2>/dev/null; then |
| 148 | + echo "ERROR: unix worker exited before binding the socket. Log:" >&2 |
| 149 | + cat "$LOG_FILE" >&2 |
| 150 | + exit 1 |
| 151 | + fi |
| 152 | + if [[ -S "$SOCK" ]]; then |
| 153 | + READY=1 |
| 154 | + break |
| 155 | + fi |
| 156 | + sleep 0.5 |
| 157 | + done |
| 158 | + if [[ -z "$READY" ]]; then |
| 159 | + echo "ERROR: timed out waiting for unix worker socket. Log:" >&2 |
| 160 | + cat "$LOG_FILE" >&2 |
| 161 | + exit 1 |
| 162 | + fi |
| 163 | + echo "unix worker ready on $SOCK (pid $SERVER_PID)" |
| 164 | + export VGI_RERANK_WORKER="unix://$SOCK" |
| 165 | + ;; |
| 166 | + |
| 167 | + *) |
| 168 | + echo "ERROR: unknown TRANSPORT '$TRANSPORT' (want subprocess|http|unix)" >&2 |
| 169 | + exit 2 |
| 170 | + ;; |
| 171 | +esac |
| 172 | + |
29 | 173 | cd "$STAGE" |
30 | 174 |
|
31 | 175 | # Warm the extension cache once: vgi from the signed community channel. A miss |
|
42 | 186 | "$HAYBARN_UNITTEST" "test/_warm.test" >/dev/null 2>&1 || echo "::warning::extension warm step did not fully succeed" |
43 | 187 | rm -f "$STAGE/test/_warm.test" |
44 | 188 |
|
45 | | -# Run the whole suite in one invocation, streaming the runner's native |
46 | | -# sqllogictest report. Any failed assertion exits non-zero and fails the job. |
47 | | -echo "Running suite (worker: $VGI_RERANK_WORKER) ..." |
48 | | -"$HAYBARN_UNITTEST" "test/sql/*" |
| 189 | +# Run the whole suite in one invocation, capturing the runner's native |
| 190 | +# sqllogictest report so we can both stream it AND assert on the summary line. |
| 191 | +echo "Running suite (transport: $TRANSPORT, worker: $VGI_RERANK_WORKER) ..." |
| 192 | +RUN_LOG="$STAGE/run.log" |
| 193 | +set +e |
| 194 | +"$HAYBARN_UNITTEST" "test/sql/*" 2>&1 | tee "$RUN_LOG" |
| 195 | +status=${PIPESTATUS[0]} |
| 196 | +set -e |
| 197 | + |
| 198 | +# SILENT-SKIP GUARD (critical for the http leg). DuckDB's sqllogictest runner |
| 199 | +# auto-SKIPS (exit 0!) any test whose error message contains "HTTP" or "Unable |
| 200 | +# to connect" — so a broken http setup reports "All tests were skipped" and the |
| 201 | +# job goes GREEN while testing nothing. Fail the leg unless the runner reports a |
| 202 | +# real pass with N>0 assertions and reported zero skips. |
| 203 | +if [[ $status -ne 0 ]]; then |
| 204 | + echo "ERROR: haybarn-unittest exited $status" >&2 |
| 205 | + exit "$status" |
| 206 | +fi |
| 207 | +if grep -Eqi 'were skipped|tests were skipped' "$RUN_LOG"; then |
| 208 | + echo "ERROR: tests were SKIPPED (likely a masked $TRANSPORT transport error — see above)." >&2 |
| 209 | + exit 1 |
| 210 | +fi |
| 211 | +if ! grep -Eq 'All tests passed \([1-9][0-9]* assertions' "$RUN_LOG"; then |
| 212 | + echo "ERROR: did not find an 'All tests passed (N assertions ...)' summary with N>0." >&2 |
| 213 | + exit 1 |
| 214 | +fi |
| 215 | +echo "Suite GREEN over transport: $TRANSPORT" |
0 commit comments