2 #===- symcov-report-server.py - Coverage Reports HTTP Serve --*- python -*--===#
4 # The LLVM Compiler Infrastructure
6 # This file is distributed under the University of Illinois Open Source
7 # License. See LICENSE.TXT for details.
9 #===------------------------------------------------------------------------===#
10 '''(EXPERIMENTAL) HTTP server to browse coverage reports from .symcov files.
12 Coverage reports for big binaries are too huge, generating them statically
13 makes no sense. Start the server and go to localhost:8001 instead.
16 ./tools/sancov/symcov-report-server.py \
17 --symcov coverage_data.symcov \
18 --srcpath root_src_dir
21 --port port_number - specifies the port to use (8001)
22 --host host_name - host name to bind server to (127.0.0.1)
38 <title>Coverage Report</title>
40 .lz { color: lightgray; }
45 <tr><th>File</th><th>Coverage</th></tr>
46 <tr><td><em>Files with 0 coverage are not shown.</em></td></tr>
53 CONTENT_PAGE_TMPL
= """
58 .covered { background: lightgreen; }
59 .not-covered { background: lightcoral; }
60 .partially-covered { background: navajowhite; }
61 .lz { color: lightgray; }
73 def __init__(self
, symcov_json
):
74 self
.covered_points
= frozenset(symcov_json
['covered-points'])
75 self
.point_symbol_info
= symcov_json
['point-symbol-info']
76 self
.file_coverage
= self
.compute_filecoverage()
79 return self
.point_symbol_info
.keys()
81 def has_file(self
, filename
):
82 return filename
in self
.point_symbol_info
84 def compute_linemap(self
, filename
):
85 """Build a line_number->css_class map."""
86 points
= self
.point_symbol_info
.get(filename
, dict())
88 line_to_points
= dict()
89 for fn
, points
in points
.items():
90 for point
, loc
in points
.items():
91 line
= int(loc
.split(":")[0])
92 line_to_points
.setdefault(line
, []).append(point
)
95 for line
, points
in line_to_points
.items():
97 covered_points
= self
.covered_points
& set(points
)
98 if not len(covered_points
):
99 status
= "not-covered"
100 elif len(covered_points
) != len(points
):
101 status
= "partially-covered"
102 result
[line
] = status
105 def compute_filecoverage(self
):
106 """Build a filename->pct coverage."""
108 for filename
, fns
in self
.point_symbol_info
.items():
110 for fn
, points
in fns
.items():
111 file_points
.extend(points
.keys())
112 covered_points
= self
.covered_points
& set(file_points
)
113 result
[filename
] = int(math
.ceil(
114 len(covered_points
) * 100 / len(file_points
)))
119 pct_str
= str(max(0, min(100, pct
)))
120 zeroes
= '0' * (3 - len(pct_str
))
122 zeroes
= '<span class="lz">{0}</span>'.format(zeroes
)
123 return zeroes
+ pct_str
125 class ServerHandler(http
.server
.BaseHTTPRequestHandler
):
131 self
.send_response(200)
132 self
.send_header("Content-type", "text/html; charset=utf-8")
136 for filename
in sorted(self
.symcov_data
.filenames()):
137 file_coverage
= self
.symcov_data
.file_coverage
[filename
]
138 if not file_coverage
:
141 "<tr><td><a href=\"./{name}\">{name}</a></td>"
142 "<td>{coverage}%</td></tr>".format(
143 name
=html
.escape(filename
, quote
=True),
144 coverage
=format_pct(file_coverage
)))
146 response
= string
.Template(INDEX_PAGE_TMPL
).safe_substitute(
147 filenames
='\n'.join(filelist
))
148 self
.wfile
.write(response
.encode('UTF-8', 'replace'))
149 elif self
.symcov_data
.has_file(self
.path
[1:]):
150 filename
= self
.path
[1:]
151 filepath
= os
.path
.join(self
.src_path
, filename
)
152 if not os
.path
.exists(filepath
):
153 self
.send_response(404)
157 self
.send_response(200)
158 self
.send_header("Content-type", "text/html; charset=utf-8")
161 linemap
= self
.symcov_data
.compute_linemap(filename
)
163 with
open(filepath
, 'r', encoding
='utf8') as f
:
165 ["<span class='{cls}'>{line} </span>".format(
166 line
=html
.escape(line
.rstrip()),
167 cls
=linemap
.get(line_no
, ""))
168 for line_no
, line
in enumerate(f
, start
=1)])
170 response
= string
.Template(CONTENT_PAGE_TMPL
).safe_substitute(
174 self
.wfile
.write(response
.encode('UTF-8', 'replace'))
176 self
.send_response(404)
181 parser
= argparse
.ArgumentParser(description
="symcov report http server.")
182 parser
.add_argument('--host', default
='127.0.0.1')
183 parser
.add_argument('--port', default
=8001)
184 parser
.add_argument('--symcov', required
=True, type=argparse
.FileType('r'))
185 parser
.add_argument('--srcpath', required
=True)
186 args
= parser
.parse_args()
188 print("Loading coverage...")
189 symcov_json
= json
.load(args
.symcov
)
190 ServerHandler
.symcov_data
= SymcovData(symcov_json
)
191 ServerHandler
.src_path
= args
.srcpath
193 socketserver
.TCPServer
.allow_reuse_address
= True
194 httpd
= socketserver
.TCPServer((args
.host
, args
.port
), ServerHandler
)
195 print("Serving at {host}:{port}".format(host
=args
.host
, port
=args
.port
))
197 httpd
.serve_forever()
198 except KeyboardInterrupt:
202 if __name__
== '__main__':