llvm/utils/check-coverage-regressions.py
Vedant Kumar 1085c3b0d6 [utils] Update coverage regression checking script
r276409 changed the coverage summary format. Update the script so that
it can parse the new output, and incorporate the new information into
its warnings.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@276446 91177308-0d34-0410-b5e6-96231b3b80d8
2016-07-22 17:38:03 +00:00

140 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python
'''Compare two coverage summaries for regressions.
You can create a coverage summary by using the `llvm-cov report` command.
Alternatively, you can use `utils/prepare-code-coverage-artifact.py` which
creates summaries as well as file-based html reports.
'''
from __future__ import print_function
import argparse
import collections
import re
import sys
# This threshold must be in [0, 1]. The lower the threshold, the less likely
# it is that a regression will be flagged and vice versa.
kThresh = 1.0
FileCoverage = collections.namedtuple('FileCoverage',
['Regions', 'MissedRegions', 'RegionCoverage',
'Functions', 'MissedFunctions', 'Executed',
'Lines', 'MissedLines', 'LineCoverage'])
CoverageEntry = re.compile(r'^(.*)'
r' +(\d+) +(\d+) +([\d.]+)%'
r' +(\d+) +(\d+) +([\d.]+)%'
r' +(\d+) +(\d+) +([\d.]+)%$')
def parse_file_coverage_line(line):
'''Parse @line as a summary of a file's coverage information.
>>> parse_file_coverage_line('report.cpp 5 2 60.00% 4 1 75.00% 13 4 69.23%')
('report.cpp', FileCoverage(\
Regions=5, MissedRegions=2, RegionCoverage=60.0, \
Functions=4, MissedFunctions=1, Executed=75.0, \
Lines=13, MissedLines=4, LineCoverage=69.23))
'''
m = re.match(CoverageEntry, line)
if not m:
print("Could not read coverage summary:", line)
exit(1)
groups = m.groups()
filename = groups[0].strip()
regions = int(groups[1])
missed_regions = int(groups[2])
region_coverage = float(groups[3])
functions = int(groups[4])
missed_functions = int(groups[5])
executed = float(groups[6])
lines = int(groups[7])
missed_lines = int(groups[8])
line_coverage = float(groups[9])
return (filename,
FileCoverage(regions, missed_regions, region_coverage,
functions, missed_functions, executed,
lines, missed_lines, line_coverage))
def parse_summary(path):
'''Parse the summary at @path. Return a dictionary mapping filenames to
FileCoverage instances.'''
with open(path, 'r') as f:
lines = f.readlines()
# Drop the header and the cell dividers. Include "TOTAL" in this list.
file_coverages = lines[2:-2] + [lines[-1]]
summary = {}
for line in file_coverages:
filename, fc = parse_file_coverage_line(line)
summary[filename] = fc
return summary
def find_coverage_regressions(old_coverage, new_coverage):
'''Given two coverage summaries, generate coverage regressions of the form:
(filename, old FileCoverage, new FileCoverage).'''
for filename in old_coverage.keys():
if filename not in new_coverage:
continue
old_fc = old_coverage[filename]
new_fc = new_coverage[filename]
if new_fc.RegionCoverage < kThresh * old_fc.RegionCoverage or \
new_fc.Executed < kThresh * old_fc.Executed:
yield (filename, old_fc, new_fc)
def print_regression(filename, old_fc, new_fc):
'''Pretty-print a coverage regression in @filename. @old_fc is the old
FileCoverage and @new_fc is the new one.
>>> print_regression('foo.cpp', \
FileCoverage(10, 5, 50.0, 10, 10, 0, 20, 10, 50.0), \
FileCoverage(10, 7, 30.0, 10, 10, 0, 20, 14, 30.0))
Code coverage regression:
File: foo.cpp
Change in function coverage: 0.00% (0/10 -> 0/10)
Change in line coverage : -20.00% (10/20 -> 6/20)
Change in region coverage : -20.00% (5/10 -> 3/10)
'''
func_coverage_delta = new_fc.Executed - old_fc.Executed
line_coverage_delta = new_fc.LineCoverage - old_fc.LineCoverage
region_coverage_delta = new_fc.RegionCoverage - old_fc.RegionCoverage
print("Code coverage regression:")
print(" File:", filename)
print(" Change in function coverage: {0:.2f}% ({1}/{2} -> {3}/{4})".format(
func_coverage_delta, old_fc.Functions - old_fc.MissedFunctions,
old_fc.Functions, new_fc.Functions - new_fc.MissedFunctions,
new_fc.Functions))
print(" Change in line coverage : {0:.2f}% ({1}/{2} -> {3}/{4})".format(
line_coverage_delta, old_fc.Lines - old_fc.MissedLines, old_fc.Lines,
new_fc.Lines - new_fc.MissedLines, new_fc.Lines))
print(" Change in region coverage : {0:.2f}% ({1}/{2} -> {3}/{4})".format(
region_coverage_delta, old_fc.Regions - old_fc.MissedRegions,
old_fc.Regions, new_fc.Regions - new_fc.MissedRegions, new_fc.Regions))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('old_summary', help='Path to the old coverage summary')
parser.add_argument('new_summary', help='Path to the new coverage summary')
args = parser.parse_args()
old_coverage = parse_summary(args.old_summary)
new_coverage = parse_summary(args.new_summary)
num_regressions = 0
for filename, old_fc, new_fc in \
find_coverage_regressions(old_coverage, new_coverage):
print_regression(filename, old_fc, new_fc)
num_regressions += 1
if num_regressions > 0:
exit(1)
exit(0)