Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/bash
#
# Step C of the CI gate — the TEST gate.
#
# Wired in the workflow with `if: always()` so it evaluates the test results
# even when the scan step already failed the job on a red quality gate (and
# vice-versa). Fails on any real surefire/failsafe failure or error. Flaky tests
# that passed on retry are recorded as <flakyFailure> with failures="0"/
# errors="0", so they do NOT trip this gate.
#
set -uo pipefail

SUMMARY="${GITHUB_STEP_SUMMARY:-/dev/null}"

if $SKIP_TESTS; then
echo "~> SKIP_TESTS=true — nothing to gate."
echo "### ⏭️ Unit test gate — skipped (SKIP_TESTS=true)" >> "$SUMMARY"
exit 0
fi

.github/github-tools/mvn.test.report.generate || true

# reports with at least one real failure or error (flaky retries carry failures="0")
FAILED_REPORTS=$(find . -type f \( -path '*/surefire-reports/*.xml' -o -path '*/failsafe-reports/*.xml' \) -print0 2>/dev/null \
| xargs -0 -r grep -lE 'failures="[1-9][0-9]*"|errors="[1-9][0-9]*"' 2>/dev/null)

if [ -n "$FAILED_REPORTS" ]; then
echo "::error title=Unit test gate FAILED::One or more tests failed. See the table in the job summary and the offending modules below."
{
echo "### ❌ Unit test gate — FAILED"
echo ""
echo "| Test suite | Module (report path) | Failures | Errors |"
echo "|---|---|---:|---:|"
} >> "$SUMMARY"

# one row per failing report, with the suite name and counts pulled from the XML
while IFS= read -r f; do
[ -z "$f" ] && continue
line=$(grep -oE '<testsuite [^>]*>' "$f" | head -1)
name=$(printf '%s' "$line" | sed -nE 's/.*[[:space:]]name="([^"]*)".*/\1/p')
fails=$(printf '%s' "$line" | sed -nE 's/.*[[:space:]]failures="([0-9]+)".*/\1/p')
errs=$(printf '%s' "$line" | sed -nE 's/.*[[:space:]]errors="([0-9]+)".*/\1/p')
mod=$(printf '%s' "$f" | sed -E 's#/target/(surefire|failsafe)-reports/.*##')
echo "| \`${name:-?}\` | \`${mod:-$f}\` | ${fails:-?} | ${errs:-?} |" >> "$SUMMARY"
# per-run log annotations naming the failing test classes
echo "::error file=$f::Failing suite ${name:-?} (failures=${fails:-?}, errors=${errs:-?})"
done <<< "$FAILED_REPORTS"

echo "" >> "$SUMMARY"
echo "> ℹ️ The Sonar analysis was still submitted — see the **Sonar quality gate** step." >> "$SUMMARY"
exit 1
fi

# Presence guard: a -fae compile-skip produces NO xml for the skipped modules,
# which would otherwise look like "no failures". Require at least one report.
REPORT_COUNT=$(find . -type f \( -path '*/surefire-reports/*.xml' -o -path '*/failsafe-reports/*.xml' \) 2>/dev/null | wc -l)
if [ "$REPORT_COUNT" -eq 0 ]; then
echo "::error title=Unit test gate FAILED::No surefire/failsafe reports were produced — tests did not run (likely a compile failure). See the 'Build & run tests' step."
{
echo "### ❌ Unit test gate — NO TESTS RAN"
echo ""
echo "No surefire/failsafe reports were found, so tests never executed (likely a compile failure). See the **Build & run tests** step."
} >> "$SUMMARY"
exit 1
fi

echo "### ✅ Unit test gate — PASSED ($REPORT_COUNT report files scanned)" >> "$SUMMARY"
echo "All tests passed ($REPORT_COUNT report files scanned) and the Sonar scan was submitted."
exit 0
76 changes: 76 additions & 0 deletions .github/scan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/bin/bash
#
# Step B of the CI gate — submit the SonarCloud analysis and BLOCK on the
# quality gate of the Compute Engine task THIS run creates.
#
# Wired in the workflow with `if: always()` so a failed test/build step can
# never prevent the scan (requirement: always update Sonar regardless of test
# results). `sonar.qualitygate.wait=true` ties the pass/fail decision to this
# run's ceTaskId (recorded in target/sonar/report-task.txt), so:
# - a red quality gate exits non-zero -> the job fails;
# - the decision can never be satisfied by a stale server-side analysis.
#
set -uo pipefail

