Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,13 @@ jobs:
GH_TOKEN: ${{ github.token }}

integration:
name: End-to-end SQL on ${{ matrix.os }}
name: SQL E2E (${{ matrix.transport }}) on ${{ matrix.os }}
needs: resolve-haybarn
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
transport: [subprocess, http, unix]
include:
- { os: ubuntu-latest, asset: haybarn_unittest-linux-amd64.zip }
- { os: macos-latest, asset: haybarn_unittest-osx-arm64.zip }
Expand All @@ -113,7 +115,7 @@ jobs:
run: uv python install 3.13

- name: Install the worker (from the lockfile)
run: uv sync --frozen --python 3.13
run: uv sync --frozen --python 3.13 --extra http

- name: Download haybarn-unittest
run: |
Expand All @@ -136,7 +138,9 @@ jobs:
UNITTEST="$PWD/$(find hb -name 'haybarn-unittest' -type f | head -1)"
chmod +x "$UNITTEST"
echo "HAYBARN_UNITTEST=$UNITTEST" >> "$GITHUB_ENV"
echo "VGI_QUANT_WORKER=uv run --python 3.13 $PWD/quant_worker.py" >> "$GITHUB_ENV"
echo "WORKER_CMD=uv run --python 3.13 $PWD/quant_worker.py" >> "$GITHUB_ENV"

- name: Run extension integration suite
- name: Run extension integration suite (${{ matrix.transport }})
run: ci/run-integration.sh
env:
TRANSPORT: ${{ matrix.transport }}
187 changes: 179 additions & 8 deletions ci/run-integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,170 @@
# VGI worker, using a prebuilt standalone `haybarn-unittest` and the signed
# community `vgi` extension — no C++ build from source. See ci/README.md.
#
# The SAME suite is exercised over three VGI transports, selected by $TRANSPORT.
# The vgi extension picks the transport from the LOCATION string the .test files
# ATTACH (`${VGI_QUANT_WORKER}`):
#
# subprocess : a bare stdio command (`uv run quant_worker.py`) — the extension
# spawns the worker per query and talks Arrow IPC over
# stdin/stdout. Default; current behavior.
# http : the worker is started out-of-band in `--http` mode on an auto
# port; LOCATION becomes `http://127.0.0.1:<port>`.
# unix : the worker is started out-of-band on an AF_UNIX socket;
# LOCATION becomes `unix:///path/to.sock`.
#
# Required environment:
# HAYBARN_UNITTEST path to the haybarn-unittest binary
# VGI_QUANT_WORKER worker LOCATION the .test files ATTACH (a stdio command
# such as `uv run quant_worker.py`, or an http:// URL)
# HAYBARN_UNITTEST path to the haybarn-unittest binary
# TRANSPORT subprocess | http | unix (default: subprocess)
# WORKER_CMD the stdio command that runs the worker. Used directly
# as the LOCATION for subprocess, and as the process to
# boot the server for http/unix. Defaults to
# `uv run --python 3.13 <repo>/quant_worker.py`.
# Optional:
# STAGE scratch dir for the preprocessed test tree (default: mktemp)
# STAGE scratch dir for the preprocessed test tree (default: mktemp)
set -euo pipefail

: "${HAYBARN_UNITTEST:?path to the haybarn-unittest binary}"
: "${VGI_QUANT_WORKER:?worker LOCATION (stdio command or http:// URL)}"

HERE="$(cd "$(dirname "$0")" && pwd)"
REPO="$(cd "$HERE/.." && pwd)"
STAGE="${STAGE:-$(mktemp -d)}"
TRANSPORT="${TRANSPORT:-subprocess}"
WORKER_CMD="${WORKER_CMD:-uv run --python 3.13 $REPO/quant_worker.py}"

