7"""Parse an LLVM coverage report to generate useable results."""
18def _fix_filename(filename):
19 """Return a filename which we can use to identify the file.
21 The file paths printed by llvm-cov take the form:
23 /path/to/repo/out/dir/../../src/filename.cpp
25 And then they're truncated to 22 characters with leading ellipses:
27 ...../../src/filename.cpp
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
35 return filename.split(
'..')[-1].lstrip(
'./')
38def _file_in_repo(filename, all_files):
39 """Return the name of the checked-in file matching the given filename.
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.
44 new_file = _fix_filename(filename)
47 if f.endswith(new_file):
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)))
57def _get_per_file_per_line_coverage(report):
58 """Return a dict whose keys are file names and values are coverage data.
60 Values are lists which take the form (lineno, coverage, code).
63 for root, dirs, files
in os.walk(os.getcwd()):
64 if 'third_party/externals' in root:
66 files = [f
for f
in files
if not (f[0] ==
'.' or f.endswith(
'.pyc'))]
67 dirs[:] = [d
for d
in dirs
if not d[0] ==
'.']
69 all_files.append(os.path.join(root[(
len(os.getcwd()) + 1):], name))
72 lines = report.splitlines()
78 m = re.match(
'([a-zA-Z0-9\./_-]+):', line)
80 if current_file
and current_file != not_checked_in:
81 files[current_file] = file_lines
82 match_filename = _file_in_repo(m.groups()[0], all_files)
83 current_file = match_filename
or not_checked_in
86 if current_file != not_checked_in:
87 skip = re.match(
'^\s{2}-+$|^\s{2}\|.+$', line)
89 cov, linenum, code = line.split(
'|', 2)
95 linenum =
int(linenum.strip())
96 assert linenum ==
len(file_lines) + 1
97 file_lines.append((linenum, cov, code.decode(
'utf-8',
'replace')))
102def _testname(filename):
103 """Transform the file name into an ingestible test name."""
104 return re.sub(
r'[^a-zA-Z0-9]',
'_', filename)
107def _nanobench_json(results, properties, key):
108 """Return the results in JSON format like that produced by nanobench."""
113 rv.update(properties)
119 'lines_not_covered': not_covered_lines,
122 'dir': os.path.dirname(f),
123 'source_type':
'coverage',
126 }
for percent, not_covered_lines, f
in results
131def _parse_key_value(kv_list):
132 """Return a dict whose key/value pairs are derived from the given list.
136 ['k1',
'v1',
'k2',
'v2']
142 if len(kv_list) % 2 != 0:
143 raise Exception(
'Invalid key/value pairs: %s' % kv_list)
146 for i
in xrange(
len(kv_list) / 2):
147 rv[kv_list[i*2]] = kv_list[i*2+1]
151def _get_per_file_summaries(line_by_line):
152 """Summarize the full line-by-line coverage report by file."""
154 for filepath, lines
in line_by_line.iteritems():
157 for _, cov, _
in lines:
163 per_file.append((
float(covered_lines)/
float(total_lines)*100.0,
164 total_lines - covered_lines,
170 """Generate useful data from a coverage report."""
172 parser = argparse.ArgumentParser()
173 parser.add_argument(
'--report', help=
'input file; an llvm coverage report.',
175 parser.add_argument(
'--nanobench', help=
'output file for nanobench data.')
177 '--key', metavar=
'key_or_value', nargs=
'+',
178 help=
'key/value pairs identifying this bot.')
180 '--properties', metavar=
'key_or_value', nargs=
'+',
181 help=
'key/value pairs representing properties of this build.')
182 parser.add_argument(
'--linebyline',
183 help=
'output file for line-by-line JSON data.')
184 args = parser.parse_args()
186 if args.nanobench
and not (args.key
and args.properties):
187 raise Exception(
'--key and --properties are required with --nanobench')
189 with open(args.report)
as f:
192 line_by_line = _get_per_file_per_line_coverage(report)
195 with open(args.linebyline,
'w')
as f:
196 json.dump(line_by_line, f)
200 key = _parse_key_value(args.key)
201 properties = _parse_key_value(args.properties)
204 per_file = _get_per_file_summaries(line_by_line)
207 format_results = _nanobench_json(per_file, properties, key)
208 with open(args.nanobench,
'w')
as f:
209 json.dump(format_results, f)
212if __name__ ==
'__main__':
static SkString join(const CommandLineFlags::StringArray &)