forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			204 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
#!/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)
 | 
						|
'''
 | 
						|
 | 
						|
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') 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)])
 | 
						|
 | 
						|
            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()
 |