-
Notifications
You must be signed in to change notification settings - Fork 0
472 lines (406 loc) · 17 KB
/
Copy pathci.yml
File metadata and controls
472 lines (406 loc) · 17 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
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
name: CI
on:
push:
branches: [main]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
MAVEN_ARGS: -B -ntp
jobs:
# The API artifact is what a caller compiles against, it has no native
# code in it, and it is the one thing that has to build on every JDK
# this client claims to support. It needs no engine, so it answers in
# under a minute and it answers first.
api:
strategy:
fail-fast: false
matrix:
java: ["17", "21", "25", "26-ea"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven
# Only the API module, because the FFM provider compiles to release
# 25 and a JDK 17 compiler cannot be asked for that. A caller on 17
# gets exactly this artifact and the JNI provider beside it.
- run: mvn $MAVEN_ARGS -pl zudb -am test
# The Arrow reader is a 17 artifact as well, and the README says so
# in a table. This is what keeps that true. Its tests need the FFM
# provider to run against, which this JDK cannot build, so what is
# checked here is that the sources a 17 caller compiles against
# compile on 17.
- run: mvn $MAVEN_ARGS -pl zudb -am install -DskipTests
- run: mvn $MAVEN_ARGS -pl zudb-arrow compile
# The whole client against the engine at its own HEAD, which is what
# makes a red job here mean the binding is wrong about the ABI rather
# than that a checked-in copy of something is stale.
engine:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
java: ["25", "26-ea"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v5
with:
repository: tamnd/zu
path: engine
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven
- uses: Swatinem/rust-cache@v2
with:
workspaces: engine
# ZU_ABI_VERSION is a header macro rather than a symbol, so a
# binding with no C compile step has nowhere to read it from and
# has to write it down. This is the step that stops the written
# down copy from drifting.
- name: The ABI this client speaks is the ABI the engine offers
run: |
set -eu
engine_abi="$(sed -n 's/^#define ZU_ABI_VERSION "\(.*\)"$/\1/p' \
engine/crates/zu-capi/include/zu.h)"
client_abi="$(sed -n 's/.*ABI_VERSION = "\(.*\)";.*/\1/p' \
zudb/src/main/java/dev/zudb/Zu.java)"
test -n "$engine_abi"
test -n "$client_abi"
echo "engine $engine_abi, client $client_abi"
test "$engine_abi" = "$client_abi"
# The shim is written against this header and opens a libzu at run
# time, so a copy that has drifted compiles clean and then reads a
# struct that moved. This is the step that stops it drifting.
- name: The vendored header is the engine's header
run: diff -u engine/crates/zu-capi/include/zu.h zudb-jni/src/main/c/zu.h
- name: Build libzu
working-directory: engine
run: cargo build --release -p zu-capi
- name: Where the library landed
run: |
set -eu
lib="$(ls engine/target/release/libzu.dylib engine/target/release/libzu.so 2>/dev/null | head -1)"
test -n "$lib"
echo "ZU_LIBRARY=$GITHUB_WORKSPACE/$lib" >> "$GITHUB_ENV"
# One platform, this runner's, which is all a runner can build and
# all the suite here needs. The other six are built in the release,
# on runners of their own.
- name: Build the JNI shim
run: ./scripts/build-shim.sh
- run: mvn $MAVEN_ARGS test
# The suite again with assertions on everywhere, including the ones
# in the JDK itself. The bounds checks a MemorySegment does are the
# difference between a wrong offset failing and a wrong offset
# reading somebody else's memory.
- run: mvn $MAVEN_ARGS test -Dzu.test.args="-ea -esa"
# Not for the numbers, which mean nothing on a shared runner, but
# because a benchmark is code that nothing else compiles and
# nothing else runs. One iteration is enough to say it still works.
- run: mvn $MAVEN_ARGS -DskipTests package
- run: java -jar zudb-bench/target/benchmarks.jar -f 1 -wi 1 -i 1 -r 1s -w 1s
# The cross client corpus, run against the engine this job builds.
# Every client answers the same fourteen hundred cases and a report is
# diffed line for line against the other four, so this is the job that
# says this client agrees with them rather than only with itself.
#
# The cases come from the same checkout the library was built from,
# which is the pairing that makes the report mean anything. A corpus
# ahead of the library reports the engine catching up to its own cases
# as this client failing, and a library ahead of the corpus reports
# nothing at all. This client builds the engine rather than shipping an
# archive of it, so both are the same checkout and there is no revision
# to pin.
#
# A job of its own rather than a step in the one above, because the run
# is fourteen hundred databases and the job above runs its suite three
# times.
corpus:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v5
with:
repository: tamnd/zu
path: engine
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "25"
cache: maven
- uses: Swatinem/rust-cache@v2
with:
workspaces: engine
- name: Build libzu
working-directory: engine
run: cargo build --release -p zu-capi
- name: Where the library landed
run: |
set -eu
lib="$(ls engine/target/release/libzu.so)"
test -n "$lib"
echo "ZU_LIBRARY=$GITHUB_WORKSPACE/$lib" >> "$GITHUB_ENV"
# Verbose, because the run logs every case the engine has not
# caught up to and a release branch is read for exactly that.
- run: mvn $MAVEN_ARGS -pl zudb-corpus -am test -Dsurefire.useFile=false
env:
ZU_CASES: ${{ github.workspace }}/engine/conformance/cases
# The JNI provider on the JDKs it exists for. Panama is not there on
# 17 or 21, so on those two this is the only way to call the engine at
# all, and a client that claims 17 and is only ever tested on 25 is a
# client that claims 17.
#
# It runs the same cases the Panama provider runs, out of the same
# artifact, so a difference between the two providers is a red job here
# rather than something a user finds.
jni:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
java: ["17", "21"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v5
with:
repository: tamnd/zu
path: engine
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven
- uses: Swatinem/rust-cache@v2
with:
workspaces: engine
- name: The vendored header is the engine's header
run: diff -u engine/crates/zu-capi/include/zu.h zudb-jni/src/main/c/zu.h
- name: Build libzu
working-directory: engine
run: cargo build --release -p zu-capi
- name: Where the library landed
run: |
set -eu
lib="$(ls engine/target/release/libzu.dylib engine/target/release/libzu.so 2>/dev/null | head -1)"
test -n "$lib"
echo "ZU_LIBRARY=$GITHUB_WORKSPACE/$lib" >> "$GITHUB_ENV"
- name: Build the JNI shim
run: ./scripts/build-shim.sh
# Named modules rather than the whole reactor, because the Panama
# provider compiles to release 25 and this JDK cannot be asked for
# that. What is left is exactly what a caller on 17 gets.
- run: mvn $MAVEN_ARGS -pl zudb,zudb-tck,zudb-jni -am test
# Again with every assertion on, including the JDK's own. A wrong
# length handed to NewDirectByteBuffer is the difference between a
# test that fails and a test that reads somebody else's memory.
- run: mvn $MAVEN_ARGS -pl zudb,zudb-tck,zudb-jni -am test -Dzu.test.args="-ea -esa"
# The claim the zudb-native artifact makes is that a user who added a
# dependency and installed nothing has an engine. Nothing in the test
# suite can check that, because the suite is told where the library is
# so that it tests the binding rather than the search. So it is
# checked here, once, the way a user meets it: a classpath, no
# property, no environment variable, and a statement.
natives:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v5
with:
repository: tamnd/zu
path: engine
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "25"
cache: maven
- uses: Swatinem/rust-cache@v2
with:
workspaces: engine
- name: Build libzu
working-directory: engine
run: cargo build --release -p zu-capi
# One platform rather than seven, because this runner can only
# build the one it is, so the rule that every platform is staged
# is stood down here and holds where it matters, in the release.
- name: Stage the one platform this runner is
run: |
set -eu
case "$RUNNER_OS" in
Linux) flavour=linux-amd64; library=libzu.so ;;
macOS) flavour=darwin-arm64; library=libzu.dylib ;;
*) echo "no row for $RUNNER_OS"; exit 1 ;;
esac
mkdir -p "zudb-native/lib/$flavour"
cp "engine/target/release/$library" "zudb-native/lib/$flavour/$library"
- run: mvn $MAVEN_ARGS -Pnatives -DskipTests -Denforcer.skip=true package
- name: A classpath, and nothing else
run: |
set -eu
mkdir -p "$RUNNER_TEMP/user"
cat > "$RUNNER_TEMP/user/Main.java" <<'EOF'
import dev.zudb.Connection;
import dev.zudb.Result;
import dev.zudb.Zu;
public class Main {
public static void main(String[] args) {
System.out.println("found " + Zu.library() + " through " + Zu.source());
try (Connection conn = Connection.memory();
Result r = conn.query("RETURN 1 AS one")) {
if (r.row(0).getLong(0) != 1L) {
throw new AssertionError("the engine answered something else");
}
}
System.out.println("the engine came out of the jar and answered");
}
}
EOF
cp=$(ls zudb/target/zudb-*.jar zudb-ffm/target/zudb-ffm-*.jar \
zudb-native/target/zudb-native-*.jar | grep -v sources | tr '\n' ':')
javac -cp "$cp" -d "$RUNNER_TEMP/user" "$RUNNER_TEMP/user/Main.java"
env -u ZU_LIBRARY java --enable-native-access=ALL-UNNAMED \
-cp "$cp$RUNNER_TEMP/user" Main
# An image has no linker in it. Every downcall stub is machine code
# written while the image is built, from a file that says which
# signatures to write, and a file that is wrong produces an image that
# builds clean and dies on the first query. The unit test checks that
# file against what the binding binds; only an image can check that the
# file is the file the builder wanted, so one gets built here and run.
native:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v5
with:
repository: tamnd/zu
path: engine
- uses: graalvm/setup-graalvm@v1
with:
java-version: "25"
distribution: graalvm
cache: maven
github-token: ${{ secrets.GITHUB_TOKEN }}
- uses: Swatinem/rust-cache@v2
with:
workspaces: engine
- name: Build libzu
working-directory: engine
run: cargo build --release -p zu-capi
- name: Stage the one platform this runner is
run: |
set -eu
case "$RUNNER_OS" in
Linux) flavour=linux-amd64; library=libzu.so ;;
macOS) flavour=darwin-arm64; library=libzu.dylib ;;
*) echo "no row for $RUNNER_OS"; exit 1 ;;
esac
mkdir -p "zudb-native/lib/$flavour"
cp "engine/target/release/$library" "zudb-native/lib/$flavour/$library"
- run: mvn $MAVEN_ARGS -Pnatives -DskipTests -Denforcer.skip=true package
# The same program the natives job runs on a JVM, compiled to a
# binary instead. No property, no environment variable, and the
# library comes out of the image rather than off the disk.
- name: An image, and nothing else
run: |
set -eu
mkdir -p "$RUNNER_TEMP/user"
cat > "$RUNNER_TEMP/user/Main.java" <<'EOF'
import dev.zudb.Connection;
import dev.zudb.Result;
import dev.zudb.Zu;
public class Main {
public static void main(String[] args) {
System.out.println("found " + Zu.library() + " through " + Zu.source());
try (Connection conn = Connection.memory();
Result r = conn.query("UNWIND [1, 2, 3] AS n RETURN sum(n) AS total")) {
if (r.row(0).getLong(0) != 6L) {
throw new AssertionError("the image answered something else");
}
}
System.out.println("the engine came out of the image and answered");
}
}
EOF
cp=$(ls zudb/target/zudb-*.jar zudb-ffm/target/zudb-ffm-*.jar \
zudb-native/target/zudb-native-*.jar | grep -v sources | tr '\n' ':')
javac -cp "$cp" -d "$RUNNER_TEMP/user" "$RUNNER_TEMP/user/Main.java"
native-image -cp "$cp$RUNNER_TEMP/user" \
--no-fallback -o "$RUNNER_TEMP/user/main" Main
env -u ZU_LIBRARY "$RUNNER_TEMP/user/main"
# A binding holds native memory, and the process that finds out later
# is the user's. The suite cannot see that: a test that closes nothing
# and asserts on a message passes, and the memory it left behind is
# somebody else's problem an hour into a run.
#
# So the allocator is asked instead. LeakSanitizer is loaded ahead of
# the JVM, a driver opens and closes every handle this client hands
# out, and the report is read for blocks whose stack names libzu. The
# JVM's own unfreed megabyte is not read, because a JVM does not free
# at exit on purpose and none of it is anything a user can act on.
#
# Both providers, because the two allocate down different paths: FFM
# arenas on one side, NewDirectByteBuffer and a shim on the other, and
# a leak in one of them is invisible from the other.
leaks:
strategy:
fail-fast: false
matrix:
provider: [ffm, jni]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v5
with:
repository: tamnd/zu
path: engine
# 25 for both rows. The FFM provider cannot be compiled by anything
# older, and the shim asks for JNI 1.8 in JNI_OnLoad, so the JNI
# row is the same code a caller on 17 runs.
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "25"
cache: maven
- uses: Swatinem/rust-cache@v2
with:
workspaces: engine
# Not built with the sanitizer, deliberately. Interposing the
# allocator is enough to see a block nobody freed, and an
# instrumented engine would mean building the whole of Rust twice
# to answer a question about this repository.
- name: Build libzu
working-directory: engine
run: cargo build --release -p zu-capi
- name: Where the library landed
run: echo "ZU_LIBRARY=$GITHUB_WORKSPACE/engine/target/release/libzu.so" >> "$GITHUB_ENV"
- name: Build the JNI shim
if: matrix.provider == 'jni'
run: ./scripts/build-shim.sh
- run: ./scripts/leaks.sh ${{ matrix.provider }}
# What Maven Central will run over the artifacts, run here instead so
# that a release is not the first time anyone sees it.
javadoc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "25"
cache: maven
- run: mvn $MAVEN_ARGS -P release -DskipTests package