|
29 | 29 | fail-fast: false |
30 | 30 | matrix: |
31 | 31 | include: |
32 | | - - java: '8' |
33 | | - runner: macos-26-intel |
34 | | - arch: x86_64 |
35 | 32 | - java: '17' |
36 | 33 | runner: macos-26 |
37 | 34 | arch: aarch64 |
|
57 | 54 | - name: Build |
58 | 55 | run: ./gradlew clean build --no-daemon |
59 | 56 |
|
60 | | - - name: Test with RocksDB engine |
61 | | - if: matrix.arch == 'x86_64' |
62 | | - run: ./gradlew :framework:testWithRocksDb --no-daemon |
63 | | - |
64 | 57 | build-ubuntu: |
65 | 58 | name: Build ubuntu24 (JDK 17 / aarch64) |
66 | 59 | if: ${{ github.event_name == 'pull_request' || inputs.job == 'all' || inputs.job == 'ubuntu' }} |
@@ -177,7 +170,236 @@ jobs: |
177 | 170 | debian11-x86_64-gradle- |
178 | 171 |
|
179 | 172 | - name: Build |
180 | | - run: ./gradlew clean build --no-daemon |
| 173 | + run: ./gradlew clean build --no-daemon --no-build-cache |
181 | 174 |
|
182 | 175 | - name: Test with RocksDB engine |
183 | | - run: ./gradlew :framework:testWithRocksDb --no-daemon |
| 176 | + run: ./gradlew :framework:testWithRocksDb --no-daemon --no-build-cache |
| 177 | + |
| 178 | + - name: Generate module coverage reports |
| 179 | + run: ./gradlew jacocoTestReport --no-daemon |
| 180 | + |
| 181 | + - name: Upload PR coverage reports |
| 182 | + uses: actions/upload-artifact@v6 |
| 183 | + with: |
| 184 | + name: jacoco-coverage-pr |
| 185 | + path: | |
| 186 | + **/build/reports/jacoco/test/jacocoTestReport.xml |
| 187 | + if-no-files-found: error |
| 188 | + |
| 189 | + coverage-base: |
| 190 | + name: Coverage Base (JDK 8 / x86_64) |
| 191 | + if: ${{ github.event_name == 'pull_request' }} |
| 192 | + runs-on: ubuntu-latest |
| 193 | + timeout-minutes: 60 |
| 194 | + container: |
| 195 | + image: eclipse-temurin:8-jdk # base image is Debian 11 (Bullseye) |
| 196 | + defaults: |
| 197 | + run: |
| 198 | + shell: bash |
| 199 | + env: |
| 200 | + GRADLE_USER_HOME: /github/home/.gradle |
| 201 | + permissions: |
| 202 | + contents: read |
| 203 | + |
| 204 | + steps: |
| 205 | + - name: Checkout code |
| 206 | + uses: actions/checkout@v5 |
| 207 | + with: |
| 208 | + ref: ${{ github.event.pull_request.base.sha }} |
| 209 | + |
| 210 | + - name: Install dependencies (Debian + build tools) |
| 211 | + run: | |
| 212 | + set -euxo pipefail |
| 213 | + apt-get update |
| 214 | + apt-get install -y git wget unzip build-essential curl jq |
| 215 | +
|
| 216 | + - name: Cache Gradle packages |
| 217 | + uses: actions/cache@v4 |
| 218 | + with: |
| 219 | + path: | |
| 220 | + /github/home/.gradle/caches |
| 221 | + /github/home/.gradle/wrapper |
| 222 | + key: coverage-base-x86_64-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} |
| 223 | + restore-keys: | |
| 224 | + coverage-base-x86_64-gradle- |
| 225 | +
|
| 226 | + - name: Build (base) |
| 227 | + run: ./gradlew clean build --no-daemon --no-build-cache |
| 228 | + |
| 229 | + - name: Test with RocksDB engine (base) |
| 230 | + run: ./gradlew :framework:testWithRocksDb --no-daemon --no-build-cache |
| 231 | + |
| 232 | + - name: Generate module coverage reports (base) |
| 233 | + run: ./gradlew jacocoTestReport --no-daemon |
| 234 | + |
| 235 | + - name: Upload base coverage reports |
| 236 | + uses: actions/upload-artifact@v6 |
| 237 | + with: |
| 238 | + name: jacoco-coverage-base |
| 239 | + path: | |
| 240 | + **/build/reports/jacoco/test/jacocoTestReport.xml |
| 241 | + if-no-files-found: error |
| 242 | + |
| 243 | + coverage-gate: |
| 244 | + name: Coverage Gate |
| 245 | + needs: [docker-build-debian11, coverage-base] |
| 246 | + if: ${{ github.event_name == 'pull_request' }} |
| 247 | + runs-on: ubuntu-latest |
| 248 | + timeout-minutes: 10 |
| 249 | + permissions: |
| 250 | + contents: read |
| 251 | + |
| 252 | + steps: |
| 253 | + - name: Checkout code |
| 254 | + uses: actions/checkout@v5 |
| 255 | + with: |
| 256 | + fetch-depth: 0 |
| 257 | + |
| 258 | + - name: Download base coverage reports |
| 259 | + uses: actions/download-artifact@v8 |
| 260 | + with: |
| 261 | + name: jacoco-coverage-base |
| 262 | + path: coverage/base |
| 263 | + |
| 264 | + - name: Download PR coverage reports |
| 265 | + uses: actions/download-artifact@v8 |
| 266 | + with: |
| 267 | + name: jacoco-coverage-pr |
| 268 | + path: coverage/pr |
| 269 | + |
| 270 | + - name: Collect coverage report paths |
| 271 | + id: collect-xml |
| 272 | + run: | |
| 273 | + BASE_XMLS=$(find coverage/base -name "jacocoTestReport.xml" | sort | paste -sd, -) |
| 274 | + PR_XMLS=$(find coverage/pr -name "jacocoTestReport.xml" | sort | paste -sd, -) |
| 275 | + if [ -z "$BASE_XMLS" ] || [ -z "$PR_XMLS" ]; then |
| 276 | + echo "Missing jacocoTestReport.xml files for base or PR." |
| 277 | + exit 1 |
| 278 | + fi |
| 279 | + echo "base_xmls=$BASE_XMLS" >> "$GITHUB_OUTPUT" |
| 280 | + echo "pr_xmls=$PR_XMLS" >> "$GITHUB_OUTPUT" |
| 281 | +
|
| 282 | + - name: Aggregate base coverage |
| 283 | + id: jacoco-base |
| 284 | + uses: madrapps/jacoco-report@v1.7.2 |
| 285 | + with: |
| 286 | + paths: ${{ steps.collect-xml.outputs.base_xmls }} |
| 287 | + token: ${{ secrets.GITHUB_TOKEN }} |
| 288 | + min-coverage-overall: 0 |
| 289 | + min-coverage-changed-files: 0 |
| 290 | + skip-if-no-changes: true |
| 291 | + title: '## Base Coverage Snapshot' |
| 292 | + update-comment: false |
| 293 | + |
| 294 | + - name: Aggregate PR coverage |
| 295 | + id: jacoco-pr |
| 296 | + uses: madrapps/jacoco-report@v1.7.2 |
| 297 | + with: |
| 298 | + paths: ${{ steps.collect-xml.outputs.pr_xmls }} |
| 299 | + token: ${{ secrets.GITHUB_TOKEN }} |
| 300 | + min-coverage-overall: 0 |
| 301 | + min-coverage-changed-files: 0 |
| 302 | + skip-if-no-changes: true |
| 303 | + title: '## PR Code Coverage Report' |
| 304 | + update-comment: false |
| 305 | + |
| 306 | + - name: Enforce coverage gates |
| 307 | + env: |
| 308 | + BASE_OVERALL_RAW: ${{ steps.jacoco-base.outputs.coverage-overall }} |
| 309 | + PR_OVERALL_RAW: ${{ steps.jacoco-pr.outputs.coverage-overall }} |
| 310 | + PR_CHANGED_RAW: ${{ steps.jacoco-pr.outputs.coverage-changed-files }} |
| 311 | + run: | |
| 312 | + set -euo pipefail |
| 313 | +
|
| 314 | + MIN_CHANGED=60 |
| 315 | + MAX_DROP=-0.1 |
| 316 | +
|
| 317 | + sanitize() { |
| 318 | + echo "$1" | tr -d ' %' |
| 319 | + } |
| 320 | + is_number() { |
| 321 | + [[ "$1" =~ ^-?[0-9]+([.][0-9]+)?$ ]] |
| 322 | + } |
| 323 | + compare_float() { |
| 324 | + # Usage: compare_float "<expr>" |
| 325 | + # Example: compare_float "1.2 >= -0.1" |
| 326 | + awk "BEGIN { if ($1) print 1; else print 0 }" |
| 327 | + } |
| 328 | +
|
| 329 | + # 1) Parse metrics from jacoco-report outputs |
| 330 | + BASE_OVERALL="$(sanitize "$BASE_OVERALL_RAW")" |
| 331 | + PR_OVERALL="$(sanitize "$PR_OVERALL_RAW")" |
| 332 | + PR_CHANGED="$(sanitize "$PR_CHANGED_RAW")" |
| 333 | +
|
| 334 | + if ! is_number "$BASE_OVERALL" || ! is_number "$PR_OVERALL"; then |
| 335 | + echo "Failed to parse coverage values: base='${BASE_OVERALL}', pr='${PR_OVERALL}'." |
| 336 | + exit 1 |
| 337 | + fi |
| 338 | +
|
| 339 | + # 2) Compare metrics against thresholds |
| 340 | + DELTA=$(awk -v pr="$PR_OVERALL" -v base="$BASE_OVERALL" 'BEGIN { printf "%.4f", pr - base }') |
| 341 | + DELTA_OK=$(compare_float "${DELTA} >= ${MAX_DROP}") |
| 342 | +
|
| 343 | + CHANGED_STATUS="SKIPPED (no changed coverage value)" |
| 344 | + CHANGED_OK=1 |
| 345 | + if [ -n "$PR_CHANGED" ] && [ "$PR_CHANGED" != "NaN" ]; then |
| 346 | + if ! is_number "$PR_CHANGED"; then |
| 347 | + echo "Failed to parse changed-files coverage: changed='${PR_CHANGED}'." |
| 348 | + exit 1 |
| 349 | + fi |
| 350 | + CHANGED_OK=$(compare_float "${PR_CHANGED} > ${MIN_CHANGED}") |
| 351 | + if [ "$CHANGED_OK" -eq 1 ]; then |
| 352 | + CHANGED_STATUS="PASS (> ${MIN_CHANGED}%)" |
| 353 | + else |
| 354 | + CHANGED_STATUS="FAIL (<= ${MIN_CHANGED}%)" |
| 355 | + fi |
| 356 | + fi |
| 357 | +
|
| 358 | + # 3) Output base metrics (always visible in logs + step summary) |
| 359 | + OVERALL_STATUS="PASS (>= ${MAX_DROP}%)" |
| 360 | + if [ "$DELTA_OK" -ne 1 ]; then |
| 361 | + OVERALL_STATUS="FAIL (< ${MAX_DROP}%)" |
| 362 | + fi |
| 363 | +
|
| 364 | + METRICS_TEXT=$(cat <<EOF |
| 365 | + Changed Files Coverage: ${PR_CHANGED}% |
| 366 | + PR Overall Coverage: ${PR_OVERALL}% |
| 367 | + Base Overall Coverage: ${BASE_OVERALL}% |
| 368 | + Delta (PR - Base): ${DELTA}% |
| 369 | + Changed Files Gate: ${CHANGED_STATUS} |
| 370 | + Overall Delta Gate: ${OVERALL_STATUS} |
| 371 | + EOF |
| 372 | + ) |
| 373 | +
|
| 374 | + echo "$METRICS_TEXT" |
| 375 | +
|
| 376 | + { |
| 377 | + echo "### Coverage Gate Metrics" |
| 378 | + echo "" |
| 379 | + echo "- Changed Files Coverage: ${PR_CHANGED}%" |
| 380 | + echo "- PR Overall Coverage: ${PR_OVERALL}%" |
| 381 | + echo "- Base Overall Coverage: ${BASE_OVERALL}%" |
| 382 | + echo "- Delta (PR - Base): ${DELTA}%" |
| 383 | + echo "- Changed Files Gate: ${CHANGED_STATUS}" |
| 384 | + echo "- Overall Delta Gate: ${OVERALL_STATUS}" |
| 385 | + } >> "$GITHUB_STEP_SUMMARY" |
| 386 | +
|
| 387 | + # 4) Decide CI pass/fail |
| 388 | + if [ "$DELTA_OK" -ne 1 ]; then |
| 389 | + echo "Coverage gate failed: overall coverage dropped more than 0.1%." |
| 390 | + echo "base=${BASE_OVERALL}% pr=${PR_OVERALL}% delta=${DELTA}%" |
| 391 | + exit 1 |
| 392 | + fi |
| 393 | +
|
| 394 | + if [ -z "$PR_CHANGED" ] || [ "$PR_CHANGED" = "NaN" ]; then |
| 395 | + echo "No changed-files coverage value detected, skip changed-files gate." |
| 396 | + exit 0 |
| 397 | + fi |
| 398 | +
|
| 399 | + if [ "$CHANGED_OK" -ne 1 ]; then |
| 400 | + echo "Coverage gate failed: changed files coverage must be > 60%." |
| 401 | + echo "changed=${PR_CHANGED}%" |
| 402 | + exit 1 |
| 403 | + fi |
| 404 | +
|
| 405 | + echo "Coverage gates passed." |
0 commit comments