Index: tools/llvm_coverage_run.py |
diff --git a/tools/llvm_coverage_run.py b/tools/llvm_coverage_run.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..902b1c6b04f1833dc3b6d48c5bfa919f3311bb16 |
--- /dev/null |
+++ b/tools/llvm_coverage_run.py |
@@ -0,0 +1,107 @@ |
+#!/usr/bin/env python |
+# Copyright (c) 2015 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+ |
+"""Run the given command through LLVM's coverage tools.""" |
+ |
+ |
+import argparse |
+import json |
+import os |
+import shlex |
+import subprocess |
+import sys |
+ |
+ |
+BUILDTYPE = 'Coverage' |
+OUT_DIR = os.path.realpath(os.path.join('out', BUILDTYPE)) |
+PROFILE_DATA = 'default.profraw' |
+PROFILE_DATA_MERGED = 'prof_merged' |
+ |
+ |
+def _fix_filename(filename): |
+ """Return a filename which we can use to identify the file. |
+ |
+ The file paths printed by llvm-cov take the form: |
+ |
+ /path/to/repo/out/dir/../../src/filename.cpp |
+ |
+ And then they're truncated to 22 characters with leading ellipses: |
+ |
+ ...../../src/filename.cpp |
+ |
+ This makes it really tough to determine whether the file actually belongs in |
+ the Skia repo. This function strips out the leading junk so that, if the file |
+ exists in the repo, the returned string matches the end of some relative path |
+ in the repo. This doesn't guarantee correctness, but it's about as close as |
+ we can get. |
+ """ |
+ return filename.split('..')[-1].lstrip('./') |
+ |
+ |
+def _filter_results(results): |
+ """Filter out any results for files not in the Skia repo. |
+ |
+ We run through the list of checked-in files and determine whether each file |
+ belongs in the repo. Unfortunately, llvm-cov leaves us with fragments of the |
+ file paths, so we can't guarantee accuracy. See the docstring for |
+ _fix_filename for more details. |
+ """ |
+ all_files = subprocess.check_output(['git', 'ls-files']).splitlines() |
+ filtered = [] |
+ for percent, filename in results: |
+ new_file = _fix_filename(filename) |
+ matched = [] |
+ for f in all_files: |
+ if f.endswith(new_file): |
+ matched.append(f) |
+ if len(matched) == 1: |
+ filtered.append((percent, matched[0])) |
+ elif len(matched) > 1: |
+ print >> sys.stderr, ('WARNING: multiple matches for %s; skipping:\n\t%s' |
+ % (new_file, '\n\t'.join(matched))) |
+ print 'Filtered out %d files.' % (len(results) - len(filtered)) |
+ return filtered |
borenet
2015/07/01 20:06:51
The right solution here is to submit a change to L
mtklein
2015/07/01 20:17:37
I wonder how basename.startswith('Sk') works as ou
borenet
2015/07/06 12:06:35
Maybe. The biggest problem with that is we'll mis
|
+ |
+ |
+def run_coverage(cmd): |
+ """Run the given command and return per-file coverage data. |
+ |
+ Assumes that the binary has been built using llvm_coverage_build and that |
+ LLVM 3.6 or newer is installed. |
+ """ |
+ binary_path = os.path.join(OUT_DIR, cmd[0]) |
+ subprocess.call([binary_path] + cmd[1:]) |
+ try: |
+ subprocess.check_call( |
+ ['llvm-profdata', 'merge', PROFILE_DATA, |
+ '-output=%s' % PROFILE_DATA_MERGED]) |
+ finally: |
+ os.remove(PROFILE_DATA) |
+ try: |
+ report = subprocess.check_output( |
+ ['llvm-cov', 'report', '-instr-profile', PROFILE_DATA_MERGED, |
+ binary_path]) |
+ finally: |
+ os.remove(PROFILE_DATA_MERGED) |
+ results = [] |
+ for line in report.splitlines()[2:-2]: |
+ filename, _, _, cover, _, _ = shlex.split(line) |
+ percent = float(cover.split('%')[0]) |
+ results.append((percent, filename)) |
+ results = _filter_results(results) |
+ results.sort() |
+ return results |
+ |
+ |
+def main(): |
+ res = run_coverage(sys.argv[1:]) |
+ print '% Covered\tFilename' |
+ for percent, f in res: |
mtklein
2015/07/01 20:17:37
Can we get a line-by-line breakdown or some sort o
borenet
2015/07/06 12:06:35
`llvm-cov show` generates a giant file which shows
|
+ print '%f\t%s' % (percent, f) |
+ |
+ |
+if __name__ == '__main__': |
+ main() |