echo "Staging preprocessed tests into $STAGE ..."
mkdir -p "$STAGE/test/sql"
for f in "$REPO"/test/sql/*.test; do
awk -f "$HERE/preprocess-require.awk" "$f" > "$STAGE/test/sql/$(basename "$f")"
done

# ---------------------------------------------------------------------------
# Per-transport: resolve VGI_QUANT_WORKER (the LOCATION) and, for the out-of-band
# transports, boot the worker server + arrange trap-cleanup.
# ---------------------------------------------------------------------------
SERVER_PID=""
SOCK=""
PORT_FILE=""

cleanup() {
local rc=$?
if [[ -n "$SERVER_PID" ]]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
if [[ -n "$SOCK" ]]; then rm -f "$SOCK"; fi
if [[ -n "$PORT_FILE" ]]; then rm -f "$PORT_FILE"; fi
# Always preserve the real exit code: an EXIT trap whose last command returns
# non-zero (e.g. a short-circuited `[[ -n "" ]] && …` when nothing needs
# cleaning) would otherwise become the script's exit status under `set -e` and
# fail an already-passing run.
return "$rc"
}
trap cleanup EXIT

case "$TRANSPORT" in
subprocess)
export VGI_QUANT_WORKER="$WORKER_CMD"
;;

http)
# The vgi extension's HTTP transport is implemented on top of DuckDB's
# httpfs extension, so an `http://` ATTACH binds with
# "Binder Error: VGI HTTP transport requires the httpfs extension."
# unless httpfs is loaded first. (The haybarn sqllogictest runner's default
# skip list swallows any error containing "HTTP", so without this the whole
# suite would silently SKIP rather than fail — a fake pass.) The .test files
# are transport-agnostic; inject a signed `INSTALL httpfs FROM core; LOAD
# httpfs;` right after the awk-injected `LOAD vgi;` in each staged file, so
# httpfs is present only when we actually run over HTTP.
echo "Injecting httpfs load into staged tests (HTTP transport needs it) ..."
for sf in "$STAGE"/test/sql/*.test; do
awk '
{ print }
/^LOAD[ \t]+vgi[ \t]*;[ \t]*$/ && !done {
print "";
print "statement ok";
print "INSTALL httpfs FROM core;";
print "";
print "statement ok";
print "LOAD httpfs;";
done = 1
}
' "$sf" > "$sf.tmp" && mv "$sf.tmp" "$sf"
done

# Boot the worker in HTTP mode on an auto-selected port. The worker writes
# the chosen port to --port-file atomically (tmp + rename), so we watch for
# the file to appear rather than parsing stdout. HTTP mode needs the `http`
# extra (waitress); WORKER_CMD must resolve it — CI installs it via
# `uv sync --extra http` and the PEP 723 header lists the extra too.
PORT_FILE="$(mktemp -u "${TMPDIR:-/tmp}/quant-port.XXXXXX")"
LOG_FILE="${TMPDIR:-/tmp}/quant-http-server.log"
echo "Starting HTTP worker: $WORKER_CMD --http --port 0 --port-file $PORT_FILE"
# shellcheck disable=SC2086
$WORKER_CMD --http --port 0 --port-file "$PORT_FILE" > "$LOG_FILE" 2>&1 &
SERVER_PID=$!

PORT=""
for _ in $(seq 1 240); do
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
echo "ERROR: HTTP worker exited before reporting a port. Log:" >&2
cat "$LOG_FILE" >&2
exit 1
fi
if [[ -s "$PORT_FILE" ]]; then
PORT="$(tr -d '[:space:]' < "$PORT_FILE")"
[[ -n "$PORT" ]] && break
fi
sleep 0.5
done
if [[ -z "$PORT" ]]; then
echo "ERROR: timed out waiting for HTTP worker port-file. Log:" >&2
cat "$LOG_FILE" >&2
exit 1
fi
echo "HTTP worker ready on port $PORT (pid $SERVER_PID)"
export VGI_QUANT_WORKER="http://127.0.0.1:$PORT"
;;

unix)
# Boot the worker bound to an AF_UNIX socket; poll for the socket file.
SOCK="${TMPDIR:-/tmp}/quant-$$.sock"
rm -f "$SOCK"
LOG_FILE="${TMPDIR:-/tmp}/quant-unix-server.log"
echo "Starting unix worker: $WORKER_CMD --unix $SOCK"
# shellcheck disable=SC2086
$WORKER_CMD --unix "$SOCK" > "$LOG_FILE" 2>&1 &
SERVER_PID=$!

READY=""
for _ in $(seq 1 240); do
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
echo "ERROR: unix worker exited before binding the socket. Log:" >&2
cat "$LOG_FILE" >&2
exit 1
fi
if [[ -S "$SOCK" ]]; then
READY=1
break
fi
sleep 0.5
done
if [[ -z "$READY" ]]; then
echo "ERROR: timed out waiting for unix worker socket. Log:" >&2
cat "$LOG_FILE" >&2
exit 1
fi
echo "unix worker ready on $SOCK (pid $SERVER_PID)"
export VGI_QUANT_WORKER="unix://$SOCK"
;;

*)
echo "ERROR: unknown TRANSPORT '$TRANSPORT' (want subprocess|http|unix)" >&2
exit 2
;;
esac

cd "$STAGE"

# Warm the extension cache once: vgi from the signed community channel. A miss
Expand All @@ -42,7 +185,35 @@ EOF
"$HAYBARN_UNITTEST" "test/_warm.test" >/dev/null 2>&1 || echo "::warning::extension warm step did not fully succeed"
rm -f "$STAGE/test/_warm.test"

# Run the whole suite in one invocation, streaming the runner's native
# Run the whole suite in one invocation, capturing the runner's native
# sqllogictest report. Any failed assertion exits non-zero and fails the job.
echo "Running suite (worker: $VGI_QUANT_WORKER) ..."
"$HAYBARN_UNITTEST" "test/sql/*"
#
# SILENT-SKIP GUARD: DuckDB's sqllogictest runner auto-SKIPS (exit 0!) any test
# whose error message contains "HTTP" or "Unable to connect". A broken http
# setup would therefore report "All tests were skipped" and go GREEN while
# testing nothing. Capture the output, echo it, and fail if the runner did not
# report a positive number of passing tests.
echo "Running suite (transport: $TRANSPORT, worker: $VGI_QUANT_WORKER) ..."
OUT_FILE="$(mktemp)"
set +e
"$HAYBARN_UNITTEST" "test/sql/*" 2>&1 | tee "$OUT_FILE"
RC="${PIPESTATUS[0]}"
set -e
if [[ "$RC" -ne 0 ]]; then
echo "ERROR: suite failed (exit $RC) over transport '$TRANSPORT'." >&2
rm -f "$OUT_FILE"
exit "$RC"
fi
# Guard against a fake pass where every test was skipped (e.g. broken http).
if grep -Eqi 'All tests were skipped|tests were skipped' "$OUT_FILE"; then
echo "ERROR: tests were SKIPPED over transport '$TRANSPORT' — refusing fake pass." >&2
rm -f "$OUT_FILE"
exit 1
fi
if ! grep -Eq 'All tests passed \([0-9]+ assertion' "$OUT_FILE"; then
echo "ERROR: runner did not report a passing summary over transport '$TRANSPORT'." >&2
rm -f "$OUT_FILE"
exit 1
fi
rm -f "$OUT_FILE"
echo "Suite GREEN over transport '$TRANSPORT'."
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ dependencies = [
]

[project.optional-dependencies]
# HTTP transport: the worker can serve over HTTP (`quant_worker.py --http`) in
# addition to stdio. That path needs vgi-python's `http` extra (waitress).
# CI's http transport leg installs this via `uv sync --extra http`.
http = [
"vgi-python[http]>=0.8.3",
]
dev = [
"pytest>=8",
"ruff",
Expand Down
2 changes: 1 addition & 1 deletion quant_worker.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# /// script
# requires-python = ">=3.13"
# dependencies = [
# "vgi-python>=0.8.3",
# "vgi-python[http]>=0.8.3",
# "QuantLib>=1.42",
# "pyarrow",
# ]
Expand Down
Loading
Loading