Skip to content

Instantly share code, notes, and snippets.

@abingham
Last active October 31, 2022 08:48
Show Gist options
  • Save abingham/0812e016f2158ff7519c4df6f3958272 to your computer and use it in GitHub Desktop.
Save abingham/0812e016f2158ff7519c4df6f3958272 to your computer and use it in GitHub Desktop.
Gunicorn server hooks for coverage analysis of a Flask app
[run]
parallel=True
omit =
test_*.py

This is an example of how to use gunicorn server hooks to run coverage on a flask app. In short:

  1. The post_fork hook starts coverage for each worker.
  2. The worker_exit hook stops and saves coverage for each worker.

This allows you to use kill -HUP <master pid> to produce coverage files. The HUP signal kills all of the worker processes, letting them write their coverage data, and then restarts the entire server.

Install dependencies

pip install -r requirements.txt

Generating coverage data

Start the server

First start the server:

gunicorn --workers 2 app:app

Make a note of the PID of the master process.

Poke the server as needed

Then invoke endpoints on your server via curl or a browser. Once you've probed it (or not!), you can restart the server:

kill -HUP <master PID>

This will generate worker-specific coverage files in the form .coverage.<host>.<PID>.<random #>.

Combine the coverage results

You can combine these (together with any existing .coverage file) with:

coverage combine

This will produce a .coverage file which can be used to report coverage.

Incremental coverage results

Note that by default the coverage combine command will lose any existing coverage results. That is, if a .coverage file exists before running combine, it will be cleared before combining the new results into it.

If you want to keep existing results and merge in the new coverage data, you can use coverage combine --append. There are other ways to control how combining work which you can find here or by running coverage combine --help.

Generate a report

To generate a report of coverage for the lib.py module, use:

coverage report lib.py

Repeat for more coverage

The HUP signal causes the server to restart, so you can continue using it afterward.

Restarting coverage analysis

If you want to start coverage analysis afresh, you should kill the server entirely. Then remove .coverage if it exists, along with any partial .coverage.<host>.<PID>.<random #> files. DO NOT DELETE .coveragerc!`

import flask
app = flask.Flask("app")
import lib
@app.route("/foo")
def foo():
return f"{lib.foo()}"
@app.route("/bar")
def bar():
return f"{lib.bar()}"
import os
import coverage
cov = coverage.Coverage()
def post_fork(server, worker):
# Start coverage right after the worker forks
print(f'post fork {os.getpid()}')
cov.start()
def worker_exit(server, worker):
# Save coverage when the worker finishes
print(f'exit {os.getpid()}')
cov.stop()
cov.save()
def foo():
return 42
def bar():
return 43
Flask
gunicorn
coverage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment