AtlatestRepositorysigil-http
sigil-http / tree / test / integrationrun-streaming-tests.sh
1
#!/usr/bin/env bash2
# Integration test for the T1 streaming-async fix.3
#4
# Proves that STREAMING responses (http-response/file and SSE) work through a5
# BARE `http-serve` (no with-async), and that the existing with-async consumer6
# pattern still works. See notes/tasks/sigil-http-t1-streaming-async.7
#8
# Why the ceremony: a loose `sigil <file>` run resolves library imports from9
# the global dep cache (the RELEASED package), not local source — so it would10
# silently test the OLD code. To exercise the working-tree sigil-http we build11
# a tiny `from-path` bundle that links THIS repo's sigil-http, then drive it12
# with curl. The bundle is generated in a temp dir (NOT committed) because a13
# nested package.sgl inside the repo confuses `sigil test`/`sigil build`14
# workspace discovery.15
#16
# Requires: curl, a working `sigil` toolchain, and network access on first run17
# (to fetch sigil-run/sigil-stdlib/sigil-json). Run from anywhere:18
# test/integration/run-streaming-tests.sh20
set -u22
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"23
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"24
SERVER_SRC="$SCRIPT_DIR/streaming-server-main.sgl"26
APP_DIR="$(mktemp -d /tmp/t1-streaming-app.XXXXXX)"27
BIN="$APP_DIR/build/dev/bin/t1-streaming-server"28
ASSET="$(mktemp /tmp/t1-asset.XXXXXX.bin)"29
DL="$(mktemp /tmp/t1-dl.XXXXXX.bin)"30
SSE="$(mktemp /tmp/t1-sse.XXXXXX.txt)"31
BARE_PORT=1820132
WRAPPED_PORT=1820233
GZIP_PORT=1820334
SRV_PID=""35
FAILED=037
cleanup() {38
[ -n "$SRV_PID" ] && kill "$SRV_PID" 2>/dev/null39
rm -rf "$APP_DIR" "$ASSET" "$DL" "$SSE"40
}41
trap cleanup EXIT43
pass() { echo "PASS: $1"; }44
fail() { echo "FAIL: $1"; FAILED=1; }46
# Scaffold the ephemeral from-path consumer bundle.47
scaffold_app() {48
mkdir -p "$APP_DIR/src/t1-streaming-server"49
cp "$SERVER_SRC" "$APP_DIR/src/t1-streaming-server/main.sgl"50
cat > "$APP_DIR/package.sgl" <<EOF51
(package52
name: "t1-streaming-server"53
version: "0.1.0"54
sigil: "^0.17"55
description: "Ephemeral integration bundle for the T1 streaming-async fix"56
entry: '(t1-streaming-server main)57
bundle-name: "t1-streaming-server"58
configs: (list59
(config name: 'dev output-dir: "build/dev" static?: #f debug?: #t optimize: 0 bundle?: #t))60
dependencies: (list61
(from-git url: "codeberg:sigil/sigil" package: "sigil-run" version: "^0.17")62
(from-git url: "codeberg:sigil/sigil" package: "sigil-stdlib" version: "^0.17")63
(from-git url: "codeberg:sigil/sigil-json" version: "^0.16")64
(from-path dir: "$REPO_ROOT" package: "sigil-http")))65
EOF66
}68
start_server() {69
local port="$1" mode="$2"70
"$BIN" "$port" "$ASSET" "$mode" >"$APP_DIR/server-$mode.log" 2>&1 &71
SRV_PID=$!72
local i73
for i in $(seq 1 60); do74
if curl -s --max-time 2 "http://127.0.0.1:$port/hello" >/dev/null 2>&1; then75
return 076
fi77
if ! kill -0 "$SRV_PID" 2>/dev/null; then78
echo " server ($mode) died on startup; log:"; cat "$APP_DIR/server-$mode.log"79
return 180
fi81
sleep 0.582
done83
echo " server ($mode) never became ready"84
return 185
}87
stop_server() {88
[ -n "$SRV_PID" ] && kill "$SRV_PID" 2>/dev/null89
wait "$SRV_PID" 2>/dev/null90
SRV_PID=""91
}93
run_mode() {94
local mode="$1" port="$2"95
echo ""96
echo "=== mode: $mode (port $port) ==="97
if ! start_server "$port" "$mode"; then98
fail "$mode: server startup"99
stop_server100
return101
fi103
# 1. Streaming file must come back byte-exact.104
if curl -s --max-time 30 "http://127.0.0.1:$port/file" -o "$DL" \105
&& cmp -s "$ASSET" "$DL"; then106
pass "$mode: http-response/file is byte-exact ($(stat -c%s "$DL") bytes)"107
else108
fail "$mode: http-response/file mismatch (got $(stat -c%s "$DL" 2>/dev/null) bytes)"109
fi111
# 2. SSE endpoint must stream all five events.112
curl -s --max-time 10 "http://127.0.0.1:$port/sse" -o "$SSE"113
local ticks114
ticks=$(grep -c '^event: tick' "$SSE" 2>/dev/null || echo 0)115
if [ "$ticks" = "5" ]; then116
pass "$mode: SSE streamed 5 events"117
else118
fail "$mode: SSE expected 5 events, got $ticks"119
fi121
# 3. No async-context crash should appear in the server log.122
if grep -q "not running in an async context" "$APP_DIR/server-$mode.log"; then123
fail "$mode: server logged an async-context error (streaming crashed)"124
else125
pass "$mode: no async-context error in server log"126
fi128
# 4. HEAD returns headers (incl. Content-Length) and ZERO body bytes.129
# curl -I masks the old HEAD-body bug (it closes after headers), so we130
# save the body to a file and assert it is empty. See review gotchas.131
local hb hh hbsize132
hb="$(mktemp)"; hh="$(mktemp)"133
curl -s --max-time 5 -X HEAD -D "$hh" -o "$hb" "http://127.0.0.1:$port/big-text"134
hbsize=$(stat -c%s "$hb" 2>/dev/null || echo -1)135
if [ "$hbsize" = "0" ] \136
&& head -1 "$hh" | grep -q " 200 " \137
&& grep -qi '^content-length:' "$hh"; then138
pass "$mode: HEAD /big-text -> headers only (0 body bytes, Content-Length present)"139
else140
fail "$mode: HEAD expected 0 body bytes + status 200 + Content-Length (got $hbsize body bytes)"141
fi142
rm -f "$hb" "$hh"144
# 5. gzip is OFF by default: even with Accept-Encoding: gzip, no encoding.145
local h5146
h5="$(curl -s --max-time 5 -H 'Accept-Encoding: gzip' -D - -o /dev/null "http://127.0.0.1:$port/big-text")"147
if echo "$h5" | grep -qi '^content-encoding:'; then148
fail "$mode: gzip disabled by default but response was content-encoded"149
else150
pass "$mode: gzip off by default (no Content-Encoding even when client accepts gzip)"151
fi153
stop_server154
}156
# gzip-enabled server (T11): opt-in gzip honoring Accept-Encoding.157
run_gzip() {158
local port="$1"159
echo ""160
echo "=== gzip mode (port $port) ==="161
if ! start_server "$port" "gzip"; then162
fail "gzip: server startup"163
stop_server164
return165
fi167
# Baseline: identity size (no Accept-Encoding).168
local raw_size gz_size hdr169
curl -s --max-time 5 -o "$DL" "http://127.0.0.1:$port/big-text"170
raw_size=$(stat -c%s "$DL" 2>/dev/null || echo 0)172
# With Accept-Encoding: gzip -> Content-Encoding: gzip and a smaller body.173
hdr="$(curl -s --max-time 5 -H 'Accept-Encoding: gzip' -D - -o "$DL" "http://127.0.0.1:$port/big-text")"174
gz_size=$(stat -c%s "$DL" 2>/dev/null || echo 0)175
if echo "$hdr" | grep -qi '^content-encoding: *gzip' \176
&& echo "$hdr" | grep -qi '^vary: *Accept-Encoding'; then177
pass "gzip: Accept-Encoding: gzip -> Content-Encoding: gzip + Vary"178
else179
fail "gzip: expected Content-Encoding: gzip and Vary: Accept-Encoding"180
fi181
if [ "$gz_size" -gt 0 ] && [ "$gz_size" -lt "$raw_size" ]; then182
pass "gzip: compressed body smaller than identity ($gz_size < $raw_size bytes)"183
else184
fail "gzip: compressed body not smaller ($gz_size vs $raw_size bytes)"185
fi187
# Without Accept-Encoding -> identity (no Content-Encoding).188
hdr="$(curl -s --max-time 5 -D - -o /dev/null "http://127.0.0.1:$port/big-text")"189
if echo "$hdr" | grep -qi '^content-encoding:'; then190
fail "gzip: response without Accept-Encoding must not be gzipped"191
else192
pass "gzip: no Accept-Encoding -> identity"193
fi195
# Incompressible/streamed file must never be gzipped, even when enabled.196
hdr="$(curl -s --max-time 15 -H 'Accept-Encoding: gzip' -D - -o "$DL" "http://127.0.0.1:$port/file")"197
if echo "$hdr" | grep -qi '^content-encoding:' || ! cmp -s "$ASSET" "$DL"; then198
fail "gzip: streaming file was altered/encoded (must stay byte-exact identity)"199
else200
pass "gzip: streaming file stays byte-exact identity (not gzipped)"201
fi203
stop_server204
}206
echo "Scaffolding + building integration bundle (from-path sigil-http)..."207
scaffold_app208
( cd "$APP_DIR" && sigil deps install >/dev/null 2>&1 && sigil build >"$APP_DIR/build.log" 2>&1 )209
if [ ! -x "$BIN" ]; then210
echo "FAIL: bundle build did not produce $BIN"211
tail -20 "$APP_DIR/build.log" 2>/dev/null212
exit 1213
fi215
# 2.1 MB random binary asset (mirrors the production-readiness repro).216
head -c 2100000 /dev/urandom > "$ASSET"218
run_mode "bare" "$BARE_PORT" # T1: streaming works WITHOUT with-async219
run_mode "wrapped" "$WRAPPED_PORT" # regression: existing with-async consumers220
run_gzip "$GZIP_PORT" # T11: opt-in gzip honoring Accept-Encoding222
echo ""223
if [ "$FAILED" -eq 0 ]; then224
echo "All streaming integration tests passed."225
exit 0226
else227
echo "Some streaming integration tests FAILED."228
exit 1229
fi