15

How do I get code coverage out of a Django project's view code (and code called by view code)?

coverage gunicorn <params> does not show any lines being hit.

Games Brainiac
  • 80,178
  • 33
  • 141
  • 199
Kimvais
  • 38,306
  • 16
  • 108
  • 142

1 Answers1

16

coverage gunicorn <params> does not work, because gunicorn creates worker processes, and the coverage module can't work across forks (basically, creation of new processes). You can use the coverage API, though, for example in the python module that contains your WSGI application:

# wsgi_with_coverage.py
import atexit
import sys
import coverage
cov = coverage.coverage()
cov.start()

from wsgi import application  # adjust to python module containing your wsgi application


def save_coverage():
    print >> sys.stderr, "saving coverage"
    cov.stop()
    cov.save()

atexit.register(save_coverage)

Then run gunicorn -w 1 wsgi_with_coverage:application <other params>.

The problem is, the atexit functions are not called if you kill the gunicorn process, for example via CTRL+C. But they are called on SIGHUP, so if you do kill -HUP $(cat <gunicorn_pidfile_here>), the coverage data should be saved (by default to ".coverage" in the current directory).

A possible caveat is that this won't work with more than one gunicorn worker, because they would all overwrite the ".coverage" file. If you absolutely need more than one worker, you could write to ".coverage-%d" % os.getpid() (set the file name via the data_file parameter to the coverage constructor) and use the combine() method to merge the individual measurements.

This should work on other WSGI servers, too, depending on whether they allow their worker processes to clean up via the atexit method.

sk1p
  • 6,645
  • 30
  • 35
  • The SIGHUP advice is not so good :). Gunicorn reloads the worker processes upon retrieving SIGHUP. That is, the collected coverage data is exactly not saved, but overwritten with those things that happen upon load only. Sending SIGTER, on the other hand, properly triggers the atexit handler and cleanly shuts down Gunicorn. – Dr. Jan-Philip Gehrcke Dec 14 '15 at 22:45
  • 1
    Update. setting `data_suffix=True` when calling the `Coverage` constructor will automatically add machine name and PID to the coverage filename. Instead of using `data_file` as suggested above. – Sean Perry May 22 '18 at 20:53
  • hi, i have implemented this solution and my coverage files are being generated from all my gunicorn workers, but they are not generating any coverage. I am using gunicorn 5.5 – Pbd May 28 '21 at 15:09
  • It may be worth checking the official documentation on this topic, as much has happened since I wrote the answer: https://coverage.readthedocs.io/en/coverage-5.5/subprocess.html – sk1p May 29 '21 at 01:47