-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
402 lines (374 loc) · 19.7 KB
/
Copy pathMakefile
File metadata and controls
402 lines (374 loc) · 19.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# VGI TypeScript Makefile
# Build and test targets for vgi-typescript.
# Tests are independent targets — use `make -j8 test` for parallel execution.
# Recipes use bash features (`read -t`, functions, `${VAR:+...}`). On Ubuntu the
# default recipe shell is /bin/sh (dash), which lacks `read -t` — force bash.
SHELL := /bin/bash
.PHONY: build build\:types build\:js install clean test test-subprocess test-http test-all test-client
# --- Configuration (all overridable) ---
VGI_DIR ?= /Users/rusty/Development/vgi
VGI_PYTHON_DIR ?= /Users/rusty/Development/vgi-python
TEST_TIMEOUT ?= 120
WORKER ?= $(CURDIR)/bin/vgi-example-worker
HTTP_WORKER := $(CURDIR)/bin/vgi-example-http-worker
VERSIONED_WORKER := $(CURDIR)/bin/vgi-example-versioned-worker
VERSIONED_HTTP := $(CURDIR)/bin/vgi-example-versioned-http-worker
VERSIONED_TABLES_WORKER := $(CURDIR)/bin/vgi-example-versioned-tables-worker
VERSIONED_TABLES_HTTP := $(CURDIR)/bin/vgi-example-versioned-tables-http-worker
ATTACH_OPTIONS_WORKER := $(CURDIR)/bin/vgi-example-attach-options-worker
ATTACH_OPTIONS_HTTP := $(CURDIR)/bin/vgi-example-attach-options-http-worker
TEST_DIR := $(VGI_DIR)/test/sql
RELEASE_BIN := $(VGI_DIR)/build/release/test/unittest
# --- Build targets ---
build:
bun run build
build\:types:
bun run build:types
build\:js:
bun run build:js
install:
bun install
clean:
rm -rf dist/
# --- Test targets ---
#
# These are the LOCAL developer targets. They drive vgi/scripts/run_tests.py,
# which has no "skipped" category — a test that skips (a failed `require`, an
# unset `require-env`) exits 0 and is reported as a pass. That is fine while
# iterating, but it means a green `make test-http` is not evidence the suite ran.
# CI uses ci/run-integration.sh instead, which counts and allowlists every skip
# and enforces a floor on executed tests; run that when you need certainty.
#
# Use the unittest harness's own -j 8 parallelism (see ~/Development/vgi
# Makefile's test_subprocess target). One unittest invocation runs every
# matching test in parallel, captures output to a log file, and prints a
# pass/fail summary plus a list of failed tests at the end.
#
# Patterns:
# "test/sql/*" — every test file
# "~test/sql/integration/writable/*" — exclude the writable fixture
# tree (we don't port that worker)
TEST_LOG := /tmp/vgi-typescript-test.log
# Excluded patterns (use ~ prefix for unittest's filter exclusion syntax):
# writable/ — writable fixture worker not ported
# schema_reconcile — writable-style fixture, also skipped
# constant_columns_types — arrow-js doesn't support TIMESTAMP_NS
#
# zero_count_bypass is NO LONGER excluded (removed 2026-08-21). The old reason
# ("broken upstream; its LIKE pattern matches set_kind=table AND
# set_kind=table_function ambiguously") was a correct diagnosis of a bug that has
# since been fixed centrally: the test now anchors on the field separator,
# LIKE '%set_kind=table,%'. Verified against THIS worker on all three lanes —
# subprocess, launch:, and http — 23 assertions pass on each.
#
# HTTP-only exclusions: NONE. This block listed three, and every reason was
# wrong — verified 2026-08-21 by running each against this worker over HTTP:
# filter_echo_partitioned "asserts COUNT(DISTINCT worker_pid) > 1" — stale;
# the test moved to the transport-neutral conn= form.
# partitioned_sequence inherited that misdiagnosis.
# order_preservation_modes "batch_received logs don't stream over HTTP (0 log
# rows)" — measured 199 rows. The scan really was
# collapsing to ONE connection, because this SDK's
# HTTP turn loop packed a whole producer stream into
# a single response (fixed in vgi-rpc-typescript).
# The exclusions were hiding a real bug in this SDK for months. Before adding
# one here, measure the claim.
TEST_PATTERNS := "test/sql/*" \
"~test/sql/integration/writable/*" \
"~test/sql/integration/schema_reconcile.test" \
"~test/sql/integration/table/constant_columns_types.test"
# Launcher transport excludes vgi_worker_pool.test, which asserts subprocess-pool
# semantics: `launch:` workers are pooled by the AF_UNIX socket, not by DuckDB's
# per-process subprocess pool, so vgi_worker_pool() legitimately returns no rows
# there (documented in vgi's CLAUDE.md). Mirrors vgi's own test_launcher target.
#
# Three more tests used to be excluded here and were removed 2026-08-21, having
# never been excluded on this repo's own CI `launch` lane (ci/run-integration.sh),
# which runs the same transport — the Makefile lane was the stale copy. Each was
# re-verified over launch: against this worker before removal:
# filter_echo_partitioned 36 assertions pass. The stale reason was the old
# COUNT(DISTINCT worker_pid) > 1 form; the test now
# counts transport-neutral conn= ids.
# versioned_tables_impl 35 assertions pass (with VGI_VERSIONED_TABLES_WORKER
# set, as this lane sets it below).
# order_preservation_modes 16 assertions pass. The FIXED_ORDER → 1-distinct-conn
# claim is a real HTTP limitation, but it does not
# apply to launch:, which collapses to one connection
# like plain subprocess.
LAUNCHER_TEST_PATTERNS := $(TEST_PATTERNS) \
"~test/sql/vgi_worker_pool.test"
# Idle-timeout for launcher-spawned workers. The C++ launcher passes
# --idle-timeout 300 by default; src/worker.ts honours
# VGI_WORKER_IDLE_TIMEOUT as an override.
#
# Under -j8 every parallel unittest process shares ONE warm Bun worker
# (the launcher pools by argv/cwd/env). With a 5 s idle timeout that shared
# worker would idle-exit and respawn mid-suite under bursty load; a query
# arriving during the respawn window stalled into the C++ side's 30 s
# catalog-RPC timeout and surfaced as flaky "VGI catalog operation timed
# out" failures (filter_echo, column_statistics, …). 120 s keeps the worker
# warm for the whole run; it still exits well before the next suite.
LAUNCHER_IDLE_TIMEOUT ?= 120
HTTP_TEST_PATTERNS := "test/sql/integration/*" \
"~test/sql/integration/writable/*" \
"~test/sql/integration/schema_reconcile.test" \
"~test/sql/integration/table/constant_columns_types.test" \
$(EXTRA_HTTP_EXCLUDES)
# Parallelism for the per-test runner. Default 8 locally; CI sets JOBS=1 to
# avoid an `INSTALL vgi` race when fresh unittest processes autoinstall the
# community extension into a cold cache simultaneously.
JOBS ?= 8
# Extra `~`-exclusions appended to HTTP_TEST_PATTERNS (set in CI to skip tests
# that are version-skewed against the prebuilt community extension).
EXTRA_HTTP_EXCLUDES ?=
# Default test target: launcher (`launch:`) transport.
#
# The vgi extension's C++ AF_UNIX launcher spawns each worker once per
# (argv, cwd, VGI_RPC_*-env) tuple and reuses it across every parallel
# unittest invocation that hashes to the same tuple. Running 8 jobs no
# longer means 8× Bun cold-starts — measured ~5× wall-clock improvement
# in the upstream extension's own suite.
#
# Worker support: src/worker.ts parses `--unix PATH` / `--idle-timeout SEC`
# (added by the launcher) and dispatches to vgi-rpc's serveUnix() instead
# of the stdio VgiRpcServer.
#
# Use `make test-subprocess` if you specifically need per-process subprocess
# semantics (e.g. debugging a worker startup issue).
# Coverage gates — see the note in vgi/Makefile (VGI_EXPECTED_SKIPS). A lane
# that stops running tests reports green, so a floor on executed tests and an
# allow-list of expected skip reasons are what keep a silently-shrinking lane
# from passing.
#
# VGI_TEST_BRANCH_DIR is a scratch dir the multi_branch_* fixtures write into.
# This lane never exported it, so seven tests skipped for an UNDECLARED reason
# and the gate failed the run — on main, not just on a branch — while the
# executed count sat at 288 under a floor of 290. The worker runs them fine
# (measured 12 passed / 1 skipped, the skip being the iceberg case that wants
# VGI_TEST_ICEBERG), so wire the var rather than allow-list the skip: seven
# tests recovered instead of seven tests excused. vgi-java does the same.
#
# TypeScript runs 294 today.
VGI_TEST_BRANCH_DIR ?= $(shell python3 -c 'import tempfile;print(tempfile.gettempdir())')
TS_MIN_EXECUTED ?= 290
COVERAGE_GATE := --min-executed $(TS_MIN_EXECUTED) \
--allow-skip 'require spatial' \
--allow-skip 'require-env VGI_DOCKER_IMAGE' \
--allow-skip 'require-env VGI_DOCKER_TCP_IMAGE' \
--allow-skip 'require-env VGI_GITHUB_NETWORK_TESTS' \
--allow-skip 'require-env VGI_TEST_ICEBERG' \
--allow-skip 'require-env VGI_TEST_COMPANION_TARGET' \
--allow-skip 'require-env VGI_TEST_BEARER_TOKEN' \
--allow-skip 'require-env VGI_TEST_DEDICATED_WORKER' \
--allow-skip 'require-env VGI_HTTP_TRANSPORT' \
--allow-skip 'require-env VGI_HTTP_DISABLE_ZSTD' \
--allow-skip 'require-env VGI_HTTP_NO_COMPRESSION' \
--allow-skip 'require-env VGI_VERSIONED_HTTP_WORKER' \
--allow-skip 'require-env VGI_VERSIONED_TABLES_HTTP_WORKER' \
--allow-skip 'require-env VGI_WORKER_SUPPORTS_DYNAMIC_CODE' \
--allow-skip 'require-env VGI_SIMPLE_WRITABLE_WORKER' \
--allow-skip 'require-env VGI_SCHEMA_RECONCILE_DB' \
--allow-skip 'require-env VGI_RULES_WORKER' \
--allow-skip 'require-env VGI_ATTACH_OPTIONS_REQUIRED_WORKER' \
--allow-skip 'require-env VGI_BAD_ENUM_WORKER' \
--allow-skip 'require-env VGI_BAD_PROTOCOL_WORKER'
test:
@cd $(VGI_DIR) && \
export VGI_TEST_WORKER="launch:$(WORKER)"; \
export VGI_VERSIONED_WORKER="launch:$(VERSIONED_WORKER)"; \
export VGI_VERSIONED_TABLES_WORKER="launch:$(VERSIONED_TABLES_WORKER)"; \
export VGI_ATTACH_OPTIONS_WORKER="launch:$(ATTACH_OPTIONS_WORKER)"; \
export VGI_REQUIRE_LAUNCHER_TRANSPORT=1; \
export VGI_WORKER_IDLE_TIMEOUT=$(LAUNCHER_IDLE_TIMEOUT); \
export VGI_TEST_BRANCH_DIR="$(VGI_TEST_BRANCH_DIR)"; \
python3 scripts/run_tests.py -j $(JOBS) $(COVERAGE_GATE) $(LAUNCHER_TEST_PATTERNS) > $(TEST_LOG) 2>&1; \
rc=$$?; \
tail -n 20 $(TEST_LOG); \
echo ""; \
if [ $$rc -eq 0 ]; then \
echo "All tests passed (launcher). Log: $(TEST_LOG)"; \
else \
echo "Some tests failed (rc=$$rc, launcher). Full log: $(TEST_LOG)"; \
fi; \
exit $$rc
# Plain subprocess transport — one worker per DuckDB process, pooled.
# Same suite as `test` plus vgi_worker_pool.test, the one file the launcher path
# can't satisfy (it asserts per-process subprocess-pool semantics; launch:
# workers are pooled by the AF_UNIX socket, so vgi_worker_pool() has no rows).
test-subprocess:
@cd $(VGI_DIR) && \
export VGI_TEST_WORKER="$(WORKER)"; \
export VGI_VERSIONED_WORKER="$(VERSIONED_WORKER)"; \
export VGI_VERSIONED_TABLES_WORKER="$(VERSIONED_TABLES_WORKER)"; \
export VGI_ATTACH_OPTIONS_WORKER="$(ATTACH_OPTIONS_WORKER)"; \
python3 scripts/run_tests.py -j $(JOBS) $(TEST_PATTERNS) > $(TEST_LOG) 2>&1; \
rc=$$?; \
tail -n 20 $(TEST_LOG); \
echo ""; \
if [ $$rc -eq 0 ]; then \
echo "All tests passed (subprocess). Log: $(TEST_LOG)"; \
else \
echo "Some tests failed (rc=$$rc, subprocess). Full log: $(TEST_LOG)"; \
fi; \
exit $$rc
# HTTP transport: same pattern, but the workers need to be running at known
# URLs. The HTTP example workers each write a PORT line on stdout when they
# start; we read it through a FIFO and export the URLs before invoking unittest.
#
# Each FIFO is opened read-write on a dedicated fd (exec N<>fifo) *before* the
# worker is launched. A plain `read < fifo` would make the worker's open() for
# write block until the reader attaches; on macOS, launching the four heavy Bun
# workers this way deadlocks (the second worker never reaches its PORT write).
# Holding the read end open keeps the worker's write-open non-blocking, while
# preserving pipe semantics (a regular-file capture is block-buffered on Linux,
# so the PORT line never flushes there). Works on both macOS and Linux/CI.
test-http:
@cd $(VGI_DIR) && \
port_fifo=$$(mktemp -u); mkfifo "$$port_fifo"; exec 3<>"$$port_fifo"; \
$(HTTP_WORKER) > "$$port_fifo" 2>/tmp/vgi-http-worker.err & http_pid=$$!; \
vport_fifo=$$(mktemp -u); mkfifo "$$vport_fifo"; exec 4<>"$$vport_fifo"; \
$(VERSIONED_HTTP) > "$$vport_fifo" 2>/dev/null & vhttp_pid=$$!; \
aport_fifo=$$(mktemp -u); mkfifo "$$aport_fifo"; exec 5<>"$$aport_fifo"; \
$(ATTACH_OPTIONS_HTTP) > "$$aport_fifo" 2>/dev/null & ahttp_pid=$$!; \
tport_fifo=$$(mktemp -u); mkfifo "$$tport_fifo"; exec 6<>"$$tport_fifo"; \
$(VERSIONED_TABLES_HTTP) > "$$tport_fifo" 2>/dev/null & thttp_pid=$$!; \
cleanup() { \
kill $$http_pid $$vhttp_pid $$ahttp_pid $$thttp_pid 2>/dev/null; \
wait $$http_pid $$vhttp_pid $$ahttp_pid $$thttp_pid 2>/dev/null; \
exec 3>&- 4>&- 5>&- 6>&- 2>/dev/null; \
rm -f "$$port_fifo" "$$vport_fifo" "$$aport_fifo" "$$tport_fifo"; \
}; \
trap cleanup EXIT; \
read -t 60 port_line <&3 || { echo "ERROR: HTTP worker timeout"; echo "--- worker stderr ---"; cat /tmp/vgi-http-worker.err 2>/dev/null; exit 1; }; \
read -t 60 vport_line <&4 || { echo "ERROR: versioned HTTP worker timeout"; exit 1; }; \
read -t 60 aport_line <&5 || { echo "ERROR: attach-options HTTP worker timeout"; exit 1; }; \
read -t 60 tport_line <&6 || { echo "ERROR: versioned-tables HTTP worker timeout"; exit 1; }; \
export VGI_TEST_WORKER="http://localhost:$${port_line#PORT:}"; \
export VGI_VERSIONED_HTTP_WORKER="http://localhost:$${vport_line#PORT:}"; \
export VGI_ATTACH_OPTIONS_WORKER="http://localhost:$${aport_line#PORT:}"; \
export VGI_VERSIONED_TABLES_HTTP_WORKER="http://localhost:$${tport_line#PORT:}"; \
python3 scripts/run_tests.py -j $(JOBS) $(HTTP_TEST_PATTERNS) > $(TEST_LOG) 2>&1; \
rc=$$?; \
tail -n 20 $(TEST_LOG); \
echo ""; \
if [ $$rc -eq 0 ]; then \
echo "All HTTP tests passed. Log: $(TEST_LOG)"; \
else \
echo "Some HTTP tests failed (rc=$$rc). Full log: $(TEST_LOG)"; \
fi; \
exit $$rc
test-all: test test-http
# Run the Arrow facade parity tests against both backends back-to-back.
# Both invocations must pass — same suite, different `#arrow-impl` resolution.
test-facade-parity:
@echo "=== arrow-js (default condition) ==="
bun test src/arrow/__tests__/parity.test.ts
@echo ""
@echo "=== flechette (--conditions=worker) ==="
bun --conditions=worker test src/arrow/__tests__/parity.test.ts
# Per-test entry point — useful when iterating on a single failure.
# `make test/integration/filter_pushdown/integers` runs just that one test
# with the verbose -s flag so the failure detail prints inline. Defaults
# to the launcher transport; use `make test-subprocess/...` to force the
# subprocess path.
test/%:
@test_file="$(TEST_DIR)/$*.test"; \
if [ ! -f "$$test_file" ]; then \
echo "ERROR: test file not found: $$test_file"; \
exit 1; \
fi; \
export VGI_TEST_WORKER="launch:$(WORKER)"; \
export VGI_VERSIONED_WORKER="launch:$(VERSIONED_WORKER)"; \
export VGI_VERSIONED_TABLES_WORKER="launch:$(VERSIONED_TABLES_WORKER)"; \
export VGI_ATTACH_OPTIONS_WORKER="launch:$(ATTACH_OPTIONS_WORKER)"; \
export VGI_REQUIRE_LAUNCHER_TRANSPORT=1; \
export VGI_WORKER_IDLE_TIMEOUT=$(LAUNCHER_IDLE_TIMEOUT); \
cd $(VGI_DIR) && ./build/release/test/unittest -s "$$test_file"
# Subprocess single-test entry point — same shape as `test/%` but without
# the `launch:` prefix. Useful when isolating a hang at the worker spawn
# layer rather than the launcher cache layer.
test-subprocess/%:
@test_file="$(TEST_DIR)/$*.test"; \
if [ ! -f "$$test_file" ]; then \
echo "ERROR: test file not found: $$test_file"; \
exit 1; \
fi; \
export VGI_TEST_WORKER="$(WORKER)"; \
export VGI_VERSIONED_WORKER="$(VERSIONED_WORKER)"; \
export VGI_VERSIONED_TABLES_WORKER="$(VERSIONED_TABLES_WORKER)"; \
export VGI_ATTACH_OPTIONS_WORKER="$(ATTACH_OPTIONS_WORKER)"; \
cd $(VGI_DIR) && ./build/release/test/unittest -s "$$test_file"
# test-http/% — single-test HTTP entry point. Spawns the HTTP worker
# triplet, points VGI_TEST_WORKER at it, runs that one test verbosely.
test-http/%:
@test_file="$(TEST_DIR)/$*.test"; \
if [ ! -f "$$test_file" ]; then \
echo "ERROR: test file not found: $$test_file"; \
exit 1; \
fi; \
port_fifo=$$(mktemp -u); mkfifo "$$port_fifo"; exec 3<>"$$port_fifo"; \
$(HTTP_WORKER) > "$$port_fifo" 2>/tmp/vgi-http-worker.err & http_pid=$$!; \
vport_fifo=$$(mktemp -u); mkfifo "$$vport_fifo"; exec 4<>"$$vport_fifo"; \
$(VERSIONED_HTTP) > "$$vport_fifo" 2>/dev/null & vhttp_pid=$$!; \
aport_fifo=$$(mktemp -u); mkfifo "$$aport_fifo"; exec 5<>"$$aport_fifo"; \
$(ATTACH_OPTIONS_HTTP) > "$$aport_fifo" 2>/dev/null & ahttp_pid=$$!; \
tport_fifo=$$(mktemp -u); mkfifo "$$tport_fifo"; exec 6<>"$$tport_fifo"; \
$(VERSIONED_TABLES_HTTP) > "$$tport_fifo" 2>/dev/null & thttp_pid=$$!; \
cleanup() { \
kill $$http_pid $$vhttp_pid $$ahttp_pid $$thttp_pid 2>/dev/null; \
wait $$http_pid $$vhttp_pid $$ahttp_pid $$thttp_pid 2>/dev/null; \
exec 3>&- 4>&- 5>&- 6>&- 2>/dev/null; \
rm -f "$$port_fifo" "$$vport_fifo" "$$aport_fifo" "$$tport_fifo"; \
}; \
trap cleanup EXIT; \
read -t 60 port_line <&3 || { echo "ERROR: HTTP worker timeout"; echo "--- worker stderr ---"; cat /tmp/vgi-http-worker.err 2>/dev/null; exit 1; }; \
read -t 60 vport_line <&4 || { echo "ERROR: versioned HTTP worker timeout"; exit 1; }; \
read -t 60 aport_line <&5 || { echo "ERROR: attach-options HTTP worker timeout"; exit 1; }; \
read -t 60 tport_line <&6 || { echo "ERROR: versioned-tables HTTP worker timeout"; exit 1; }; \
export VGI_TEST_WORKER="http://localhost:$${port_line#PORT:}"; \
export VGI_VERSIONED_HTTP_WORKER="http://localhost:$${vport_line#PORT:}"; \
export VGI_ATTACH_OPTIONS_WORKER="http://localhost:$${aport_line#PORT:}"; \
export VGI_VERSIONED_TABLES_HTTP_WORKER="http://localhost:$${tport_line#PORT:}"; \
cd $(VGI_DIR) && ./build/release/test/unittest -s "$$test_file"
# VgiClient end-to-end tests against vgi-python's HTTP workers.
# Spawns the normal + versioned HTTP workers, each with --port-file
# pointing at a temp file the worker writes atomically when listening.
# The Makefile polls those files (no FIFOs, no stdout parsing) and
# exports VGI_PYTHON_HTTP_WORKER + VGI_PYTHON_VERSIONED_HTTP_WORKER
# before running bun:test. Always cleans up on exit. Requires `uv` on
# PATH and vgi-python at $VGI_PYTHON_DIR.
test-client:
@tmpdir=$$(mktemp -d); \
normal_file="$$tmpdir/normal.port"; \
vers_file="$$tmpdir/versioned.port"; \
ao_file="$$tmpdir/attach-options.port"; \
( cd "$(VGI_PYTHON_DIR)" && uv run vgi-fixture-http --port 0 --port-file "$$normal_file" ) >/dev/null 2>&1 & \
py_pid=$$!; \
( cd "$(VGI_PYTHON_DIR)" && uv run vgi-fixture-versioned-worker --http --port 0 --port-file "$$vers_file" ) >/dev/null 2>&1 & \
vers_pid=$$!; \
( cd "$(VGI_PYTHON_DIR)" && uv run vgi-fixture-attach-options-worker --http --port 0 --port-file "$$ao_file" ) >/dev/null 2>&1 & \
ao_pid=$$!; \
cleanup() { \
kill $$py_pid $$vers_pid $$ao_pid 2>/dev/null; \
wait $$py_pid $$vers_pid $$ao_pid 2>/dev/null; \
rm -rf "$$tmpdir"; \
}; \
trap cleanup EXIT; \
for i in $$(seq 1 300); do \
[ -s "$$normal_file" ] && [ -s "$$vers_file" ] && [ -s "$$ao_file" ] && break; \
sleep 0.1; \
done; \
if [ ! -s "$$normal_file" ] || [ ! -s "$$vers_file" ] || [ ! -s "$$ao_file" ]; then \
echo "ERROR: Python workers did not publish ports within 30s"; \
cleanup; exit 1; \
fi; \
export VGI_PYTHON_HTTP_WORKER="http://127.0.0.1:$$(cat $$normal_file)"; \
export VGI_PYTHON_VERSIONED_HTTP_WORKER="http://127.0.0.1:$$(cat $$vers_file)"; \
export VGI_PYTHON_ATTACH_OPTIONS_HTTP_WORKER="http://127.0.0.1:$$(cat $$ao_file)"; \
echo "Python HTTP worker: $$VGI_PYTHON_HTTP_WORKER"; \
echo "Python versioned worker: $$VGI_PYTHON_VERSIONED_HTTP_WORKER"; \
echo "Python attach-options worker: $$VGI_PYTHON_ATTACH_OPTIONS_HTTP_WORKER"; \
bun test src/; \
rc=$$?; \
cleanup; \
exit $$rc