Skip to content

feat(doccano-django): keploy compat lane sample + Python line coverage gate #1

feat(doccano-django): keploy compat lane sample + Python line coverage gate

feat(doccano-django): keploy compat lane sample + Python line coverage gate #1

# doccano-django sample CI — keploy-independent end-to-end smoke +
# coverage gate.
#
# Triggers ONLY on changes under doccano-django/ (or this workflow
# file). Other samples in this repo have their own orthogonal CI;
# gating the whole repo on every doccano change would slow them
# all down for no benefit.
#
# What it gates:
# * `release-coverage` — checks out the PR's base branch (main)
# and runs the sample end-to-end: docker compose up, bootstrap
# admin token, drive flow.sh record-traffic with the per-call
# audit log enabled, capture the route-coverage percentage from
# `flow.sh coverage`. This is the baseline.
# * `build-coverage` — same end-to-end against the PR's HEAD ref.
# * `coverage-gate` — fails the PR if `build`'s coverage drops
# more than COVERAGE_THRESHOLD percentage points below
# `release`. Default threshold is 1.0pp; override via repo
# variable `DOCCANO_COVERAGE_THRESHOLD` for a tighter or
# looser bar.
#
# On push to main, only `build-coverage` runs (no baseline to
# compare against — main IS the baseline).
#
# Standards-aligned choices:
# * `paths:` filter on both push and pull_request triggers — the
# canonical GH Actions way to scope a workflow to one
# subdirectory.
# * Job outputs (steps.<id>.outputs.coverage → needs.<job>.outputs)
# to thread the captured percentage between jobs.
# * `concurrency:` cancel-in-progress on the same ref so a stale
# run doesn't waste runner minutes.
# * actions/upload-artifact for the human-readable
# coverage_report.txt — reviewers can inspect missing routes
# directly from the PR's "checks" tab.
# * marocchino/sticky-pull-request-comment for the PR-side diff
# comment. Pinned-by-header so successive runs update the same
# comment instead of fanning out.
# * The compare step is plain bash + python3 (no external
# coverage service). For full Python coverage.py XMLs you'd
# want diff-cover or codecov, but the sample's coverage is
# API-route-based (single percentage), so the gate is a 3-line
# subtraction.
#
# Sample is genuinely keploy-independent here: the workflow uses
# flow.sh's $DOCCANO_FIRED_ROUTES_FILE per-call audit log as its
# numerator source, not a keploy recording. The lane scripts in
# keploy/integrations and keploy/enterprise consume the same
# flow.sh, but use the keploy/test-set-*/tests/*.yaml tree as
# their numerator (authoritative — only calls keploy actually
# CAPTURED count). Both modes are wired into
# `flow.sh::doccano_list_recorded_routes`.
name: doccano-django sample
on:
pull_request:
paths:
- 'doccano-django/**'
- '.github/workflows/doccano-django.yml'
push:
branches: [main]
paths:
- 'doccano-django/**'
- '.github/workflows/doccano-django.yml'
workflow_dispatch: {}
concurrency:
group: doccano-django-${{ github.ref }}
cancel-in-progress: true
env:
COVERAGE_THRESHOLD: ${{ vars.DOCCANO_COVERAGE_THRESHOLD || '1.0' }}
jobs:
build-coverage:
name: build (current ref) coverage
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
coverage: ${{ steps.measure.outputs.coverage }}
steps:
- uses: actions/checkout@v4
- id: measure
name: Run sample end-to-end + measure coverage
working-directory: doccano-django
env:
DOCCANO_FIRED_ROUTES_FILE: ${{ runner.temp }}/fired-routes-build.log
DOCCANO_PHASE: ci-build
run: ../.github/workflows/scripts/run-and-measure.sh
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-build
path: doccano-django/coverage_report.txt
if-no-files-found: warn
release-coverage:
if: github.event_name == 'pull_request'
name: release (base ref) coverage
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
coverage: ${{ steps.measure.outputs.coverage }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
- id: measure
name: Run sample end-to-end + measure coverage
working-directory: doccano-django
env:
DOCCANO_FIRED_ROUTES_FILE: ${{ runner.temp }}/fired-routes-release.log
DOCCANO_PHASE: ci-release
run: ../.github/workflows/scripts/run-and-measure.sh
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-release
path: doccano-django/coverage_report.txt
if-no-files-found: warn
coverage-gate:
if: github.event_name == 'pull_request'
name: coverage gate
needs: [build-coverage, release-coverage]
runs-on: ubuntu-latest
steps:
- name: Compare build vs release
env:
BUILD: ${{ needs.build-coverage.outputs.coverage }}
RELEASE: ${{ needs.release-coverage.outputs.coverage }}
THRESHOLD: ${{ env.COVERAGE_THRESHOLD }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
set -Eeuo pipefail
if [ -z "${BUILD:-}" ] || [ -z "${RELEASE:-}" ]; then
echo "::error::missing coverage outputs — build='${BUILD:-}' release='${RELEASE:-}'"
exit 1
fi
drop=$(python3 -c "print(round(${RELEASE} - ${BUILD}, 2))")
echo "Release (${BASE_REF}): ${RELEASE}%"
echo "Build (this PR): ${BUILD}%"
echo "Drop: ${drop}pp (threshold ${THRESHOLD}pp)"
if python3 -c "import sys; sys.exit(0 if (${RELEASE} - ${BUILD}) > ${THRESHOLD} else 1)"; then
echo "::error::doccano-django coverage dropped from ${RELEASE}% → ${BUILD}% (-${drop}pp), exceeding the ${THRESHOLD}pp threshold."
echo "Suggested actions:"
echo " * Add curl(s) to flow.sh::doccano_record_traffic that exercise the routes you changed/touched."
echo " * If the route(s) was intentionally retired, drop it from doccano-django/flow.sh::doccano_list_routes' SCOPE_PREFIXES too so it's removed from the denominator."
exit 1
fi
echo "OK — coverage delta within ${THRESHOLD}pp threshold."
- name: Sticky PR comment
if: ${{ !cancelled() }}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: doccano-django-coverage
message: |
### doccano-django sample coverage
| ref | coverage |
|---|---|
| base (`${{ github.event.pull_request.base.ref }}`) | **${{ needs.release-coverage.outputs.coverage }}%** |
| this PR | **${{ needs.build-coverage.outputs.coverage }}%** |
Threshold: PR may not drop coverage by more than **${{ env.COVERAGE_THRESHOLD }}pp**. Override per-repo via the `DOCCANO_COVERAGE_THRESHOLD` actions variable.
Coverage measures the API surface (`/v1/projects/*` + `/v1/me` + `/v1/users` + `/v1/health` + `/v1/auth`) that `flow.sh::doccano_record_traffic` actually exercises against the running backend's URL resolver. Reports are attached as artifacts on each job ("coverage-build" / "coverage-release").