|
| 1 | +# parse-server-mongo sample CI — keploy-independent end-to-end smoke + |
| 2 | +# coverage gate. |
| 3 | +# |
| 4 | +# Triggers ONLY on changes under parse-server-mongo/ (or this workflow |
| 5 | +# file). Other samples in this repo have their own orthogonal CI; |
| 6 | +# gating the whole repo on every parse-server change would slow them |
| 7 | +# all down for no benefit. |
| 8 | +# |
| 9 | +# What it gates: |
| 10 | +# * `release-coverage` — checks out the PR's base branch (main) |
| 11 | +# and runs the sample end-to-end: docker compose up, bootstrap |
| 12 | +# the fixed user + session, drive flow.sh record-traffic with |
| 13 | +# the per-call audit log enabled, capture the route-coverage |
| 14 | +# percentage from `flow.sh coverage`. This is the baseline. |
| 15 | +# * `build-coverage` — same end-to-end against the PR's HEAD ref. |
| 16 | +# * `coverage-gate` — fails the PR if `build`'s coverage drops |
| 17 | +# more than COVERAGE_THRESHOLD percentage points below |
| 18 | +# `release`. Default threshold is 1.0pp; override via repo |
| 19 | +# variable `PARSE_COVERAGE_THRESHOLD` for a tighter or |
| 20 | +# looser bar. |
| 21 | +# |
| 22 | +# On push to main, only `build-coverage` runs (no baseline to |
| 23 | +# compare against — main IS the baseline). |
| 24 | +# |
| 25 | +# Standards-aligned choices: |
| 26 | +# * `paths:` filter on both push and pull_request triggers — the |
| 27 | +# canonical GH Actions way to scope a workflow to one |
| 28 | +# subdirectory. |
| 29 | +# * Job outputs (steps.<id>.outputs.coverage → needs.<job>.outputs) |
| 30 | +# to thread the captured percentage between jobs. |
| 31 | +# * `concurrency:` cancel-in-progress on the same ref so a stale |
| 32 | +# run doesn't waste runner minutes. |
| 33 | +# * actions/upload-artifact for the human-readable |
| 34 | +# coverage_report.txt — reviewers can inspect missing routes |
| 35 | +# directly from the PR's "checks" tab. |
| 36 | +# * marocchino/sticky-pull-request-comment for the PR-side diff |
| 37 | +# comment. Pinned-by-header so successive runs update the same |
| 38 | +# comment instead of fanning out. |
| 39 | +# * The compare step is plain bash + python3 (no external |
| 40 | +# coverage service). The sample's coverage is a single |
| 41 | +# route-based percentage, so the gate is a 3-line subtraction. |
| 42 | +# |
| 43 | +# Sample is genuinely keploy-independent here: the workflow uses |
| 44 | +# flow.sh's $PARSE_FIRED_ROUTES_FILE per-call audit log as its |
| 45 | +# numerator source, not a keploy recording. The lane scripts in |
| 46 | +# keploy/integrations and keploy/enterprise consume the same |
| 47 | +# flow.sh, but use the keploy/test-set-*/tests/*.yaml tree as |
| 48 | +# their numerator (authoritative — only calls keploy actually |
| 49 | +# CAPTURED count). Both modes are wired into |
| 50 | +# `flow.sh::parse_collect_recorded_routes`. |
| 51 | +name: parse-server-mongo sample |
| 52 | + |
| 53 | +on: |
| 54 | + pull_request: |
| 55 | + paths: |
| 56 | + - 'parse-server-mongo/**' |
| 57 | + - '.github/workflows/parse-server-mongo.yml' |
| 58 | + - '.github/workflows/scripts/run-and-measure-parse-server.sh' |
| 59 | + push: |
| 60 | + branches: [main] |
| 61 | + paths: |
| 62 | + - 'parse-server-mongo/**' |
| 63 | + - '.github/workflows/parse-server-mongo.yml' |
| 64 | + - '.github/workflows/scripts/run-and-measure-parse-server.sh' |
| 65 | + workflow_dispatch: {} |
| 66 | + |
| 67 | +concurrency: |
| 68 | + group: parse-server-mongo-${{ github.ref }} |
| 69 | + cancel-in-progress: true |
| 70 | + |
| 71 | +env: |
| 72 | + COVERAGE_THRESHOLD: ${{ vars.PARSE_COVERAGE_THRESHOLD || '1.0' }} |
| 73 | + |
| 74 | +jobs: |
| 75 | + build-coverage: |
| 76 | + name: build (current ref) coverage |
| 77 | + runs-on: ubuntu-latest |
| 78 | + timeout-minutes: 20 |
| 79 | + outputs: |
| 80 | + coverage: ${{ steps.measure.outputs.coverage }} |
| 81 | + steps: |
| 82 | + - uses: actions/checkout@v4 |
| 83 | + - id: measure |
| 84 | + name: Run sample end-to-end + measure coverage |
| 85 | + working-directory: parse-server-mongo |
| 86 | + env: |
| 87 | + PARSE_FIRED_ROUTES_FILE: ${{ runner.temp }}/fired-routes-build.log |
| 88 | + PARSE_PHASE: ci-build |
| 89 | + run: ../.github/workflows/scripts/run-and-measure-parse-server.sh |
| 90 | + |
| 91 | + - name: Upload coverage report |
| 92 | + if: always() |
| 93 | + uses: actions/upload-artifact@v4 |
| 94 | + with: |
| 95 | + name: coverage-build |
| 96 | + path: parse-server-mongo/coverage_report.txt |
| 97 | + if-no-files-found: warn |
| 98 | + |
| 99 | + release-coverage: |
| 100 | + if: github.event_name == 'pull_request' |
| 101 | + name: release (base ref) coverage |
| 102 | + runs-on: ubuntu-latest |
| 103 | + timeout-minutes: 20 |
| 104 | + outputs: |
| 105 | + coverage: ${{ steps.measure.outputs.coverage || steps.empty-baseline.outputs.coverage }} |
| 106 | + sample-existed: ${{ steps.detect.outputs.sample-existed }} |
| 107 | + steps: |
| 108 | + - uses: actions/checkout@v4 |
| 109 | + with: |
| 110 | + ref: ${{ github.event.pull_request.base.ref }} |
| 111 | + |
| 112 | + # First-PR bootstrap escape hatch: the very PR that |
| 113 | + # introduces the parse-server-mongo/ sample has no baseline |
| 114 | + # (parse-server-mongo/ doesn't exist on the base ref). Detect |
| 115 | + # that and short-circuit to coverage=0; the gate then |
| 116 | + # treats build's coverage as the new baseline and trivially |
| 117 | + # passes for any percentage > 0. After the introducing PR |
| 118 | + # merges, every subsequent PR has a real baseline to diff |
| 119 | + # against. |
| 120 | + - id: detect |
| 121 | + name: Detect baseline presence |
| 122 | + run: | |
| 123 | + if [ -d parse-server-mongo ] && [ -x parse-server-mongo/flow.sh ]; then |
| 124 | + echo "sample-existed=true" >>"$GITHUB_OUTPUT" |
| 125 | + echo "Sample exists on base ref — running full measurement." |
| 126 | + else |
| 127 | + echo "sample-existed=false" >>"$GITHUB_OUTPUT" |
| 128 | + echo "No parse-server-mongo/ on base ref — first-PR bootstrap; baseline coverage treated as 0%." |
| 129 | + fi |
| 130 | +
|
| 131 | + - id: measure |
| 132 | + name: Run sample end-to-end + measure coverage |
| 133 | + if: steps.detect.outputs.sample-existed == 'true' |
| 134 | + working-directory: parse-server-mongo |
| 135 | + env: |
| 136 | + PARSE_FIRED_ROUTES_FILE: ${{ runner.temp }}/fired-routes-release.log |
| 137 | + PARSE_PHASE: ci-release |
| 138 | + run: ../.github/workflows/scripts/run-and-measure-parse-server.sh |
| 139 | + |
| 140 | + - id: empty-baseline |
| 141 | + name: Emit zero baseline (first-PR bootstrap) |
| 142 | + if: steps.detect.outputs.sample-existed != 'true' |
| 143 | + run: echo "coverage=0.0" >>"$GITHUB_OUTPUT" |
| 144 | + |
| 145 | + - name: Upload coverage report |
| 146 | + if: always() && steps.detect.outputs.sample-existed == 'true' |
| 147 | + uses: actions/upload-artifact@v4 |
| 148 | + with: |
| 149 | + name: coverage-release |
| 150 | + path: parse-server-mongo/coverage_report.txt |
| 151 | + if-no-files-found: warn |
| 152 | + |
| 153 | + coverage-gate: |
| 154 | + if: github.event_name == 'pull_request' |
| 155 | + name: coverage gate |
| 156 | + needs: [build-coverage, release-coverage] |
| 157 | + runs-on: ubuntu-latest |
| 158 | + steps: |
| 159 | + - name: Compare build vs release |
| 160 | + env: |
| 161 | + BUILD: ${{ needs.build-coverage.outputs.coverage }} |
| 162 | + RELEASE: ${{ needs.release-coverage.outputs.coverage }} |
| 163 | + THRESHOLD: ${{ env.COVERAGE_THRESHOLD }} |
| 164 | + BASE_REF: ${{ github.event.pull_request.base.ref }} |
| 165 | + run: | |
| 166 | + set -Eeuo pipefail |
| 167 | + if [ -z "${BUILD:-}" ] || [ -z "${RELEASE:-}" ]; then |
| 168 | + echo "::error::missing coverage outputs — build='${BUILD:-}' release='${RELEASE:-}'" |
| 169 | + exit 1 |
| 170 | + fi |
| 171 | + drop=$(python3 -c "print(round(${RELEASE} - ${BUILD}, 2))") |
| 172 | + echo "Release (${BASE_REF}): ${RELEASE}%" |
| 173 | + echo "Build (this PR): ${BUILD}%" |
| 174 | + echo "Drop: ${drop}pp (threshold ${THRESHOLD}pp)" |
| 175 | + if python3 -c "import sys; sys.exit(0 if (${RELEASE} - ${BUILD}) > ${THRESHOLD} else 1)"; then |
| 176 | + echo "::error::parse-server-mongo coverage dropped from ${RELEASE}% → ${BUILD}% (-${drop}pp), exceeding the ${THRESHOLD}pp threshold." |
| 177 | + echo "Suggested actions:" |
| 178 | + echo " * Add curl(s) to flow.sh::parse_record_traffic that exercise the routes you changed/touched." |
| 179 | + echo " * If the route(s) was intentionally retired, drop it from parse-server-mongo/flow.sh::parse_list_routes too so it's removed from the denominator." |
| 180 | + exit 1 |
| 181 | + fi |
| 182 | + echo "OK — coverage delta within ${THRESHOLD}pp threshold." |
| 183 | +
|
| 184 | + - name: Sticky PR comment |
| 185 | + if: ${{ !cancelled() }} |
| 186 | + uses: marocchino/sticky-pull-request-comment@v2 |
| 187 | + with: |
| 188 | + header: parse-server-mongo-coverage |
| 189 | + message: | |
| 190 | + ### parse-server-mongo sample coverage |
| 191 | +
|
| 192 | + | ref | coverage | |
| 193 | + |---|---| |
| 194 | + | base (`${{ github.event.pull_request.base.ref }}`) | **${{ needs.release-coverage.outputs.coverage }}%** | |
| 195 | + | this PR | **${{ needs.build-coverage.outputs.coverage }}%** | |
| 196 | +
|
| 197 | + Threshold: PR may not drop coverage by more than **${{ env.COVERAGE_THRESHOLD }}pp**. Override per-repo via the `PARSE_COVERAGE_THRESHOLD` actions variable. |
| 198 | +
|
| 199 | + Coverage measures the parse-server REST + GraphQL surface (`/parse/users` + `/parse/sessions` + `/parse/classes/*` + `/parse/roles` + `/parse/files/*` + `/parse/functions/*` + `/parse/schemas/*` + `/parse/hooks/*` + `/parse/graphql` + `/parse/aggregate/*` + health) that `flow.sh::parse_record_traffic` exercises against the running backend. Reports are attached as artifacts on each job ("coverage-build" / "coverage-release"). |
0 commit comments