Skip to content

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

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

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

# 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.empty-baseline.outputs.coverage }}
sample-existed: ${{ steps.detect.outputs.sample-existed }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
# First-PR bootstrap escape hatch: the very PR that
# introduces the doccano-django/ sample has no baseline
# (doccano-django/ doesn't exist on the base ref). Detect
# that and short-circuit to coverage=0; the gate then
# treats build's coverage as the new baseline and trivially
# passes for any percentage > 0. After the introducing PR
# merges, every subsequent PR has a real baseline to diff
# against.
- id: detect
name: Detect baseline presence
run: |
if [ -d doccano-django ] && [ -x doccano-django/flow.sh ]; then
echo "sample-existed=true" >>"$GITHUB_OUTPUT"
echo "Sample exists on base ref — running full measurement."
else
echo "sample-existed=false" >>"$GITHUB_OUTPUT"
echo "No doccano-django/ on base ref — first-PR bootstrap; baseline coverage treated as 0%."
fi
- id: measure
name: Run sample end-to-end + measure coverage
if: steps.detect.outputs.sample-existed == 'true'
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
- id: empty-baseline
name: Emit zero baseline (first-PR bootstrap)
if: steps.detect.outputs.sample-existed != 'true'
run: echo "coverage=0.0" >>"$GITHUB_OUTPUT"
- name: Upload coverage report
if: always() && steps.detect.outputs.sample-existed == 'true'
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 new code paths."
echo " * Or extend the .coveragerc 'omit' list if the new module is not part of the runtime backend (migrations, management commands, tests)."
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.