1 |
2 |
jamieiles |
#!/usr/bin/env python
|
2 |
|
|
|
3 |
|
|
# Copyright Jamie Iles, 2017
|
4 |
|
|
#
|
5 |
|
|
# This file is part of s80x86.
|
6 |
|
|
#
|
7 |
|
|
# s80x86 is free software: you can redistribute it and/or modify
|
8 |
|
|
# it under the terms of the GNU General Public License as published by
|
9 |
|
|
# the Free Software Foundation, either version 3 of the License, or
|
10 |
|
|
# (at your option) any later version.
|
11 |
|
|
#
|
12 |
|
|
# s80x86 is distributed in the hope that it will be useful,
|
13 |
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14 |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15 |
|
|
# GNU General Public License for more details.
|
16 |
|
|
#
|
17 |
|
|
# You should have received a copy of the GNU General Public License
|
18 |
|
|
# along with s80x86. If not, see .
|
19 |
|
|
|
20 |
|
|
import argparse
|
21 |
|
|
import sys
|
22 |
|
|
import os.path
|
23 |
|
|
import time
|
24 |
|
|
|
25 |
|
|
from collections import namedtuple, defaultdict
|
26 |
|
|
from xml.dom import minidom
|
27 |
|
|
from xml.etree.ElementTree import Element, SubElement, tostring
|
28 |
|
|
|
29 |
|
|
def get_coverage(filename):
|
30 |
|
|
CoveragePoint = namedtuple('CoveragePoint', ['file', 'line', 'count'])
|
31 |
|
|
FIELD_SPLIT = '\x01'
|
32 |
|
|
KEY_VALUE_SPLIT = '\x02'
|
33 |
|
|
|
34 |
|
|
def parse_point(point, count):
|
35 |
|
|
filename = None
|
36 |
|
|
line = None
|
37 |
|
|
|
38 |
|
|
for pair in point:
|
39 |
|
|
if not KEY_VALUE_SPLIT in pair:
|
40 |
|
|
continue
|
41 |
|
|
key, value = pair.split(KEY_VALUE_SPLIT)
|
42 |
|
|
if key == 'f':
|
43 |
|
|
filename = value
|
44 |
|
|
if key == 'l':
|
45 |
|
|
line = value
|
46 |
|
|
|
47 |
|
|
if not filename or not line:
|
48 |
|
|
return None
|
49 |
|
|
|
50 |
|
|
return CoveragePoint(filename, line, int(count))
|
51 |
|
|
|
52 |
|
|
def parse_coverage(filename):
|
53 |
|
|
with open(filename) as coverage:
|
54 |
|
|
coverage_lines = filter(lambda x: x.startswith('C'), coverage.readlines())
|
55 |
|
|
|
56 |
|
|
coverage = defaultdict(lambda: defaultdict(int))
|
57 |
|
|
|
58 |
|
|
for l in coverage_lines:
|
59 |
|
|
line_type, data, count = l.split()
|
60 |
|
|
entry_dict = data.replace("'", "").split(FIELD_SPLIT)
|
61 |
|
|
point = parse_point(entry_dict, count)
|
62 |
|
|
if point:
|
63 |
|
|
coverage[point.file][point.line] += point.count
|
64 |
|
|
|
65 |
|
|
return coverage
|
66 |
|
|
|
67 |
|
|
return parse_coverage(filename)
|
68 |
|
|
|
69 |
|
|
def verilator_to_cobertura(coverage_info):
|
70 |
|
|
line_rates = {}
|
71 |
|
|
global_line_rate = 0.0
|
72 |
|
|
|
73 |
|
|
total_lines = 0.0
|
74 |
|
|
covered_lines = 0.0
|
75 |
|
|
for filename, lines in coverage_info.items():
|
76 |
|
|
file_covered_lines = 0.0
|
77 |
|
|
for line, count in lines.items():
|
78 |
|
|
total_lines += 1
|
79 |
|
|
if count > 0:
|
80 |
|
|
file_covered_lines += 1
|
81 |
|
|
covered_lines += 1
|
82 |
|
|
line_rates[filename] = file_covered_lines / len(lines)
|
83 |
|
|
global_line_rate = 0.0 if not total_lines else covered_lines / total_lines
|
84 |
|
|
|
85 |
|
|
coverage = Element('coverage', attrib={
|
86 |
|
|
'branch-rate': '0.0',
|
87 |
|
|
'complexity': '0.0',
|
88 |
|
|
'line-rate': str(global_line_rate),
|
89 |
|
|
'timestamp': str(int(time.time())),
|
90 |
|
|
'version': 'verilator-cobertura-1',
|
91 |
|
|
})
|
92 |
|
|
sources = SubElement(coverage, 'sources')
|
93 |
|
|
source = SubElement(sources, 'source')
|
94 |
|
|
source.text = args.source_root
|
95 |
|
|
packages = SubElement(coverage, 'packages')
|
96 |
|
|
package = SubElement(packages, 'package', attrib={
|
97 |
|
|
'name': 'verilator',
|
98 |
|
|
'branch-rate': '0.0',
|
99 |
|
|
'complexity': '0.0',
|
100 |
|
|
'line-rate': str(global_line_rate),
|
101 |
|
|
})
|
102 |
|
|
classes = SubElement(package, 'classes')
|
103 |
|
|
for filename, lines in coverage_info.items():
|
104 |
|
|
f = SubElement(classes, 'class', attrib={
|
105 |
|
|
'filename': os.path.relpath(filename, args.source_root),
|
106 |
|
|
'name': os.path.basename(filename).replace('.', '_'),
|
107 |
|
|
'line-rate': str(line_rates[filename]),
|
108 |
|
|
'branch-rate': '0.0',
|
109 |
|
|
'complexity': '0.0',
|
110 |
|
|
})
|
111 |
|
|
lines_elem = SubElement(f, 'lines')
|
112 |
|
|
for line in sorted(lines.keys(), key=lambda x: int(x)):
|
113 |
|
|
count = lines[line]
|
114 |
|
|
# Maximum of 31-bits so that cobertura plugin doesn't get sad.
|
115 |
|
|
# With toggles etc this can get pretty large.
|
116 |
|
|
if count > 0x7fffffff:
|
117 |
|
|
count = 0x7fffffff
|
118 |
|
|
l = SubElement(lines_elem, 'line',
|
119 |
|
|
attrib={'hits': str(count), 'number': str(line), 'branch': 'false'})
|
120 |
|
|
|
121 |
|
|
return minidom.parseString(tostring(coverage)).toprettyxml(indent=" ").replace('', '\n')
|
122 |
|
|
|
123 |
|
|
parser = argparse.ArgumentParser()
|
124 |
|
|
parser.add_argument('--source-root', default='/')
|
125 |
|
|
parser.add_argument('--output', default='-')
|
126 |
|
|
parser.add_argument('coverage_file')
|
127 |
|
|
args = parser.parse_args()
|
128 |
|
|
|
129 |
|
|
coverage_info = get_coverage(args.coverage_file)
|
130 |
|
|
report = verilator_to_cobertura(coverage_info)
|
131 |
|
|
|
132 |
|
|
if args.output == '-':
|
133 |
|
|
print report
|
134 |
|
|
else:
|
135 |
|
|
with open(args.output, 'w') as outfile:
|
136 |
|
|
outfile.write(report)
|