-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
359 lines (332 loc) · 17.4 KB
/
Copy pathMakefile
File metadata and controls
359 lines (332 loc) · 17.4 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
# 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 ---
#
# 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 — broken upstream, fails against Python worker too;
# the test's LIKE pattern matches set_kind=table
# AND set_kind=table_function ambiguously
#
# HTTP-only exclusions (subprocess runs them fine):
# filter_echo_partitioned — asserts COUNT(DISTINCT worker_pid) > 1; an
# HTTP worker is one OS process, so worker_pid
# collapses to a single value (test's own
# docstring spells this out). Python HTTP
# auto-skips this via sqllogictest's default
# ignore_error_messages={"HTTP", ...}; TS gets
# farther so we exclude explicitly.
# partitioned_sequence — same root cause: asserts >=2 distinct conn=
# in batch_received logs under threads=4. The
# C++ HTTP transport's parallel-scan connection
# accounting is what's tested, not the worker.
# order_preservation_modes — its FIXED_ORDER -> distinct-conn assertion
# relies on VGI batch_received logs, which don't
# stream over HTTP (0 log rows). Meaningful only
# on subprocess transport; already excluded from
# the launcher patterns for the same reason.
TEST_PATTERNS := "test/sql/*" \
"~test/sql/integration/writable/*" \
"~test/sql/integration/schema_reconcile.test" \
"~test/sql/integration/table/constant_columns_types.test" \
"~test/sql/integration/catalog/zero_count_bypass.test"
# Launcher transport excludes a few extra tests that assert subprocess-pool
# semantics — `launch:` workers are pooled by the AF_UNIX socket, not by
# DuckDB's per-process subprocess pool, so these intentionally don't apply.
# Mirrors vgi's own test_launcher target.
#
# order_preservation_modes is also excluded: its FIXED_ORDER → 1-distinct-conn
# assertion is meaningful only on the subprocess transport. Under launcher
# (and HTTP), DuckDB allocates one FunctionConnection per worker thread
# eagerly during InitLocalState, so all N pre-allocated conns appear in the
# per-batch logs even when the planner has serialised execution onto a
# single producer. Reproduces with the upstream Python worker too.
LAUNCHER_TEST_PATTERNS := $(TEST_PATTERNS) \
"~test/sql/vgi_worker_pool.test" \
"~test/sql/integration/table/filter_echo_partitioned.test" \
"~test/sql/integration/attach/versioned_tables_impl.test" \
"~test/sql/integration/table/order_preservation_modes.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" \
"~test/sql/integration/catalog/zero_count_bypass.test" \
"~test/sql/integration/table/filter_echo_partitioned.test" \
"~test/sql/integration/table/partitioned_sequence.test" \
"~test/sql/integration/table/batch_index.test" \
"~test/sql/integration/table/order_preservation_modes.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).
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); \
python3 scripts/run_tests.py -j $(JOBS) $(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 three tests the launcher path can't satisfy
# (vgi_worker_pool, filter_echo_partitioned, versioned_tables_impl assert
# per-process pool semantics — meaningful only under subprocess transport).
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:}/vgi"; \
export VGI_VERSIONED_HTTP_WORKER="http://localhost:$${vport_line#PORT:}/vgi"; \
export VGI_ATTACH_OPTIONS_WORKER="http://localhost:$${aport_line#PORT:}/vgi"; \
export VGI_VERSIONED_TABLES_HTTP_WORKER="http://localhost:$${tport_line#PORT:}/vgi"; \
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:}/vgi"; \
export VGI_VERSIONED_HTTP_WORKER="http://localhost:$${vport_line#PORT:}/vgi"; \
export VGI_ATTACH_OPTIONS_WORKER="http://localhost:$${aport_line#PORT:}/vgi"; \
export VGI_VERSIONED_TABLES_HTTP_WORKER="http://localhost:$${tport_line#PORT:}/vgi"; \
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