Skip to content

Latest commit

 

History

History
101 lines (67 loc) · 3.78 KB

File metadata and controls

101 lines (67 loc) · 3.78 KB

flask-coverage demo

A two-file Flask app that demonstrates flask-coverage and shows realistic coverage numbers as you exercise it.

Files

File Role
demo.py Tiny bootstrap. Calls start_early() before importing the app, so coverage is active for everything below.
demo_app.py The actual app — three routes, an app factory, FlaskCoverage(app). Fully measurable from line 1.

The split exists because coverage.py can only trace lines that execute after Coverage.start() runs. If route definitions and the factory live in the same file as the bootstrap, those module-level lines are missed even though the tool is working correctly. Putting them in a separate module — imported only after start_early() — sidesteps the problem and gives an honest 90%+ number when all routes are exercised. (For real projects, the same pattern works: wsgi.py does the bootstrap, myapp/ is the application package.)

What's in demo_app.py

Route Purpose
GET / Always-hit route — covered after the first request.
GET /maybe Two branches selected by the ?fail=1 query string — exercise the gap.
GET /never-called Wired up but not called by the walkthrough — its body shows as missing in the report.

Run it

From the repository root:

uv sync
uv run flask --app examples/demo run --debug

--debug is what authorises the /debug/coverage blueprint without needing a password.

Walkthrough

In a second terminal:

# 1. Open the dashboard. demo_app.py should already be at ~72% — its
#    module-level setup ran during import (with coverage already active).
open http://127.0.0.1:5000/debug/coverage/

# 2. Exercise / and one branch of /maybe.
curl http://127.0.0.1:5000/
curl http://127.0.0.1:5000/maybe

# 3. Refresh the dashboard. Coverage on demo_app.py rises to ~88%; the
#    fail-branch of /maybe and never_called's body are still missing.

# 4. Hit the other branch.
curl 'http://127.0.0.1:5000/maybe?fail=1'

# 5. Refresh again. demo_app.py reaches ~94%. Only never_called's body
#    remains uncovered — exactly as documented.

Click html report in the dashboard nav (or GET /debug/coverage/html/) to see the line-by-line source view with covered / missing lines highlighted. For programmatic consumption (e.g. CI gating), GET /debug/coverage/files returns the same data as JSON.

What about demo.py itself?

The bootstrap module shows poor coverage in the report — that's expected. Its top three lines (docstring, from __future__ import annotations, the import of start_early) necessarily run before start_early() is called, so they can't be traced. That's the price of starting coverage in user-space code rather than via COVERAGE_PROCESS_START (see below).

Variants worth trying

Production-style with HTTP basic auth (no --debug):

FLASK_COVERAGE_PASSWORD=s3cret \
    uv run flask --app examples/demo run

Hit the dashboard with credentials:

curl -u admin:s3cret http://127.0.0.1:5000/debug/coverage/report

Disable without redeploying:

FLASK_COVERAGE_DISABLED=1 \
    uv run flask --app examples/demo run --debug

FlaskCoverage(app) and start_early() both become no-ops — no tracer, no blueprint, no auth check. Useful when an operator needs to pull tracing fast.

Start coverage from outside the process — the tidiest option for real deployments, because then nothing in user code is missed:

COVERAGE_PROCESS_START=$(pwd)/pyproject.toml \
    uv run flask --app examples/demo run --debug

start_early() detects the already-running tracer and is a no-op; FlaskCoverage(app) adopts it.

Cleaning up

The demo writes a .coverage file in your current directory:

rm -f .coverage .coverage.*