SUMMARY="${GITHUB_STEP_SUMMARY:-/dev/null}"
SCAN_LOG="sonar-scan.log"

if $SKIP_SCANS; then
echo "~> SKIP_SCANS=true — skipping Sonar scan."
echo "### ⏭️ Sonar quality gate — skipped (SKIP_SCANS=true)" >> "$SUMMARY"
exit 0
fi

# Make this step self-sufficient when Step A was skipped (SKIP_TESTS) and the
# poms were not version-set yet. Harmless (and quiet) when already set.
mvn versions:set -DnewVersion="$ARTIFACT_VERSION" -q || true

mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:5.0.0.4389:sonar \
-Dsonar.verbose=true \
-Dsonar.qualitygate.wait=true \
-Dsonar.qualitygate.timeout=600 \
${SONAR_PROJECT_KEY:+-Dsonar.projectKey="$SONAR_PROJECT_KEY"} \
${SONAR_ORG:+-Dsonar.organization="$SONAR_ORG"} \
2>&1 | tee "$SCAN_LOG"
SONAR_RC="${PIPESTATUS[0]}"

# Dashboard URL of THIS run's analysis (if the scan got far enough to write it).
RT=$(find . -path '*/target/sonar/report-task.txt' 2>/dev/null | head -n1)
DASH=""
[ -n "$RT" ] && DASH=$(grep -E '^dashboardUrl=' "$RT" | head -1 | cut -d= -f2-)

# --- case 1: the scan did not run / produced no analysis -> stale-analysis guard
if [ -z "$RT" ]; then
echo "::error title=Sonar scan did not run::No report-task.txt was produced — refusing to treat the quality gate as passed (stale-analysis guard)."
{
echo "### ❌ Sonar quality gate — SCAN DID NOT RUN"
echo ""
echo "No \`report-task.txt\` was produced, so there is **no fresh analysis** to gate on. Failing rather than passing on a possibly stale server-side result."
} >> "$SUMMARY"
exit "$([ "$SONAR_RC" -ne 0 ] && echo "$SONAR_RC" || echo 1)"
fi

# --- case 2: the analysis ran but the quality gate is RED
if grep -q "QUALITY GATE STATUS: FAILED" "$SCAN_LOG" || [ "$SONAR_RC" -ne 0 ]; then
echo "::error title=Sonar quality gate FAILED::The SonarCloud quality gate did not pass for this run's analysis. Details: ${DASH:-see the scan log}"
{
echo "### ❌ Sonar quality gate — FAILED"
echo ""
[ -n "$DASH" ] && echo "📊 [View the failing quality gate on SonarCloud]($DASH)"
echo ""
echo "Failing conditions (from the scan log):"
echo '```'
grep -iE 'QUALITY GATE STATUS|condition|new coverage|duplicated|reliability|security|maintainability' "$SCAN_LOG" | tail -n 30 || true
echo '```'
} >> "$SUMMARY"
exit "$([ "$SONAR_RC" -ne 0 ] && echo "$SONAR_RC" || echo 1)"
fi

# --- case 3: analysis ran and the quality gate passed
{
echo "### ✅ Sonar quality gate — PASSED"
echo ""
[ -n "$DASH" ] && echo "📊 [View the analysis on SonarCloud]($DASH)"
} >> "$SUMMARY"
echo "~> Quality gate PASSED for this run's analysis. ${DASH}"
exit 0
53 changes: 0 additions & 53 deletions .github/test-and-scan.sh

This file was deleted.

