Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(18)

Side by Side Diff: tools/parse_llvm_coverage.py

Issue 1239963002: Split llvm_coverage_run into two scripts (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Split into two scripts Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/llvm_coverage_run.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2015 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6
7 """Parse an LLVM coverage report to generate useable results."""
8
9
10 import argparse
11 import json
12 import os
13 import re
14 import subprocess
15 import sys
16
17
18 def _fix_filename(filename):
19 """Return a filename which we can use to identify the file.
20
21 The file paths printed by llvm-cov take the form:
22
23 /path/to/repo/out/dir/../../src/filename.cpp
24
25 And then they're truncated to 22 characters with leading ellipses:
26
27 ...../../src/filename.cpp
28
29 This makes it really tough to determine whether the file actually belongs in
30 the Skia repo. This function strips out the leading junk so that, if the file
31 exists in the repo, the returned string matches the end of some relative path
32 in the repo. This doesn't guarantee correctness, but it's about as close as
33 we can get.
34 """
35 return filename.split('..')[-1].lstrip('./')
36
37
38 def _file_in_repo(filename, all_files):
39 """Return the name of the checked-in file matching the given filename.
40
41 Use suffix matching to determine which checked-in files the given filename
42 matches. If there are no matches or multiple matches, return None.
43 """
44 new_file = _fix_filename(filename)
45 matched = []
46 for f in all_files:
47 if f.endswith(new_file):
48 matched.append(f)
49 if len(matched) == 1:
50 return matched[0]
51 elif len(matched) > 1:
52 print >> sys.stderr, ('WARNING: multiple matches for %s; skipping:\n\t%s'
53 % (new_file, '\n\t'.join(matched)))
54 return None
55
56
57 def _get_per_file_per_line_coverage(report):
58 """Return a dict whose keys are file names and values are coverage data.
59
60 Values are lists which take the form (lineno, coverage, code).
61 """
62 all_files = subprocess.check_output(['git', 'ls-files']).splitlines()
63 lines = report.splitlines()
64 current_file = None
65 file_lines = []
66 files = {}
67 not_checked_in = '%' # Use this as the file name for not-checked-in files.
68 for line in lines:
69 m = re.match('([a-zA-Z0-9\./_-]+):', line)
70 if m:
71 if current_file and current_file != not_checked_in:
72 files[current_file] = file_lines
73 match_filename = _file_in_repo(m.groups()[0], all_files)
74 current_file = match_filename or not_checked_in
75 file_lines = []
76 else:
77 if current_file != not_checked_in:
78 skip = re.match('^\s{2}-+$|^\s{2}\|.+$', line)
79 if line and not skip:
80 cov, linenum, code = line.split('|', 2)
81 cov = cov.strip()
82 if cov:
83 cov = int(cov)
84 else:
85 cov = None # We don't care about coverage for this line.
86 linenum = int(linenum.strip())
87 assert linenum == len(file_lines) + 1
88 file_lines.append((linenum, cov, code.decode('utf-8', 'replace')))
89 return files
90
91
92
93 def _testname(filename):
94 """Transform the file name into an ingestible test name."""
95 return re.sub(r'[^a-zA-Z0-9]', '_', filename)
96
97
98 def _nanobench_json(results, properties, key):
99 """Return the results in JSON format like that produced by nanobench."""
100 rv = {}
101 # Copy over the properties first, then set the 'key' and 'results' keys,
102 # in order to avoid bad formatting in case the user passes in a properties
103 # dict containing those keys.
104 rv.update(properties)
105 rv['key'] = key
106 rv['results'] = {
107 _testname(f): {
108 'coverage': {
109 'percent': percent,
110 'options': {
111 'fullname': f,
112 'dir': os.path.dirname(f),
113 },
114 },
115 } for percent, f in results
116 }
117 return rv
118
119
120 def _parse_key_value(kv_list):
121 """Return a dict whose key/value pairs are derived from the given list.
122
123 For example:
124
125 ['k1', 'v1', 'k2', 'v2']
126 becomes:
127
128 {'k1': 'v1',
129 'k2': 'v2'}
130 """
131 if len(kv_list) % 2 != 0:
132 raise Exception('Invalid key/value pairs: %s' % kv_list)
133
134 rv = {}
135 for i in xrange(len(kv_list) / 2):
136 rv[kv_list[i*2]] = kv_list[i*2+1]
137 return rv
138
139
140 def _get_per_file_summaries(line_by_line):
141 """Summarize the full line-by-line coverage report by file."""
142 per_file = []
143 for filepath, lines in line_by_line.iteritems():
144 total_lines = 0
145 covered_lines = 0
146 for _, cov, _ in lines:
147 if cov is not None:
148 total_lines += 1
149 if cov > 0:
150 covered_lines += 1
151 if total_lines > 0:
152 per_file.append((float(covered_lines)/float(total_lines)*100.0,
153 filepath))
154 return per_file
155
156
157 def main():
158 """Generate useful data from a coverage report."""
159 # Parse args.
160 parser = argparse.ArgumentParser()
161 parser.add_argument('--report', help='input file; an llvm coverage report.',
162 required=True)
163 parser.add_argument('--nanobench', help='output file for nanobench data.')
164 parser.add_argument(
165 '--key', metavar='key_or_value', nargs='+',
166 help='key/value pairs identifying this bot.')
167 parser.add_argument(
168 '--properties', metavar='key_or_value', nargs='+',
169 help='key/value pairs representing properties of this build.')
170 parser.add_argument('--linebyline',
171 help='output file for line-by-line JSON data.')
172 args = parser.parse_args()
173
174 if args.nanobench and not (args.key and args.properties):
175 raise Exception('--key and --properties are required with --nanobench')
176
177 with open(args.report) as f:
178 report = f.read()
179
180 line_by_line = _get_per_file_per_line_coverage(report)
181
182 if args.linebyline:
183 with open(args.linebyline, 'w') as f:
184 json.dump(line_by_line, f)
185
186 if args.nanobench:
187 # Parse the key and properties for use in the nanobench JSON output.
188 key = _parse_key_value(args.key)
189 properties = _parse_key_value(args.properties)
190
191 # Get per-file summaries.
192 per_file = _get_per_file_summaries(line_by_line)
193
194 # Write results.
195 format_results = _nanobench_json(per_file, properties, key)
196 with open(args.nanobench, 'w') as f:
197 json.dump(format_results, f)
198
199
200 if __name__ == '__main__':
201 main()
OLDNEW
« no previous file with comments | « tools/llvm_coverage_run.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698