How Coverage.py works

For advanced use of coverage.py, or just because you are curious, it helps tounderstand what’s happening behind the scenes. Coverage.py works in threephases:

  • Execution: Coverage.py runs your code, and monitors it to see what lineswere executed.
  • Analysis: Coverage.py examines your code to determine what lines couldhave run.
  • Reporting: Coverage.py combines the results of execution and analysis toproduce a coverage number and an indication of missing execution.

The execution phase is handled by thecoveragerun command. The analysisand reporting phases are handled by the reporting commands likecoveragereport orcoveragehtml.

Let’s look at each phase in more detail.

Execution

At the heart of the execution phase is a Python trace function. This is afunction that the Python interpreter invokes for each line executed in aprogram. Coverage.py implements a trace function that records each file andline number as it is executed.

Executing a function for every line in your program can make execution veryslow. Coverage.py’s trace function is implemented in C to reduce thatslowdown. It also takes care to not trace code that you aren’t interested in.

When measuring branch coverage, the same trace function is used, but instead ofrecording line numbers, coverage.py records pairs of line numbers. Eachinvocation of the trace function remembers the line number, then the nextinvocation records the pair(prev, this) to indicate that executiontransitioned from the previous line to this line. Internally, these are calledarcs.

For more details of trace functions, see the Python docs forsys.settrace,or if you are really brave,How C trace functions really work.

At the end of execution, coverage.py writes the data it collected to a datafile, usually named.coverage. This is a JSON-based file containing all ofthe recorded file names and line numbers executed.

Analysis

After your program has been executed and the line numbers recorded, coverage.pyneeds to determine what lines could have been executed. Luckily, compiledPython files (.pyc files) have a table of line numbers in them. Coverage.pyreads this table to get the set of executable lines, with a little more sourceanalysis to leave out things like docstrings.

The data file is read to get the set of lines that were executed. Thedifference between the executable lines, and the executed lines, are the linesthat were not executed.

The same principle applies for branch measurement, though the process fordetermining possible branches is more involved. Coverage.py uses the abstractsyntax tree of the Python source file to determine the set of possiblebranches.

Reporting

Once we have the set of executed lines and missing lines, reporting is just amatter of formatting that information in a useful way. Each reporting method(text, html, annotated source, xml) has a different output format, but theprocess is the same: write out the information in the particular format,possibly including the source code itself.

Plugins

Plugins interact with these phases.