56 changes: 56 additions & 0 deletions .github/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash
#
# Step A of the CI gate — build the reactor and run the tests (with coverage).
#
# This step NEVER fails on a test failure (-fae + -Dmaven.test.failure.ignore=true):
# the whole reactor is exercised so (a) the scan step always has something to
# analyse and (b) the gate step can evaluate every module's reports. A non-zero
# exit here therefore means a BUILD error (compile / plugin / dependency) — NOT a
# test failure. Test failures are surfaced later by the "Unit test gate" step.
#
set -uo pipefail

SUMMARY="${GITHUB_STEP_SUMMARY:-/dev/null}"

# --- keep the reactor version consistent with the build job ---
PARENT_VERSION=$(mvn help:evaluate -Dexpression=project.parent.version -q -DforceStdout)
if [[ "$PARENT_VERSION" == *"-PR"* ]]; then
echo "~> Parent PR version detected ($PARENT_VERSION), purging parent dependency cache"
mvn dependency:purge-local-repository \
-DmanualInclude=org.entando:entando-maven-root \
-DreResolve=false \
-DactTransitively=false
fi

mvn versions:set -DnewVersion="$ARTIFACT_VERSION"

if $SKIP_TESTS; then
echo "~> SKIP_TESTS=true — skipping test execution."
echo "### ⏭️ Build & tests — skipped (SKIP_TESTS=true)" >> "$SUMMARY"
exit 0
fi

# -fae: fail at end (keep exercising the reactor after a failing module).
# -Dmaven.test.failure.ignore=true: a failing/flaky test must not stop the build,
# so the scan step (run with if: always()) always executes and the test gate is
# re-enforced by .github/gate.sh.
mvn -B -fae -Dmaven.test.failure.ignore=true \
-Ppre-deployment-verification \
org.jacoco:jacoco-maven-plugin:prepare-agent \
verify \
org.jacoco:jacoco-maven-plugin:report
RC=$?

if [ "$RC" -ne 0 ]; then
echo "::error title=Build failed::Compilation/plugin/dependency error in the reactor (exit $RC). NOTE: test failures alone do NOT fail this step — check the 'Unit test gate' step for those."
{
echo "### ❌ Build & tests — BUILD ERROR"
echo ""
echo "Maven exited with code \`$RC\` **before** tests could complete — this is a compilation, plugin or dependency error, **not** a test failure."
echo "See the **Build & run tests** step log for the failing module."
} >> "$SUMMARY"
exit "$RC"
fi

echo "### ✅ Build & tests — completed (results evaluated by the Unit test gate)" >> "$SUMMARY"
exit 0
40 changes: 27 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ jobs:
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
Expand All @@ -44,7 +44,7 @@ jobs:
gh.job.outputVar SKIP_TESTS

- name: Cache Maven packages
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
Expand All @@ -54,10 +54,13 @@ jobs:
run: .github/build.sh

- name: Submit Dependency Snapshot
# NOTE: still runs on node20 even at its latest (v5); the maintainer has not
# shipped a node24 build yet. GitHub runs it on node24 via the compatibility
# shim, so it keeps working — bump the major once a node24 release lands.
uses: advanced-security/maven-dependency-submission-action@v4

- name: Save the build output
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: target
overwrite: true
Expand All @@ -84,24 +87,26 @@ jobs:
SONAR_URL: ${{ vars.SONAR_URL }}

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
cache: maven

- name: Cache SonarQube packages
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar

- name: Restore the build output
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: target
path: .
Expand All @@ -110,12 +115,21 @@ jobs:
id: configure
run: if [ -f ".github/configure" ]; then . .github/configure "test-and-scan"; fi

- name: Test and Scan
run: .github/test-and-scan.sh
- name: Build & run tests
id: tests
run: .github/test.sh

- name: Sonar quality gate
if: always()
run: .github/scan.sh

- name: Unit test gate
if: always()
run: .github/gate.sh

- name: Save the test report
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: tests-report
compression-level: 0
Expand All @@ -134,10 +148,10 @@ jobs:
needs: [build, test-and-scan]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Restore the build output
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: target
path: .
Expand Down
Loading
Loading