From 243926563e5f2c78c2b43fd6b36424f0695345b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Sat, 26 Sep 2026 13:21:01 +0200 Subject: [PATCH] test: verify real SpotBugs annotations on the validation fork Use the candidate merger and an actual SpotBugs finding report alongside a clean sibling. Point the preserved source bases at the committed fixture and require successful Code Scanning processing. This fork-only harness replaces the build workflow to avoid another full reactor build; it has read-only content access and no publishing or deployment steps. Co-Authored-By: Claude Opus 5.5 --- .../com/example/Example.java | 2 + .github/workflows/verify.yml | 331 ++---------------- 2 files changed, 25 insertions(+), 308 deletions(-) create mode 100644 .github/tests/analysis/annotation-source/com/example/Example.java diff --git a/.github/tests/analysis/annotation-source/com/example/Example.java b/.github/tests/analysis/annotation-source/com/example/Example.java new file mode 100644 index 000000000..f86e5004a --- /dev/null +++ b/.github/tests/analysis/annotation-source/com/example/Example.java @@ -0,0 +1,2 @@ +package com.example; +public class Example { public static void main(String[] args) { String s = null; System.out.println(s.length()); } } diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index daab13733..c782c2c60 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -1,321 +1,36 @@ -name: verify +name: verify-annotation-fixture on: pull_request: - -# Code Scanning needs write access to upload SARIF results for inline annotations. permissions: contents: read security-events: write - jobs: - analysis-regression: + annotations: runs-on: ubuntu-24.04 - permissions: - contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Install validation dependencies - run: sudo apt-get update && sudo apt-get install --yes jq libxml2-utils - - name: Check report gates and merge semantics - run: | - bash .github/tests/analysis/run.sh - bash .github/tests/analysis/mutations.sh - bash .github/tests/analysis/scope.sh - - # Fast, early-fail lint lane: PMD + Checkstyle (+ CPD). Turns red in a few - # minutes on any violation, independent of the long build below, so a stray - # PMD/Checkstyle issue is reported immediately rather than after `verify`. - # - # `compile` is in the same invocation as the analysis goals: PMD's - # type-resolving rules (e.g. InvalidLogMessageFormat on the SLF4J - # trailing-Throwable idiom) need Tycho's aux-classpath, which a fresh `mvn` - # does not inherit from a prior step's target/classes. - # - # Preserve Maven failures and validate every expected report before counting findings. - lint: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 # need the PR base commit to diff the changed modules - - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 - with: - distribution: 'temurin' - java-version: '21' - - name: Set up Workspace Environment Variable - run: echo "WORKSPACE=${{ github.workspace }}" >> $GITHUB_ENV - - name: Restore Maven dependency cache - # Restore-only, mirroring snapshot.yml's producer cache exactly (path and - # key are hashed into the cache version — see the maven-verify step). - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-publish-${{ hashFiles('**/pom.xml', '**/*.target') }} - restore-keys: ${{ runner.os }}-maven-publish- - - - name: Install XML report validator - run: sudo apt-get update && sudo apt-get install --yes libxml2-utils - - - name: Scope static analysis to the PR's changed modules - # Injects pmd/cpd/checkstyle skip properties into unchanged module poms and - # exports LINT_SCOPE_ARGS (-pl -am) so only the changed modules and - # their upstream deps build (skip-injected deps compile for PMD's type - # resolution but are not analysed). Build/config change -> full scan, full - # reactor. pull_request only; master/snapshot run a full scan. - run: bash .github/scripts/compute-analysis-skip.sh "${{ github.event.pull_request.base.sha }}" lint - - - name: PMD + Checkstyle reports (SARIF) - # PMD: SarifRenderer FQCN — emits pmd.sarif.json AND keeps pmd.xml. - # Checkstyle: output.format=sarif — SARIF content in checkstyle-result.xml. - # CPD is excluded here: the global -Dformat flag uses PMD's Renderer - # hierarchy and would ClassCastException CPD's CPDReportRenderer. - # `compile` stays: PMD's type-resolving rules need Tycho's aux-classpath. - # jgit.dirtyWorkingTree=ignore: the scope step edits poms (see the spotbugs - # lane for the rationale; this job releases nothing). - # Skipped entirely when the scope step kept no modules (e.g. a docs-only - # PR): every module would carry the skip properties, so the compile - # output would be unused. The gate below relaxes on the same condition. - if: env.LINT_KEPT != '0' - run: | - mvn -T 2C -f ./ddk-parent/pom.xml ${LINT_SCOPE_ARGS:-} --batch-mode --fail-at-end \ - compile \ - pmd:pmd checkstyle:checkstyle \ - -Dformat=net.sourceforge.pmd.renderers.SarifRenderer \ - -Dcheckstyle.output.format=sarif \ - -Djgit.dirtyWorkingTree=ignore - - - name: CPD report (separate invocation — no SARIF support) - # CPD has no SARIF renderer; emits cpd.xml only. Run standalone so the - # PMD -Dformat flag isn't in scope. - # No `compile`: CPD is token-based over src/ and needs neither bytecode - # nor the target platform — cpd.xml is identical with and without a - # compile pass. - # NOTE: the CPD token threshold is governed by pmd.cpd.min in - # ddk-parent/pom.xml. - # No jgit flag needed: a direct goal invocation runs no lifecycle, so the - # build-qualifier's dirty-tree check never executes here. - if: env.LINT_KEPT != '0' - run: | - mvn -T 2C -f ./ddk-parent/pom.xml ${LINT_SCOPE_ARGS:-} --batch-mode --fail-at-end \ - pmd:cpd-check - - - name: Merge per-module SARIFs (PMD + Checkstyle) - if: always() - # Merge only expected module reports into one run per analyzer. - run: | - set -euo pipefail - if [ "${LINT_KEPT:-}" = "0" ]; then - echo "Scope contains no analysable modules — nothing to lint." - exit 0 - fi - source .github/scripts/sarif.sh - source_modules=$(sarif_source_modules) - merge_sarif pmd.sarif.json .sarif-merged/pmd.sarif "${LINT_EXPECT_REPORTS:-$source_modules}" PMD - merge_sarif checkstyle-result.xml .sarif-merged/checkstyle.sarif "${LINT_EXPECT_REPORTS:-$source_modules}" Checkstyle - - - name: Gate on PMD / CPD / Checkstyle violations - # Require successful reports from every expected module before counting findings. - run: | - set -euo pipefail - if [ "${LINT_KEPT:-}" = "0" ]; then - echo "Scope contains no analysable modules — nothing to lint." - exit 0 - fi - source .github/scripts/sarif.sh - source_modules=$(sarif_source_modules) - cpd_reports=() - for mod in ${LINT_EXPECT_REPORTS:-$source_modules}; do - validate_sarif "${mod}/target/pmd.sarif.json" PMD - validate_sarif "${mod}/target/checkstyle-result.xml" Checkstyle - cpd_reports+=("${mod}/target/cpd.xml") - done - validate_sarif .sarif-merged/pmd.sarif PMD - validate_sarif .sarif-merged/checkstyle.sarif Checkstyle - if [ ${#cpd_reports[@]} -eq 0 ]; then - echo "::error::No expected CPD reports — the analysis silently failed." - exit 1 - fi - sarif_total=$(jq -s '[.[].runs[].results[]] | length' \ - .sarif-merged/pmd.sarif .sarif-merged/checkstyle.sarif) - cpd_total=0 - for report in "${cpd_reports[@]}"; do - count=$(cpd_count "$report") - cpd_total=$((cpd_total + count)) - done - echo "PMD/Checkstyle SARIF violations: $sarif_total" - echo "CPD duplications: $cpd_total" - if [ "$sarif_total" != "0" ] || [ "$cpd_total" != "0" ]; then - echo "::error::Static analysis found violations (PMD/CPD/Checkstyle)." - exit 1 - fi - - - name: Upload PMD/Checkstyle SARIF to Code Scanning - # Skip when nothing was scanned (e.g. a docs-only PR -> all modules skipped): - # an empty .sarif-merged would otherwise fail upload-sarif ("No SARIF files"). - if: ${{ always() && hashFiles('.sarif-merged/pmd.sarif', '.sarif-merged/checkstyle.sarif') != '' }} - # Annotation-only, never the gate: a fork PR gets a read-only token and - # upload-sarif 403s, which must not red an otherwise-clean lane. - continue-on-error: true - uses: github/codeql-action/upload-sarif@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3.35.4 - with: - sarif_file: .sarif-merged - category: lint - - # SpotBugs is the slow critical-path analysis (the experiments' durable - # finding), so it runs in its own parallel lane and never delays `lint`. - spotbugs: - runs-on: ubuntu-24.04 - env: - MAVEN_OPTS: -Xmx4g - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: - fetch-depth: 0 # need the PR base commit to diff the changed modules - - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 - with: - distribution: 'temurin' - java-version: '21' - - name: Set up Workspace Environment Variable - run: echo "WORKSPACE=${{ github.workspace }}" >> $GITHUB_ENV - - name: Restore Maven dependency cache - # Restore-only, mirroring snapshot.yml's producer cache exactly (path and - # key are hashed into the cache version — see the maven-verify step). - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-publish-${{ hashFiles('**/pom.xml', '**/*.target') }} - restore-keys: ${{ runner.os }}-maven-publish- - - - name: Scope SpotBugs to the PR's changed modules - # Injects true> into unchanged module poms so their analysis is - # skipped, and exports SPOTBUGS_SCOPE_ARGS (-pl -am) so only the - # changed modules and their upstream deps build at all (skip-injected deps - # compile for the aux-classpath but are not analysed). A build/config change -> - # full scan, full reactor. pull_request only; master/snapshot run a full scan. - run: bash .github/scripts/compute-analysis-skip.sh "${{ github.event.pull_request.base.sha }}" spotbugs - - - name: SpotBugs report (SARIF) - # sarifOutput=true emits spotbugsSarif.json (also writes spotbugsXml.xml). - # jgit.dirtyWorkingTree=ignore: the scope step intentionally edits poms, so the - # working tree is dirty here; this job releases nothing, so we tell Tycho's jgit - # build-qualifier to use the last commit's timestamp instead of failing (the - # repo keeps jgit.dirtyWorkingTree=error for maven-verify / releases). - # Skipped entirely when the scope step kept no modules (e.g. a docs-only - # PR): every module would carry spotbugs.skip, so the compile output - # would be unused. The gate below relaxes on the same condition. - if: env.SPOTBUGS_KEPT != '0' - run: | - mvn -T 2C -f ./ddk-parent/pom.xml ${SPOTBUGS_SCOPE_ARGS:-} --batch-mode --fail-at-end \ - compile \ - spotbugs:spotbugs \ - -Dspotbugs.sarifOutput=true \ - -Djgit.dirtyWorkingTree=ignore - - - name: Merge per-module SpotBugs SARIFs - if: always() - run: | - set -euo pipefail - if [ "${SPOTBUGS_KEPT:-}" = "0" ]; then - echo "Scope contains no analysable modules — nothing to scan." - exit 0 - fi - source .github/scripts/sarif.sh - source_modules=$(sarif_source_modules) - merge_sarif spotbugsSarif.json .sarif-merged/spotbugs.sarif "${SPOTBUGS_EXPECT_REPORTS:-$source_modules}" SpotBugs - - - name: Gate on SpotBugs violations - # Require successful reports from every expected module before counting findings. + fetch-depth: 0 + - name: Merge a real SpotBugs finding with a clean sibling + shell: bash run: | set -euo pipefail - if [ "${SPOTBUGS_KEPT:-}" = "0" ]; then - echo "Scope contains no analysable modules — nothing to scan." - exit 0 - fi source .github/scripts/sarif.sh - source_modules=$(sarif_source_modules) - for mod in ${SPOTBUGS_EXPECT_REPORTS:-$source_modules}; do - validate_sarif "${mod}/target/spotbugsSarif.json" SpotBugs - done + mkdir -p .validation-fixture/a/target .validation-fixture/b/target + cp .github/tests/analysis/fixtures/spotbugs-clean.json .validation-fixture/a/target/spotbugsSarif.json + jq --arg root "file://${GITHUB_WORKSPACE}/.github/tests/analysis/annotation-source/" \ + '.runs[].originalUriBaseIds[] |= (.uri = $root)' \ + .github/tests/analysis/fixtures/spotbugs-finding.json > .validation-fixture/b/target/spotbugsSarif.json + merge_sarif spotbugsSarif.json .sarif-merged/spotbugs.sarif '.validation-fixture/a .validation-fixture/b' SpotBugs validate_sarif .sarif-merged/spotbugs.sarif SpotBugs - sb_total=$(jq '[.runs[].results[]] | length' .sarif-merged/spotbugs.sarif) - echo "SpotBugs SARIF violations: $sb_total" - if [ "$sb_total" != "0" ]; then - echo "::error::SpotBugs found violations." - exit 1 - fi - - - name: Upload SpotBugs SARIF to Code Scanning - # Skip when nothing was scanned (e.g. a docs-only PR -> all modules skipped): - # an empty .sarif-merged would otherwise fail upload-sarif ("No SARIF files"). - if: ${{ always() && hashFiles('.sarif-merged/spotbugs.sarif') != '' }} - # Annotation-only, never the gate: a fork PR gets a read-only token and - # upload-sarif 403s, which must not red an otherwise-clean lane. - continue-on-error: true - uses: github/codeql-action/upload-sarif@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3.35.4 - with: - sarif_file: .sarif-merged - category: spotbugs - - line-endings: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Check LF line endings - run: bash .github/scripts/check-line-endings.sh - - # Build + tests only. Static analysis now lives in the `lint` and `spotbugs` - # jobs, so the redundant checkstyle/pmd/spotbugs goals are dropped from here — - # this is the wall-clock long pole and no longer re-runs analysis. - # No `-T 2C`: tests are not known to pass reliably under reactor parallelism. - maven-verify: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - # Full history: Tycho's jgit build-qualifier derives each bundle's qualifier - # from the last commit touching it. A shallow clone truncates that history, so - # every bundle falls back to the HEAD (merge-ref) timestamp — inflating all - # qualifiers and making compare-version-with-baselines fail on unchanged - # bundles ("Only qualifier changed"). snapshot.yml already fetches full history. - fetch-depth: 0 - - name: Set up JDK 21 - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 - with: - distribution: 'temurin' - java-version: '21' - - name: Log Maven version - run: mvn --version - - name: Set up Workspace Environment Variable - run: echo "WORKSPACE=${{ github.workspace }}" >> $GITHUB_ENV - - name: Restore Maven dependency cache - # Restore-only: PR scopes cannot share caches with each other, so per-PR - # saves are dead weight that evicts the useful master-scoped caches - # (10 GB repo budget). The producer is snapshot.yml on master pushes - # (Linux-maven-publish-*). Path and key must mirror snapshot.yml exactly: - # the literal path spec is hashed into the cache *version*, so any - # variation (~/.m2 vs /home/runner/.m2) makes its caches unmatchable. - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-publish-${{ hashFiles('**/pom.xml', '**/*.target') }} - restore-keys: ${{ runner.os }}-maven-publish- - - name: Drop cached p2 metadata for the DDK update site - # The restored blob persists Tycho's HTTP cache (~/.m2/repository/.cache/tycho). - # Tycho's cache-first transport never revalidates cached 404/301 entries, and - # p2/releases/latest + p2/snapshots/latest are moving pointers, so a stale blob - # silently disables the compare-version-with-baselines gate (the mojo has no - # "baseline not found" branch and passes vacuously). Deleting only the DDK hosts - # forces a fresh metadata fetch (a few KB) while keeping eclipse.org metadata and - # all downloaded artifacts cached. - run: rm -rf ~/.m2/repository/.cache/tycho/https/dsldevkit.github.io ~/.m2/repository/.cache/tycho/https/ddk.tools.avaloq.com - - name: Build with Maven within a virtual X Server Environment - run: xvfb-run mvn clean verify -f ./ddk-parent/pom.xml --batch-mode --fail-at-end - - name: Fail on missing surefire reports - if: always() - run: bash .github/scripts/check-surefire-reports.sh - - name: Archive Tycho Surefire Plugin - if: ${{ failure() }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: tycho-surefire-plugin - path: ${{ env.GITHUB_WORKSPACE }}/com.avaloq.tools.ddk.xtext.test/target/work/data/.metadata/.log + test "$(jq '[.runs[].results[]] | length' .sarif-merged/spotbugs.sarif)" = 2 + jq -e 'all(.runs[].results[].locations[].physicalLocation.artifactLocation; + .uri == ".github/tests/analysis/annotation-source/com/example/Example.java" + and (has("uriBaseId") | not))' .sarif-merged/spotbugs.sarif + git rev-parse HEAD + - name: Upload real findings and require successful processing + uses: github/codeql-action/upload-sarif@7fd177fa680c9881b53cdab4d346d32574c9f7f4 + with: + sarif_file: .sarif-merged/spotbugs.sarif + category: validation-real-spotbugs + wait-for-processing: true