123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- #!/usr/bin/env python3
- #===- symcov-report-server.py - Coverage Reports HTTP Serve --*- python -*--===#
- #
- # The LLVM Compiler Infrastructure
- #
- # This file is distributed under the University of Illinois Open Source
- # License. See LICENSE.TXT for details.
- #
- #===------------------------------------------------------------------------===#
- '''(EXPERIMENTAL) HTTP server to browse coverage reports from .symcov files.
- Coverage reports for big binaries are too huge, generating them statically
- makes no sense. Start the server and go to localhost:8001 instead.
- Usage:
- ./tools/sancov/symcov-report-server.py \
- --symcov coverage_data.symcov \
- --srcpath root_src_dir
- Other options:
- --port port_number - specifies the port to use (8001)
- --host host_name - host name to bind server to (127.0.0.1)
- '''
- from __future__ import print_function
- import argparse
- import http.server
- import json
- import socketserver
- import time
- import html
- import os
- import string
- import math
- INDEX_PAGE_TMPL = """
- <html>
- <head>
- <title>Coverage Report</title>
- <style>
- .lz { color: lightgray; }
- </style>
- </head>
- <body>
- <table>
- <tr><th>File</th><th>Coverage</th></tr>
- <tr><td><em>Files with 0 coverage are not shown.</em></td></tr>
- $filenames
- </table>
- </body>
- </html>
- """
- CONTENT_PAGE_TMPL = """
- <html>
- <head>
- <title>$path</title>
- <style>
- .covered { background: lightgreen; }
- .not-covered { background: lightcoral; }
- .partially-covered { background: navajowhite; }
- .lz { color: lightgray; }
- </style>
- </head>
- <body>
- <pre>
- $content
- </pre>
- </body>
- </html>
- """
- class SymcovData:
- def __init__(self, symcov_json):
- self.covered_points = frozenset(symcov_json['covered-points'])
- self.point_symbol_info = symcov_json['point-symbol-info']
- self.file_coverage = self.compute_filecoverage()
- def filenames(self):
- return self.point_symbol_info.keys()
- def has_file(self, filename):
- return filename in self.point_symbol_info
- def compute_linemap(self, filename):
- """Build a line_number->css_class map."""
- points = self.point_symbol_info.get(filename, dict())
- line_to_points = dict()
- for fn, points in points.items():
- for point, loc in points.items():
- line = int(loc.split(":")[0])
- line_to_points.setdefault(line, []).append(point)
- result = dict()
- for line, points in line_to_points.items():
- status = "covered"
- covered_points = self.covered_points & set(points)
- if not len(covered_points):
- status = "not-covered"
- elif len(covered_points) != len(points):
- status = "partially-covered"
- result[line] = status
- return result
- def compute_filecoverage(self):
- """Build a filename->pct coverage."""
- result = dict()
- for filename, fns in self.point_symbol_info.items():
- file_points = []
- for fn, points in fns.items():
- file_points.extend(points.keys())
- covered_points = self.covered_points & set(file_points)
- result[filename] = int(math.ceil(
- len(covered_points) * 100 / len(file_points)))
- return result
- def format_pct(pct):
- pct_str = str(max(0, min(100, pct)))
- zeroes = '0' * (3 - len(pct_str))
- if zeroes:
- zeroes = '<span class="lz">{0}</span>'.format(zeroes)
- return zeroes + pct_str
- class ServerHandler(http.server.BaseHTTPRequestHandler):
- symcov_data = None
- src_path = None
- def do_GET(self):
- if self.path == '/':
- self.send_response(200)
- self.send_header("Content-type", "text/html; charset=utf-8")
- self.end_headers()
- filelist = []
- for filename in sorted(self.symcov_data.filenames()):
- file_coverage = self.symcov_data.file_coverage[filename]
- if not file_coverage:
- continue
- filelist.append(
- "<tr><td><a href=\"./{name}\">{name}</a></td>"
- "<td>{coverage}%</td></tr>".format(
- name=html.escape(filename, quote=True),
- coverage=format_pct(file_coverage)))
- response = string.Template(INDEX_PAGE_TMPL).safe_substitute(
- filenames='\n'.join(filelist))
- self.wfile.write(response.encode('UTF-8', 'replace'))
- elif self.symcov_data.has_file(self.path[1:]):
- filename = self.path[1:]
- filepath = os.path.join(self.src_path, filename)
- if not os.path.exists(filepath):
- self.send_response(404)
- self.end_headers()
- return
- self.send_response(200)
- self.send_header("Content-type", "text/html; charset=utf-8")
- self.end_headers()
- linemap = self.symcov_data.compute_linemap(filename)
- with open(filepath, 'r', encoding='utf8') as f:
- content = "\n".join(
- ["<span class='{cls}'>{line} </span>".format(
- line=html.escape(line.rstrip()),
- cls=linemap.get(line_no, ""))
- for line_no, line in enumerate(f, start=1)])
- response = string.Template(CONTENT_PAGE_TMPL).safe_substitute(
- path=self.path[1:],
- content=content)
- self.wfile.write(response.encode('UTF-8', 'replace'))
- else:
- self.send_response(404)
- self.end_headers()
- def main():
- parser = argparse.ArgumentParser(description="symcov report http server.")
- parser.add_argument('--host', default='127.0.0.1')
- parser.add_argument('--port', default=8001)
- parser.add_argument('--symcov', required=True, type=argparse.FileType('r'))
- parser.add_argument('--srcpath', required=True)
- args = parser.parse_args()
- print("Loading coverage...")
- symcov_json = json.load(args.symcov)
- ServerHandler.symcov_data = SymcovData(symcov_json)
- ServerHandler.src_path = args.srcpath
- socketserver.TCPServer.allow_reuse_address = True
- httpd = socketserver.TCPServer((args.host, args.port), ServerHandler)
- print("Serving at {host}:{port}".format(host=args.host, port=args.port))
- try:
- httpd.serve_forever()
- except KeyboardInterrupt:
- pass
- httpd.server_close()
- if __name__ == '__main__':
- main()
|