Summary: Ultimate goals of the coverage report are: * Report the coverage for all files (done in this diff) * Report the coverage for recently updated files (not fully finished) * Report is available in html form (done in this diff, but need some extra work to integrate it in Jenkin) Task link: https://our.intern.facebook.com/intern/tasks/?s=1154818042&t=2604914 Test Plan: Ran: coverage/coverage_test.sh The sample output can be found here: https://phabricator.fb.com/P2433892 Reviewers: dhruba, emayanke CC: leveldb Differential Revision: https://reviews.facebook.net/D11943main
parent
03bd4461ad
commit
9f6b8f0032
@ -0,0 +1,68 @@ |
||||
#!/bin/bash |
||||
|
||||
# Exit on error. |
||||
set -e |
||||
|
||||
if [ -n "$USE_CLANG" ]; then |
||||
echo "Error: Coverage test is supported only for gcc." |
||||
exit 1 |
||||
fi |
||||
|
||||
ROOT=".." |
||||
# Fetch right version of gcov |
||||
if [ -d /mnt/gvfs/third-party -a -z "$CXX" ]; then |
||||
source $ROOT/fbcode.gcc471.sh |
||||
GCOV=$TOOLCHAIN_EXECUTABLES/gcc/gcc-4.7.1/cc6c9dc/bin/gcov |
||||
else |
||||
GCOV=$(which gcov) |
||||
fi |
||||
|
||||
COVERAGE_DIR=$(mktemp -t -d rocksdb_coverage_XXXX) |
||||
mkdir -p $COVERAGE_DIR |
||||
|
||||
# Find all gcno files to generate the coverage report |
||||
|
||||
GCNO_FILES=`find $ROOT -name "*.gcno"` |
||||
$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null | |
||||
# Parse the raw gcov report to more human readable form. |
||||
python $ROOT/coverage/parse_gcov_output.py | |
||||
# Write the output to both stdout and report file. |
||||
tee $COVERAGE_DIR/coverage_report_all.txt && |
||||
echo -e "Generated coverage report for all files: $COVERAGE_DIR/coverage_report_all.txt\n" |
||||
|
||||
# TODO: we also need to get the files of the latest commits. |
||||
# Get the most recently committed files. |
||||
LATEST_FILES=` |
||||
git show --pretty="format:" --name-only HEAD | |
||||
grep -v "^$" | |
||||
paste -s -d,` |
||||
RECENT_REPORT=$COVERAGE_DIR/coverage_report_recent.txt |
||||
|
||||
echo -e "Recently updated files: $LATEST_FILES\n" > $RECENT_REPORT |
||||
$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null | |
||||
python $ROOT/coverage/parse_gcov_output.py -interested-files $LATEST_FILES | |
||||
tee -a $RECENT_REPORT && |
||||
echo -e "Generated coverage report for recently updated files: $RECENT_REPORT\n" |
||||
|
||||
# Generate the html report. If we cannot find lcov in this machine, we'll simply |
||||
# skip this step. |
||||
echo "Generating the html coverage report..." |
||||
set +e |
||||
LCOV=$(which lcov 2>/dev/null) |
||||
if [ -z $LCOV ] |
||||
then |
||||
echo "Skip: Cannot find lcov to generate the html report." |
||||
exit 0 |
||||
fi |
||||
|
||||
set -e |
||||
|
||||
(cd $ROOT; lcov --no-external \ |
||||
--capture \ |
||||
--directory $PWD \ |
||||
--gcov-tool $GCOV \ |
||||
--output-file $COVERAGE_DIR/coverage.info &>/dev/null) |
||||
|
||||
genhtml $COVERAGE_DIR/coverage.info -o $COVERAGE_DIR &>/dev/null |
||||
|
||||
echo "HTML Coverage report is generated in $COVERAGE_DIR" |
@ -0,0 +1,118 @@ |
||||
import optparse |
||||
import re |
||||
import sys |
||||
|
||||
from optparse import OptionParser |
||||
|
||||
# the gcov report follows certain pattern. Each file will have two lines |
||||
# of report, from which we can extract the file name, total lines and coverage |
||||
# percentage. |
||||
def parse_gcov_report(gcov_input): |
||||
per_file_coverage = {} |
||||
total_coverage = None |
||||
|
||||
for line in sys.stdin: |
||||
line = line.strip() |
||||
|
||||
# --First line of the coverage report (with file name in it)? |
||||
match_obj = re.match("^File '(.*)'$", line) |
||||
if match_obj: |
||||
# fetch the file name from the first line of the report. |
||||
current_file = match_obj.group(1) |
||||
continue |
||||
|
||||
# -- Second line of the file report (with coverage percentage) |
||||
match_obj = re.match("^Lines executed:(.*)% of (.*)", line) |
||||
|
||||
if match_obj: |
||||
coverage = float(match_obj.group(1)) |
||||
lines = int(match_obj.group(2)) |
||||
|
||||
if current_file is not None: |
||||
per_file_coverage[current_file] = (coverage, lines) |
||||
current_file = None |
||||
else: |
||||
# If current_file is not set, we reach the last line of report, |
||||
# which contains the summarized coverage percentage. |
||||
total_coverage = (coverage, lines) |
||||
continue |
||||
|
||||
# If the line's pattern doesn't fall into the above categories. We |
||||
# can simply ignore them since they're either empty line or doesn't |
||||
# find executable lines of the given file. |
||||
current_file = None |
||||
|
||||
return per_file_coverage, total_coverage |
||||
|
||||
def get_option_parser(): |
||||
usage = "Parse the gcov output and generate more human-readable code " +\ |
||||
"coverage report." |
||||
parser = OptionParser(usage) |
||||
|
||||
parser.add_option( |
||||
"--interested-files", "-i", |
||||
dest="filenames", |
||||
help="Comma separated files names. if specified, we will display " + |
||||
"the coverage report only for interested source files. " + |
||||
"Otherwise we will display the coverage report for all " + |
||||
"source files." |
||||
) |
||||
return parser |
||||
|
||||
def display_file_coverage(per_file_coverage, total_coverage): |
||||
# To print out auto-adjustable column, we need to know the longest |
||||
# length of file names. |
||||
max_file_name_length = max( |
||||
len(fname) for fname in per_file_coverage.keys() |
||||
) |
||||
|
||||
# -- Print header |
||||
# size of separator is determined by 3 column sizes: |
||||
# file name, coverage percentage and lines. |
||||
header_template = \ |
||||
"%" + str(max_file_name_length) + "s\t%s\t%s" |
||||
separator = "-" * (max_file_name_length + 10 + 20) |
||||
print header_template % ("Filename", "Coverage", "Lines") |
||||
print separator |
||||
|
||||
# -- Print body |
||||
# template for printing coverage report for each file. |
||||
record_template = "%" + str(max_file_name_length) + "s\t%5.2f%%\t%10d" |
||||
|
||||
for fname, coverage_info in per_file_coverage.items(): |
||||
coverage, lines = coverage_info |
||||
print record_template % (fname, coverage, lines) |
||||
|
||||
# -- Print footer |
||||
if total_coverage: |
||||
print separator |
||||
print record_template % ("Total", total_coverage[0], total_coverage[1]) |
||||
|
||||
def report_coverage(): |
||||
parser = get_option_parser() |
||||
(options, args) = parser.parse_args() |
||||
|
||||
interested_files = set() |
||||
if options.filenames is not None: |
||||
interested_files = set(f.strip() for f in options.filenames.split(',')) |
||||
|
||||
# To make things simple, right now we only read gcov report from the input |
||||
per_file_coverage, total_coverage = parse_gcov_report(sys.stdin) |
||||
|
||||
# Check if we need to display coverage info for interested files. |
||||
if len(interested_files): |
||||
per_file_coverage = dict( |
||||
(fname, per_file_coverage[fname]) for fname in interested_files |
||||
if fname in per_file_coverage |
||||
) |
||||
# If we only interested in several files, it makes no sense to report |
||||
# the total_coverage |
||||
total_coverage = None |
||||
|
||||
if not len(per_file_coverage): |
||||
print >> sys.stderr, "Cannot find coverage info for the given files." |
||||
return |
||||
display_file_coverage(per_file_coverage, total_coverage) |
||||
|
||||
if __name__ == "__main__": |
||||
report_coverage() |
Loading…
Reference in new issue