2424 image : xd009642/tarpaulin:develop-nightly
2525 options : --security-opt seccomp=unconfined
2626 steps :
27- - uses : actions/checkout@v6
27+ - uses : actions/checkout@v7
2828 - uses : oven-sh/setup-bun@v2
2929 with :
3030 bun-version : latest
3939 run : cargo clippy --all-targets --all-features -- -D warnings
4040 - name : Test Deploy
4141 run : cargo publish --dry-run
42+ - name : Doctest
43+ # tarpaulin's --all-targets / default run never compiles doc
44+ # tests, which let a never-passing doctest land unnoticed —
45+ # run them explicitly before the (slow) coverage step.
46+ run : cargo test --workspace --doc
4247 - name : Test
4348 run : |
4449 # rust coverage issue
@@ -53,18 +58,86 @@ jobs:
5358 cargo fmt
5459 cargo tarpaulin --out Lcov Stdout --engine llvm
5560 - name : Upload to codecov.io
56- uses : codecov/codecov-action@v6
61+ uses : codecov/codecov-action@v7
5762 with :
5863 token : ${{ secrets.CODECOV_TOKEN }}
5964 fail_ci_if_error : true
6065 files : lcov.info
6166 if : github.ref == 'refs/heads/main'
6267
68+ # OBSERVATIONAL ONLY — this job must never gate, and its percentage must
69+ # never become a threshold. Rust's branch instrumentation is still unstable
70+ # (rust-lang/rust#79649), and rust-lang/rust#124118 lists as NOT yet
71+ # supported: match arms and or-patterns, the `?` operator, `.await`, and
72+ # any branch introduced by macro expansion — "the current implementation
73+ # discards any branch span that isn't directly visible in the function
74+ # body". vespera_macro owns roughly four fifths of the measured branches
75+ # and exists to generate code inside `quote!`, so those generated branches
76+ # are not reported as uncovered — they are dropped from the denominator.
77+ # The number therefore under-counts precisely where this codebase is most
78+ # complex, which is why it is a trend signal and not a pass/fail metric.
79+ #
80+ # vespera_jni is excluded for the same reason tarpaulin excludes it: it
81+ # cannot run without a JVM, and jni-e2e is its real coverage. Including it
82+ # reports ~4% branch and drowns out every other crate.
83+ rust-branch-coverage :
84+ name : Rust branch coverage (observational)
85+ runs-on : ubuntu-latest
86+ continue-on-error : true
87+ timeout-minutes : 30
88+ steps :
89+ - uses : actions/checkout@v7
90+ - uses : dtolnay/rust-toolchain@nightly
91+ with :
92+ components : llvm-tools-preview
93+ - uses : taiki-e/install-action@cargo-llvm-cov
94+ - name : Run instrumented tests
95+ # Allowed to fail. This pins a MOVING nightly, and toolchain drift
96+ # breaks tests that assert compiler output — the trybuild UI suite
97+ # blesses its .stderr files against stable, so a nightly diagnostic
98+ # reword fails it with nothing actually broken. The profraw data is
99+ # still written, so the report step below runs regardless.
100+ continue-on-error : true
101+ run : cargo llvm-cov --branch --workspace --no-fail-fast --no-report
102+ - name : Summarise branch coverage
103+ run : |
104+ # `--branch` belongs on the instrumented RUN above, not here: the
105+ # profdata already carries branch counters, and `report` rejects
106+ # the flag.
107+ cargo llvm-cov report --summary-only \
108+ --ignore-filename-regex '(benches|examples|vespera_jni)' \
109+ | tee branch-coverage.txt
110+ {
111+ echo '### Rust branch coverage (observational)'
112+ echo
113+ echo 'Excludes benches, examples and `vespera_jni` (JVM-only — covered by jni-e2e).'
114+ echo 'Not a gate: see the job comment in CI.yml for why this number under-counts.'
115+ echo
116+ echo '```'
117+ cat branch-coverage.txt
118+ echo '```'
119+ } >> "$GITHUB_STEP_SUMMARY"
120+ - name : Generate lcov
121+ if : always()
122+ run : |
123+ cargo llvm-cov report --lcov --output-path branch-lcov.info \
124+ --ignore-filename-regex '(benches|examples|vespera_jni)'
125+ - name : Upload branch coverage report
126+ if : always()
127+ uses : actions/upload-artifact@v7
128+ with :
129+ name : rust-branch-coverage
130+ path : |
131+ branch-coverage.txt
132+ branch-lcov.info
133+
63134 # publish
64135 changepacks :
65136 name : changepacks
66137 runs-on : ubuntu-latest
67- needs : test
138+ # jni-e2e gates publishing: a release must never ship with a broken
139+ # JNI dispatch path on any supported OS.
140+ needs : [test, jni-e2e]
68141 permissions :
69142 # create pull request comments
70143 pull-requests : write
76149 # Publish to GitHub Packages
77150 packages : write
78151 steps :
79- - uses : actions/checkout@v6
152+ - uses : actions/checkout@v7
80153 - uses : actions/setup-java@v5
81154 with :
82155 distribution : ' temurin'
@@ -101,6 +174,105 @@ jobs:
101174 # GPG signing (in-memory key, no keyring file)
102175 ORG_GRADLE_PROJECT_signingInMemoryKey : ${{ secrets.GPG_SIGNING_KEY }}
103176 ORG_GRADLE_PROJECT_signingInMemoryKeyPassword : ${{ secrets.GPG_SIGNING_PASSWORD }}
177+ # Gradle Plugin Portal credentials (read natively by
178+ # com.gradle.plugin-publish for the `publishPlugins` task)
179+ GRADLE_PUBLISH_KEY : ${{ secrets.GRADLE_PUBLISH_KEY }}
180+ GRADLE_PUBLISH_SECRET : ${{ secrets.GRADLE_PUBLISH_SECRET }}
104181 outputs :
105182 changepacks : ${{ steps.changepacks.outputs.changepacks }}
106183 release_assets_urls : ${{ steps.changepacks.outputs.release_assets_urls }}
184+
185+ # Java gate — builds the rust-jni-demo cdylib, runs the vespera-bridge unit
186+ # suite, publishes the bridge JAR to mavenLocal (so the demo-app Gradle
187+ # plugin can resolve kr.devfive:vespera-bridge), then runs the full
188+ # :demo-app:test suite (StreamingClosureStressTest + JNI dispatch tests)
189+ # across all three target host OSes.
190+ #
191+ # This is the ONLY thing that exercises Java in this repository, and it is
192+ # also what covers `crates/vespera_jni`: that crate is excluded from
193+ # tarpaulin (`#![cfg(not(tarpaulin_include))]`) because its symbols cannot
194+ # run without a JVM, so this job is its coverage — not the Rust line
195+ # coverage number.
196+ #
197+ # Runs unconditionally on every push/PR (matching the existing CI job's
198+ # style — no per-job paths-filter). The whole workflow already inherits
199+ # the workflow-level `paths-ignore` for docs-only changes.
200+ jni-e2e :
201+ name : JNI E2E (${{ matrix.os }})
202+ runs-on : ${{ matrix.os }}
203+ timeout-minutes : 25
204+ strategy :
205+ fail-fast : false
206+ matrix :
207+ os : [ubuntu-latest, windows-latest, macos-latest]
208+ steps :
209+ - uses : actions/checkout@v7
210+ - uses : actions/setup-java@v5
211+ with :
212+ distribution : ' temurin'
213+ java-version : ' 17'
214+ cache : ' gradle'
215+ - uses : actions-rust-lang/setup-rust-toolchain@v1
216+ - name : Build rust-jni-demo cdylib (release)
217+ # The vespera-bridge Gradle plugin's bundleNativeLib task copies
218+ # this cdylib from target/release into demo-app's resources, so it
219+ # must exist before `:demo-app:test` (processResources) runs.
220+ run : cargo build -p rust-jni-demo --release
221+ - name : Make gradlew executable (unix)
222+ if : runner.os != 'Windows'
223+ run : |
224+ chmod +x libs/vespera-bridge/gradlew
225+ chmod +x libs/vespera-bridge-gradle-plugin/gradlew
226+ chmod +x examples/rust-jni-demo/java/gradlew
227+ - name : Publish vespera-bridge Gradle plugin to mavenLocal
228+ # demo-app's plugins block resolves kr.devfive.vespera-bridge from
229+ # mavenLocal (settings.gradle.kts pluginManagement) — the plugin is
230+ # not on the Gradle Plugin Portal.
231+ shell : bash
232+ working-directory : libs/vespera-bridge-gradle-plugin
233+ run : ./gradlew publishToMavenLocal --console=plain --no-daemon
234+ - name : Run vespera-bridge unit tests
235+ # The bridge's own suite: wire codec, zero-copy header reader, direct
236+ # buffer pool, hop-by-hop header policy, dispatch-mode resolvers and
237+ # the Spring autoconfiguration. Until now the workflow only PUBLISHED
238+ # this library, so none of these ever ran in CI — the Rust side was
239+ # gated while ~2 kLOC of Java shipped untested. Runs before the publish
240+ # step so a broken bridge fails here rather than as a confusing
241+ # demo-app failure. Bench knobs are NOT propagated, so the
242+ # `vespera.bench`-gated PerfAllocBench cases stay skipped.
243+ shell : bash
244+ working-directory : libs/vespera-bridge
245+ run : ./gradlew test --console=plain --no-daemon
246+ - name : Publish vespera-bridge to mavenLocal
247+ # demo-app resolves kr.devfive:vespera-bridge from mavenLocal at the
248+ # version declared in libs/vespera-bridge/build.gradle.kts (see
249+ # examples/rust-jni-demo/java/demo-app/build.gradle.kts — the version
250+ # is read from that file, never pinned, so bumping the bridge cannot
251+ # silently fall back to the last release on Maven Central).
252+ shell : bash
253+ working-directory : libs/vespera-bridge
254+ run : ./gradlew publishToMavenLocal --console=plain --no-daemon
255+ - name : Run demo-app JNI E2E tests
256+ # Includes StreamingClosureStressTest (1000 × 1 MiB SHA256
257+ # bidirectional round-trip). Bench knobs are NOT propagated —
258+ # gated bench tests stay skipped in CI.
259+ shell : bash
260+ working-directory : examples/rust-jni-demo/java
261+ run : ./gradlew :demo-app:test --console=plain --no-daemon
262+ - name : Merge Java coverage
263+ # Regenerates the bridge's JaCoCo report now that demo-app has run, so
264+ # it also credits the classes only reachable with a loaded cdylib — the
265+ # JNI wrappers and the Spring proxy, which the unit suite cannot drive.
266+ # The report task reads demo-app/build/jacoco/test.exec when present.
267+ shell : bash
268+ working-directory : libs/vespera-bridge
269+ run : ./gradlew jacocoTestReport --console=plain --no-daemon
270+ - name : Upload Java test results and coverage
271+ if : always()
272+ uses : actions/upload-artifact@v7
273+ with :
274+ name : jni-e2e-${{ matrix.os }}-test-results
275+ path : |
276+ examples/rust-jni-demo/java/demo-app/build/test-results/test/*.xml
277+ libs/vespera-bridge/build/test-results/test/*.xml
278+ libs/vespera-bridge/build/reports/jacoco/test/jacocoTestReport.xml
0 commit comments