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