Skip to content

Commit b29e8fa

Browse files
rustyconoverclaude
andcommitted
ci: run the vgi integration suite on every push + expression-filter pushdown
Add a GitHub Actions workflow that runs the canonical Query-farm/vgi integration sqllogictest suite against the Java example worker on every push/PR, without building the C++ extension from source. It drives a prebuilt standalone haybarn-unittest and installs the signed vgi extension via `INSTALL vgi FROM community` (deps from core); ci/preprocess-require.awk rewrites each `require <ext>` gate into an explicit INSTALL+LOAD so the standalone runner can run the tests. ci/run-integration.sh + ci/wrappers/ are the committed, path-relative replacements for the old /tmp harness. Validated at 172 pass / 13 skip / 0 fail (skips are the http/bearer/dynamic/ schema_reconcile require-env gates). Greening the suite surfaced a real gap: spatial_filter_example and expression_filter_test were zero-row stubs, and table/expression_filter.test had always silently skipped (the dev harness can't load spatial). Implement the expression-filter pushdown subsystem end-to-end: - core: PushdownFilterType.EXPRESSION + PushdownFilter.Expression; decoder renders the pushed expression tree to SQL (geoarrow.wkb -> ST_GeomFromHEXWKB), mirroring vgi-python's ExpressionNode.to_sql; expressionPredicates() accessors. - worker: ExpressionFilterEvaluator evaluates predicates against each batch via an embedded haybarn_jdbc engine (spatial loaded), bridged in over the Arrow C Data interface. haybarn_jdbc is a worker-only dependency; vgi core stays engine-free. - FunctionMetadata.withSupportedExpressionFilters -> FunctionInfo wire field. - real SpatialFilterExampleFunction + ExpressionFilterTestFunction fixtures replace the stubs. expression_filter.test now runs and passes (30 assertions, incl. the pushdown EXPLAIN asserts). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ad7757d commit b29e8fa

22 files changed

Lines changed: 936 additions & 40 deletions

.github/workflows/integration.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Copyright 2026 Query Farm LLC - https://query.farm
2+
#
3+
# Runs the canonical vgi integration sqllogictest suite against the Java
4+
# example worker on every push / PR. Rather than building the C++ extension
5+
# from source, it drives a prebuilt standalone `haybarn-unittest` (the
6+
# DuckDB/Haybarn sqllogictest runner) and installs the SIGNED vgi extension
7+
# from the Haybarn community channel + its deps from core. The .test files
8+
# come from a pinned Query-farm/vgi checkout; ci/preprocess-require.awk turns
9+
# each `require <ext>` gate into an explicit INSTALL+LOAD so the standalone
10+
# runner (which links none of these extensions) can run them.
11+
#
12+
# See ci/README.md for the full design and the version-pin coupling.
13+
name: Integration suite
14+
15+
on:
16+
push:
17+
pull_request:
18+
workflow_dispatch:
19+
20+
permissions:
21+
contents: read
22+
23+
concurrency:
24+
group: integration-${{ github.ref }}
25+
cancel-in-progress: true
26+
27+
env:
28+
# The Query-farm/vgi commit whose test/sql/integration suite we run. Pinned
29+
# for reproducibility; bump deliberately and re-validate against the then-current
30+
# community extension (see ci/README.md).
31+
VGI_REF: 4444d669c700d9efd70e5f97db4d2c4c886f509f
32+
# The Haybarn release providing the prebuilt haybarn-unittest binary. Must be
33+
# ABI-compatible with the community-published vgi extension (both v1.5.3).
34+
HAYBARN_RELEASE: haybarn-v1.5.3-rc7
35+
36+
jobs:
37+
integration:
38+
runs-on: ubuntu-latest
39+
steps:
40+
- name: Checkout vgi-java
41+
uses: actions/checkout@v4
42+
43+
- name: Checkout pinned vgi test suite
44+
uses: actions/checkout@v4
45+
with:
46+
repository: Query-farm/vgi
47+
ref: ${{ env.VGI_REF }}
48+
path: vgi-upstream
49+
50+
- name: Set up JDK 25
51+
uses: actions/setup-java@v4
52+
with:
53+
distribution: temurin
54+
java-version: '25'
55+
56+
- name: Cache Gradle
57+
uses: actions/cache@v4
58+
with:
59+
path: |
60+
~/.gradle/caches
61+
~/.gradle/wrapper
62+
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
63+
restore-keys: gradle-${{ runner.os }}-
64+
65+
- name: Build example worker
66+
run: ./gradlew --no-daemon :vgi-example-worker:installDist
67+
68+
- name: Download haybarn-unittest
69+
run: |
70+
gh release download "$HAYBARN_RELEASE" \
71+
--repo Query-farm-haybarn/haybarn \
72+
--pattern 'haybarn_unittest-linux-amd64.zip' \
73+
--output /tmp/haybarn-unittest.zip --clobber
74+
unzip -o -q /tmp/haybarn-unittest.zip -d /tmp/haybarn-unittest
75+
UNITTEST=$(find /tmp/haybarn-unittest -name 'haybarn-unittest' -type f | head -1)
76+
chmod +x "$UNITTEST"
77+
echo "HAYBARN_UNITTEST=$UNITTEST" >> "$GITHUB_ENV"
78+
env:
79+
GH_TOKEN: ${{ github.token }}
80+
81+
- name: Run integration suite
82+
run: ci/run-integration.sh
83+
env:
84+
VGI_SRC: ${{ github.workspace }}/vgi-upstream
85+
VGI_WORKER_BIN: ${{ github.workspace }}/vgi-example-worker/build/install/vgi-example-worker/bin/vgi-example-worker
86+
# JDK 25 + native access for the worker JVM (FFM shm, haybarn_jdbc).
87+
JAVA_TOOL_OPTIONS: --enable-native-access=ALL-UNNAMED

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ build/
66
.vscode/
77
.DS_Store
88
/tmp/
9+
# Local scratch / test artifacts
10+
duckdb_unittest_tempdir/
11+
.wrangler/
12+
.playwright-mcp/

CLAUDE.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,63 @@ older interfaces** (`TableFunction`, `TableInOutFunction`, etc.) — the
248248
`ScalarFn` style hasn't been extended to those because their richer
249249
lifecycle methods + per-execution state don't translate one-for-one.
250250

251+
## State of play (as of 2026-06-12)
252+
253+
**2026-06-12 — GitHub Actions integration CI + expression-filter pushdown.**
254+
Two coupled pieces landed so the integration suite runs on every push/PR
255+
without a C++ build:
256+
257+
- **CI (`.github/workflows/integration.yml` + `ci/`).** Drives the **prebuilt**
258+
standalone `haybarn-unittest` (Haybarn release asset) against the Java worker,
259+
installing the **signed** vgi extension via `INSTALL vgi FROM community` (deps
260+
from `core`) — no extension build from source. The `.test` files come from a
261+
pinned `Query-farm/vgi` checkout (`VGI_REF` in the workflow `env:`). The
262+
standalone runner links **none** of vgi/httpfs/json/parquet/spatial, so
263+
`ci/preprocess-require.awk` rewrites every `require <ext>` into an explicit
264+
`INSTALL … FROM {community,core}; LOAD …;` (`require-env` untouched).
265+
`ci/run-integration.sh` stages the preprocessed tree, sets the four
266+
`VGI_*_WORKER` vars (`launch:` + the three `ci/wrappers/` catalog wrappers —
267+
the committed, path-relative replacements for the old `/tmp/vgi-worker-*`),
268+
warms the extension cache once, and tallies pass/skip/fail. Validated locally
269+
at **172 pass / 13 skip / 0 fail** (the 13 skips are the http/bearer/dynamic/
270+
`schema_reconcile` `require-env` gates). **Path-B coupling:** the extension is
271+
pulled live from community (not version-pinned), so a `VGI_REF` bump must be
272+
re-validated against the then-current community build — see `ci/README.md`.
273+
274+
- **Expression-filter pushdown subsystem** (was entirely absent; this is why
275+
`spatial_filter_example`/`expression_filter_test` were zero-row `Stub`s and
276+
`table/expression_filter.test` always *skipped* — the local dev harness can't
277+
load spatial). Now implemented end-to-end:
278+
- **Wire decode (core):** `PushdownFilterType.EXPRESSION` +
279+
`PushdownFilter.Expression(columnName, columnIndex, sql)`.
280+
`PushdownFiltersDecoder` renders the pushed expression tree
281+
(`column_ref`/`constant`/`function`/`comparison`/`conjunction`, constants
282+
resolved from sibling `value_ref` columns, geoarrow.wkb →
283+
`ST_GeomFromHEXWKB`) to a SQL predicate — mirroring vgi-python's
284+
`ExpressionNode.to_sql`. `evaluate()` skips Expression (not row-at-a-time);
285+
`PushdownFilters.expressionPredicates()` / `FilterApplier.expressionPredicates()`
286+
expose the SQL.
287+
- **Evaluator (worker):** `example/table/ExpressionFilterEvaluator` evaluates
288+
the predicates against each batch via an embedded **haybarn_jdbc** engine
289+
(Maven Central `farm.query.haybarn:haybarn_jdbc:1.5.3` + `arrow-c-data`),
290+
thread-local connection with spatial loaded — the Java analogue of
291+
vgi-python's `vgi._duckdb` evaluator. Batch is bridged in via the Arrow C
292+
Data interface (`registerArrowStream`); a registered stream is **single-use**
293+
so it's queried once per batch; `FilterApplier.compact` does the masking.
294+
**haybarn_jdbc is a worker-only dep — the published `vgi` core stays
295+
engine-free.**
296+
- **Capability wiring:** `FunctionMetadata.withSupportedExpressionFilters(...)`
297+
`FunctionInfo.supported_expression_filters` (was hardcoded `List.of()` at
298+
`VgiServiceImpl` ~line 1731). The C++ extension only pushes an expression
299+
filter when every function name in the tree is declared; otherwise it keeps
300+
a FILTER node (the `length(name) > 7` EXPLAIN case).
301+
- **Fixtures:** real `SpatialFilterExampleFunction` (`{n,x,y,geom GEOMETRY}`
302+
grid, declares `&&`/`st_intersects_extent`) + `ExpressionFilterTestFunction`
303+
(`{id,name,tags,score}`, declares `list_contains`/`starts_with`/`contains`)
304+
replace the two stubs. `table/expression_filter.test` now **runs and passes**
305+
(30 assertions, incl. the pushdown EXPLAIN asserts) — strictly better than
306+
the prior silent skip.
307+
251308
## State of play (as of 2026-06-11)
252309

253310
**2026-06-11 — ported the evolved vgi-python storage layer** (counters + ranged

ci/README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# CI: the vgi integration suite
2+
3+
[`.github/workflows/integration.yml`](../.github/workflows/integration.yml)
4+
runs the canonical [Query-farm/vgi](https://github.com/Query-farm/vgi)
5+
integration sqllogictest suite against this repo's Java example worker on every
6+
push / PR. The same `.test` files run against the Python and Go ports, so a
7+
green run here is real wire-compatibility evidence.
8+
9+
## How it works (no C++ build)
10+
11+
Rather than building the vgi DuckDB extension from source, CI drives a
12+
**prebuilt** standalone `haybarn-unittest` (the DuckDB/Haybarn sqllogictest
13+
runner, published in Haybarn's releases) and installs the **signed** vgi
14+
extension from the Haybarn community channel:
15+
16+
1. **Build the worker**`./gradlew :vgi-example-worker:installDist`.
17+
2. **Checkout the test suite**`Query-farm/vgi` at a pinned commit; its
18+
`test/sql/integration/*.test` files are the suite.
19+
3. **Download the runner**`haybarn_unittest-linux-amd64.zip` from the pinned
20+
Haybarn release.
21+
4. **Preprocess** — the standalone runner links none of the extensions the
22+
tests gate on, so [`preprocess-require.awk`](preprocess-require.awk) rewrites
23+
each `require <ext>` into an explicit signed `INSTALL <ext> FROM
24+
{community,core}; LOAD <ext>;`. Everything else (notably `require-env`) is
25+
left untouched.
26+
5. **Run**[`run-integration.sh`](run-integration.sh) stages the preprocessed
27+
tree, points the four `VGI_*_WORKER` env vars at the worker (via `launch:`,
28+
which amortises JVM cold-start across the run) and the three catalog
29+
[`wrappers/`](wrappers), warms the extension cache once, then runs each
30+
`.test` and tallies pass / skip / fail.
31+
32+
Out of scope and excluded: `writable/`, `simple_writable/` (the port is
33+
read-only), and `nested_type_combinations.test` (segfaults the upstream
34+
runner — see the project `CLAUDE.md`). The HTTP / bearer / dynamic-code /
35+
`schema_reconcile` tests skip via their `require-env` gates (we don't set those
36+
workers), exactly as in the reference harness.
37+
38+
## Run it locally
39+
40+
```bash
41+
./gradlew :vgi-example-worker:installDist
42+
VGI_SRC=~/path/to/vgi-checkout \
43+
HAYBARN_UNITTEST=/path/to/haybarn-unittest \
44+
VGI_WORKER_BIN="$PWD/vgi-example-worker/build/install/vgi-example-worker/bin/vgi-example-worker" \
45+
ci/run-integration.sh
46+
```
47+
48+
## Version pins (and their coupling)
49+
50+
Two pins live in the workflow's `env:` block:
51+
52+
| Pin | What | Why |
53+
|-----|------|-----|
54+
| `VGI_REF` | the `Query-farm/vgi` commit supplying the `.test` files | reproducibility — bump deliberately |
55+
| `HAYBARN_RELEASE` | the Haybarn release supplying `haybarn-unittest` | must be ABI-compatible with the community vgi extension (both `v1.5.3`) |
56+
57+
**The coupling to know about:** the vgi extension is pulled live from the
58+
community channel (`INSTALL vgi FROM community`), which always serves the
59+
*currently published* build — it is not version-pinned here. So CI verifies the
60+
Java worker against **what users can actually install today**. If `VGI_REF`
61+
points at a commit whose tests exercise a protocol feature the published
62+
extension doesn't yet ship (or vice-versa), that test can fail or skip. When
63+
bumping `VGI_REF`, re-run the suite locally against the current community
64+
extension and adjust the pin / wrappers together. The catalog
65+
[`wrappers/`](wrappers) encode the canonical `versioned` /
66+
`versioned_tables` / `attach_options` fixture version sets and must track those
67+
fixtures when they move.

ci/preprocess-require.awk

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2026 Query Farm LLC - https://query.farm
2+
#
3+
# Rewrite each `require <ext>` gate in an upstream vgi sqllogictest into an
4+
# explicit signed INSTALL+LOAD, so the prebuilt standalone `haybarn-unittest`
5+
# (which links none of these extensions) can run the suite. The vgi extension
6+
# comes from the signed community channel; httpfs/json/parquet/spatial from the
7+
# signed core channel. `require-env` and every other directive pass through
8+
# untouched. See ci/README.md.
9+
/^require[ \t]+vgi[ \t]*$/ {
10+
print "statement ok"; print "INSTALL vgi FROM community;"; print "";
11+
print "statement ok"; print "LOAD vgi;"; next
12+
}
13+
/^require[ \t]+(httpfs|json|parquet|spatial)[ \t]*$/ {
14+
ext = $2
15+
print "statement ok"; print "INSTALL " ext " FROM core;"; print "";
16+
print "statement ok"; print "LOAD " ext ";"; next
17+
}
18+
{ print }

ci/run-integration.sh

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2026 Query Farm LLC - https://query.farm
3+
#
4+
# Run the vgi integration sqllogictest suite against the Java example worker,
5+
# using a prebuilt standalone `haybarn-unittest` and the signed community vgi
6+
# extension (no C++ build from source). See ci/README.md for the approach.
7+
#
8+
# Required environment:
9+
# VGI_SRC path to a Query-farm/vgi checkout (contains test/sql/integration)
10+
# HAYBARN_UNITTEST path to the haybarn-unittest binary
11+
# VGI_WORKER_BIN path to the built example worker launcher
12+
# (vgi-example-worker/build/install/.../bin/vgi-example-worker)
13+
# Optional:
14+
# STAGE scratch dir for the preprocessed test tree (default: mktemp)
15+
set -euo pipefail
16+
17+
: "${VGI_SRC:?path to a Query-farm/vgi checkout}"
18+
: "${HAYBARN_UNITTEST:?path to the haybarn-unittest binary}"
19+
: "${VGI_WORKER_BIN:?path to the example worker launcher}"
20+
21+
HERE="$(cd "$(dirname "$0")" && pwd)"
22+
STAGE="${STAGE:-$(mktemp -d)}"
23+
INTEGRATION="$VGI_SRC/test/sql/integration"
24+
[ -d "$INTEGRATION" ] || { echo "::error::no test/sql/integration under VGI_SRC=$VGI_SRC"; exit 1; }
25+
26+
echo "Staging preprocessed tests into $STAGE ..."
27+
mkdir -p "$STAGE/test/sql/integration"
28+
( cd "$INTEGRATION"
29+
# writable/simple_writable are out of scope (read-only port);
30+
# nested_type_combinations segfaults the upstream runner (documented in CLAUDE.md).
31+
find . -name '*.test' \
32+
-not -path '*/writable/*' -not -path '*/simple_writable/*' \
33+
-not -name 'nested_type_combinations.test' | while read -r f; do
34+
mkdir -p "$STAGE/test/sql/integration/$(dirname "$f")"
35+
awk -f "$HERE/preprocess-require.awk" "$f" > "$STAGE/test/sql/integration/$f"
36+
done )
37+
38+
# launch: amortises the JVM cold-start across the whole run via a flock-coordinated
39+
# AF_UNIX worker pool. VGI_TEST_DEDICATED_WORKER is a plain (non-pooled) worker for
40+
# the crash/pool-recovery tests. The three wrappers route the one binary into the
41+
# versioned / versioned_tables / attach_options catalogs.
42+
export VGI_WORKER_BIN
43+
export VGI_TEST_WORKER="launch:${VGI_WORKER_BIN}"
44+
export VGI_TEST_DEDICATED_WORKER="${VGI_WORKER_BIN}"
45+
export VGI_VERSIONED_WORKER="launch:${HERE}/wrappers/vgi-worker-versioned"
46+
export VGI_VERSIONED_TABLES_WORKER="launch:${HERE}/wrappers/vgi-worker-versioned-tables"
47+
export VGI_ATTACH_OPTIONS_WORKER="launch:${HERE}/wrappers/vgi-worker-attach-options"
48+
49+
cd "$STAGE"
50+
51+
echo "Warming the extension cache (vgi from community, deps from core) ..."
52+
mkdir -p "$STAGE/test"
53+
cat > "$STAGE/test/_warm.test" <<'EOF'
54+
# name: test/_warm.test
55+
# group: [warm]
56+
statement ok
57+
INSTALL vgi FROM community;
58+
59+
statement ok
60+
INSTALL httpfs FROM core;
61+
62+
statement ok
63+
INSTALL json FROM core;
64+
65+
statement ok
66+
INSTALL parquet FROM core;
67+
68+
statement ok
69+
INSTALL spatial FROM core;
70+
EOF
71+
"$HAYBARN_UNITTEST" "test/_warm.test" >/dev/null 2>&1 || echo "::warning::extension warm step did not fully succeed"
72+
rm -f "$STAGE/test/_warm.test"
73+
74+
echo "Running suite ..."
75+
pass=0; skip=0; fail=0; failed=()
76+
while IFS= read -r t; do
77+
rel="${t#"$STAGE"/}"
78+
if out=$("$HAYBARN_UNITTEST" "$rel" 2>&1); then :; fi
79+
if grep -q "All tests passed" <<<"$out"; then
80+
pass=$((pass + 1))
81+
elif grep -qE "All tests were skipped|No tests ran" <<<"$out"; then
82+
skip=$((skip + 1))
83+
else
84+
fail=$((fail + 1)); failed+=("$rel")
85+
echo "----- FAIL: $rel -----"
86+
printf '%s\n' "$out" | tail -40
87+
fi
88+
done < <(find "$STAGE/test" -name '*.test' | sort)
89+
90+
echo "=================================================="
91+
echo "PASS=$pass SKIP=$skip FAIL=$fail"
92+
if [ "$fail" -gt 0 ]; then
93+
printf ' failed: %s\n' "${failed[@]}"
94+
exit 1
95+
fi
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
# Routes the single example worker binary into the `attach_options` catalog
3+
# (Main.java short-circuits this catalog name to the echo_attach_options
4+
# worker). Worker binary named via $VGI_WORKER_BIN.
5+
export VGI_WORKER_CATALOG_NAME=attach_options
6+
exec "${VGI_WORKER_BIN:?set VGI_WORKER_BIN to the example worker launcher}" "$@"

ci/wrappers/vgi-worker-versioned

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
# Routes the single example worker binary into the canonical `versioned`
3+
# catalog (vgi-python/_test_fixtures/versioned.py). Must stay in sync with that
4+
# fixture's version sets. The worker binary is named via $VGI_WORKER_BIN.
5+
export VGI_WORKER_CATALOG_NAME=versioned
6+
export VGI_WORKER_IMPLEMENTATION_VERSION=1.0.0
7+
export VGI_WORKER_SUPPORTED_IMPL_VERSIONS=1.0.0
8+
export VGI_WORKER_DATA_VERSION_SPEC='>=1.0.0,<2.0.0'
9+
export VGI_WORKER_SUPPORTED_VERSIONS=1.0.0,1.1.0,1.2.0
10+
export VGI_WORKER_DEFAULT_DATA_VERSION=1.2.0
11+
exec "${VGI_WORKER_BIN:?set VGI_WORKER_BIN to the example worker launcher}" "$@"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
# Routes the single example worker binary into the canonical `versioned_tables`
3+
# catalog (vgi-python/_test_fixtures/versioned_tables.py). Must stay in sync
4+
# with that fixture's version sets. Worker binary named via $VGI_WORKER_BIN.
5+
export VGI_WORKER_CATALOG_NAME=versioned_tables
6+
export VGI_WORKER_IMPLEMENTATION_VERSION=11.0.0
7+
export VGI_WORKER_SUPPORTED_IMPL_VERSIONS=10.0.0,10.1.0,11.0.0
8+
export VGI_WORKER_DATA_VERSION_SPEC='>=1.0.0,<4.0.0'
9+
export VGI_WORKER_SUPPORTED_VERSIONS=1.0.0,1.1.0,2.0.0,3.0.0
10+
export VGI_WORKER_DEFAULT_DATA_VERSION=3.0.0
11+
exec "${VGI_WORKER_BIN:?set VGI_WORKER_BIN to the example worker launcher}" "$@"

vgi-example-worker/build.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ plugins {
66
dependencies {
77
implementation(project(":vgi"))
88
implementation("org.slf4j:slf4j-simple:2.0.16")
9+
// Embedded Haybarn engine for evaluating pushed expression filters
10+
// (spatial &&, list_contains, ...) against emitted batches — mirrors
11+
// vgi-python's `vgi._duckdb` expression-filter evaluator. arrow-c-data
12+
// bridges an Arrow batch into the engine via the C Data interface.
13+
implementation("farm.query.haybarn:haybarn_jdbc:1.5.3")
14+
implementation("org.apache.arrow:arrow-c-data:18.1.0")
15+
16+
testImplementation("org.junit.jupiter:junit-jupiter:5.11.3")
17+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
918
}
1019

1120
application {

0 commit comments

Comments
 (0)