prefect server in zig
1#!/usr/bin/env bash
2# test-matrix - run integration tests across all backend combinations
3#
4# combinations tested:
5# 1. sqlite + memory broker
6# 2. sqlite + redis broker
7# 3. postgres + memory broker
8# 4. postgres + redis broker
9#
10# usage:
11# ./scripts/test-matrix # run full matrix (default)
12# ./scripts/test-matrix --quick # sqlite + memory only (fast local dev)
13# ./scripts/test-matrix --no-cleanup # keep containers running after
14
15set -euo pipefail
16
17SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
18PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
19
20RED='\033[0;31m'
21GREEN='\033[0;32m'
22YELLOW='\033[1;33m'
23BLUE='\033[0;34m'
24CYAN='\033[0;36m'
25NC='\033[0m'
26
27info() { echo -e "${GREEN}[INFO]${NC} $*"; }
28step() { echo -e "${BLUE}[STEP]${NC} $*"; }
29warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
30error() { echo -e "${RED}[ERROR]${NC} $*"; }
31matrix() { echo -e "${CYAN}[MATRIX]${NC} $*"; }
32
33SERVER_PID=""
34TEST_PORT=4202
35POSTGRES_PORT=5434
36REDIS_PORT=6380
37CLEANUP=true
38QUICK_MODE=false
39
40# parse args
41for arg in "$@"; do
42 case $arg in
43 --quick) QUICK_MODE=true ;;
44 --no-cleanup) CLEANUP=false ;;
45 esac
46done
47
48cleanup() {
49 if [[ -n "$SERVER_PID" ]] && kill -0 "$SERVER_PID" 2>/dev/null; then
50 kill "$SERVER_PID" 2>/dev/null || true
51 wait "$SERVER_PID" 2>/dev/null || true
52 fi
53 lsof -ti:$TEST_PORT 2>/dev/null | xargs -r kill 2>/dev/null || true
54 rm -f /tmp/prefect-matrix-*.db 2>/dev/null || true
55}
56trap cleanup EXIT
57
58wait_for_server() {
59 local timeout=${1:-30}
60 local count=0
61 while [[ $count -lt $timeout ]]; do
62 if curl -s "http://localhost:$TEST_PORT/api/health" >/dev/null 2>&1; then
63 return 0
64 fi
65 sleep 0.5
66 ((count++))
67 done
68 return 1
69}
70
71wait_for_postgres() {
72 local timeout=${1:-30}
73 local count=0
74 while [[ $count -lt $timeout ]]; do
75 if docker exec prefect-test-matrix-postgres pg_isready -U prefect >/dev/null 2>&1; then
76 return 0
77 fi
78 sleep 1
79 ((count++))
80 done
81 return 1
82}
83
84start_services() {
85 step "Starting docker services (postgres + redis)..."
86
87 # stop any existing test containers
88 docker rm -f prefect-test-matrix-postgres prefect-test-matrix-redis 2>/dev/null || true
89
90 # start postgres
91 docker run -d \
92 --name prefect-test-matrix-postgres \
93 -e POSTGRES_USER=prefect \
94 -e POSTGRES_PASSWORD=prefect \
95 -e POSTGRES_DB=prefect_test \
96 -p "${POSTGRES_PORT}:5432" \
97 postgres:16-alpine >/dev/null 2>&1
98
99 # start redis
100 docker run -d \
101 --name prefect-test-matrix-redis \
102 -p "${REDIS_PORT}:6379" \
103 redis:7-alpine >/dev/null 2>&1
104
105 # wait for postgres
106 if ! wait_for_postgres 30; then
107 error "PostgreSQL failed to start"
108 return 1
109 fi
110
111 # wait for redis
112 local count=0
113 while [[ $count -lt 30 ]]; do
114 if redis-cli -p "$REDIS_PORT" ping >/dev/null 2>&1; then
115 break
116 fi
117 sleep 0.5
118 ((count++))
119 done
120
121 info "Services ready"
122}
123
124stop_services() {
125 if [[ "$CLEANUP" == "true" ]]; then
126 step "Stopping docker services..."
127 docker rm -f prefect-test-matrix-postgres prefect-test-matrix-redis 2>/dev/null || true
128 else
129 info "Leaving containers running (--no-cleanup)"
130 fi
131}
132
133# run a single test configuration
134run_test() {
135 local db_backend="$1"
136 local broker_backend="$2"
137 local label="${db_backend}+${broker_backend}"
138
139 matrix "Testing: $label"
140
141 # prepare environment
142 local db_path="/tmp/prefect-matrix-${db_backend}-${broker_backend}-$$.db"
143 rm -f "$db_path"
144
145 export PREFECT_SERVER_PORT=$TEST_PORT
146 export PREFECT_SERVER_LOGGING_LEVEL=WARNING
147 export PREFECT_BROKER_BACKEND="$broker_backend"
148
149 if [[ "$db_backend" == "sqlite" ]]; then
150 export PREFECT_DATABASE_BACKEND=sqlite
151 export PREFECT_DATABASE_PATH="$db_path"
152 unset PREFECT_DATABASE_URL 2>/dev/null || true
153 else
154 export PREFECT_DATABASE_BACKEND=postgres
155 export PREFECT_DATABASE_URL="postgresql://prefect:prefect@localhost:${POSTGRES_PORT}/prefect_test"
156 # truncate all tables between tests (safer than DROP SCHEMA which breaks connections)
157 docker exec prefect-test-matrix-postgres psql -U prefect -d prefect_test -c "
158 DO \$\$ DECLARE r RECORD;
159 BEGIN
160 FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
161 EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE';
162 END LOOP;
163 END \$\$;
164 " >/dev/null 2>&1 || true
165 fi
166
167 if [[ "$broker_backend" == "redis" ]]; then
168 export PREFECT_REDIS_MESSAGING_HOST=localhost
169 export PREFECT_REDIS_MESSAGING_PORT=$REDIS_PORT
170 # clear redis between tests
171 redis-cli -p "$REDIS_PORT" FLUSHALL >/dev/null 2>&1 || true
172 fi
173
174 # start server
175 "$PROJECT_DIR/zig-out/bin/prefect-server" &
176 SERVER_PID=$!
177
178 if ! wait_for_server 30; then
179 error "[$label] Server failed to start"
180 return 1
181 fi
182
183 # run test suite
184 step "[$label] Running API tests..."
185 if PREFECT_API_URL="http://localhost:$TEST_PORT/api" "$PROJECT_DIR/scripts/test-api-sequence" >/dev/null 2>&1; then
186 info "[$label] PASSED"
187 local result=0
188 else
189 error "[$label] FAILED"
190 local result=1
191 fi
192
193 # stop server
194 kill "$SERVER_PID" 2>/dev/null || true
195 wait "$SERVER_PID" 2>/dev/null || true
196 SERVER_PID=""
197
198 # ensure port is released before next test
199 local port_wait=0
200 while [[ $port_wait -lt 20 ]] && lsof -ti:$TEST_PORT >/dev/null 2>&1; do
201 lsof -ti:$TEST_PORT 2>/dev/null | xargs -r kill 2>/dev/null || true
202 sleep 0.25
203 ((port_wait++))
204 done
205
206 rm -f "$db_path"
207 return $result
208}
209
210main() {
211 echo ""
212 matrix "============================================"
213 matrix " prefect-server test matrix"
214 matrix "============================================"
215 echo ""
216
217 # build first
218 step "Building..."
219 (cd "$PROJECT_DIR" && zig build) || { error "Build failed"; exit 1; }
220
221 # run unit tests
222 step "Running unit tests..."
223 (cd "$PROJECT_DIR" && zig build test --summary all 2>&1) || { error "Unit tests failed"; exit 1; }
224 echo ""
225
226 if [[ "$QUICK_MODE" == "true" ]]; then
227 matrix "Quick mode: sqlite + memory only"
228 echo ""
229
230 run_test sqlite memory || exit 1
231
232 echo ""
233 matrix "============================================"
234 matrix " QUICK TEST PASSED (1/4 combinations)"
235 matrix "============================================"
236 echo ""
237 warn "Run without --quick to test full matrix"
238 exit 0
239 fi
240
241 # start services for full matrix
242 start_services || exit 1
243 echo ""
244
245 # track results
246 local passed=0
247 local failed=0
248 declare -a results=()
249
250 # test all 4 combinations
251 for db in sqlite postgres; do
252 for broker in memory redis; do
253 step "Starting test: ${db}+${broker}"
254 if run_test "$db" "$broker"; then
255 results+=("${GREEN}PASS${NC} ${db}+${broker}")
256 ((passed++)) || true
257 else
258 results+=("${RED}FAIL${NC} ${db}+${broker}")
259 ((failed++)) || true
260 fi
261 step "Completed test: ${db}+${broker}"
262 echo ""
263 done
264 done
265
266 # cleanup
267 stop_services
268
269 # summary
270 echo ""
271 matrix "============================================"
272 matrix " TEST MATRIX RESULTS"
273 matrix "============================================"
274 for r in "${results[@]}"; do
275 echo -e " $r"
276 done
277 echo ""
278 matrix "Passed: $passed / 4"
279
280 if [[ $failed -gt 0 ]]; then
281 error "Matrix test FAILED"
282 exit 1
283 fi
284
285 echo ""
286 matrix "============================================"
287 matrix " ALL 4 COMBINATIONS PASSED"
288 matrix "============================================"
289 echo ""
290}
291
292main