brintos

brintos / llvm-project-archived public Read only

0
0
Text · 6.8 KiB · 7b0b494 Raw
223 lines · python
1#!/usr/bin/env python32# ===- symcov-report-server.py - Coverage Reports HTTP Serve --*- python -*--===#3#4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.5# See https://llvm.org/LICENSE.txt for license information.6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception7#8# ===------------------------------------------------------------------------===#9"""(EXPERIMENTAL) HTTP server to browse coverage reports from .symcov files.10 11Coverage reports for big binaries are too huge, generating them statically12makes no sense. Start the server and go to localhost:8001 instead.13 14Usage:15    ./tools/sancov/symcov-report-server.py \16            --symcov coverage_data.symcov \17            --srcpath root_src_dir18 19Other options:20    --port port_number - specifies the port to use (8001)21    --host host_name - host name to bind server to (127.0.0.1)22"""23 24from __future__ import print_function25 26import argparse27import http.server28import json29import socketserver30import time31import html32import os33import string34import math35import urllib36 37INDEX_PAGE_TMPL = """38<html>39<head>40  <title>Coverage Report</title>41  <style>42    .lz { color: lightgray; }43  </style>44</head>45<body>46    <table>47      <tr><th>File</th><th>Coverage</th></tr>48      <tr><td><em>Files with 0 coverage are not shown.</em></td></tr>49$filenames50    </table>51</body>52</html>53"""54 55CONTENT_PAGE_TMPL = """56<html>57<head>58  <title>$path</title>59  <style>60    .covered { background: lightgreen; }61    .not-covered { background: lightcoral; }62    .partially-covered { background: navajowhite; }63    .lz { color: lightgray; }64  </style>65</head>66<body>67<pre>68$content69</pre>70</body>71</html>72"""73 74FILE_URI_PREFIX = "/file/"75 76 77class SymcovData:78    def __init__(self, symcov_json):79        self.covered_points = frozenset(symcov_json["covered-points"])80        self.point_symbol_info = symcov_json["point-symbol-info"]81        self.file_coverage = self.compute_filecoverage()82 83    def filenames(self):84        return self.point_symbol_info.keys()85 86    def has_file(self, filename):87        return filename in self.point_symbol_info88 89    def compute_linemap(self, filename):90        """Build a line_number->css_class map."""91        points = self.point_symbol_info.get(filename, dict())92 93        line_to_points = dict()94        for fn, points in points.items():95            for point, loc in points.items():96                line = int(loc.split(":")[0])97                line_to_points.setdefault(line, []).append(point)98 99        result = dict()100        for line, points in line_to_points.items():101            status = "covered"102            covered_points = self.covered_points & set(points)103            if not len(covered_points):104                status = "not-covered"105            elif len(covered_points) != len(points):106                status = "partially-covered"107            result[line] = status108        return result109 110    def compute_filecoverage(self):111        """Build a filename->pct coverage."""112        result = dict()113        for filename, fns in self.point_symbol_info.items():114            file_points = []115            for fn, points in fns.items():116                file_points.extend(points.keys())117            covered_points = self.covered_points & set(file_points)118            result[filename] = int(119                math.ceil(len(covered_points) * 100 / len(file_points))120            )121        return result122 123 124def format_pct(pct):125    pct_str = str(max(0, min(100, pct)))126    zeroes = "0" * (3 - len(pct_str))127    if zeroes:128        zeroes = '<span class="lz">{0}</span>'.format(zeroes)129    return zeroes + pct_str130 131 132class ServerHandler(http.server.BaseHTTPRequestHandler):133    symcov_data = None134    src_path = None135 136    def do_GET(self):137        norm_path = os.path.normpath(138            urllib.parse.unquote(self.path[len(FILE_URI_PREFIX) :])139        )140        if self.path == "/":141            self.send_response(200)142            self.send_header("Content-type", "text/html; charset=utf-8")143            self.end_headers()144 145            filelist = []146            for filename in sorted(self.symcov_data.filenames()):147                file_coverage = self.symcov_data.file_coverage[filename]148                if not file_coverage:149                    continue150                filelist.append(151                    '<tr><td><a href="{prefix}{name}">{name}</a></td>'152                    "<td>{coverage}%</td></tr>".format(153                        prefix=FILE_URI_PREFIX,154                        name=html.escape(filename, quote=True),155                        coverage=format_pct(file_coverage),156                    )157                )158 159            response = string.Template(INDEX_PAGE_TMPL).safe_substitute(160                filenames="\n".join(filelist)161            )162            self.wfile.write(response.encode("UTF-8", "replace"))163        elif self.symcov_data.has_file(norm_path):164            filename = norm_path165            filepath = os.path.join(self.src_path, filename)166            if not os.path.exists(filepath):167                self.send_response(404)168                self.end_headers()169                return170 171            self.send_response(200)172            self.send_header("Content-type", "text/html; charset=utf-8")173            self.end_headers()174 175            linemap = self.symcov_data.compute_linemap(filename)176 177            with open(filepath, "r", encoding="utf8") as f:178                content = "\n".join(179                    [180                        "<span class='{cls}'>{line}&nbsp;</span>".format(181                            line=html.escape(line.rstrip()),182                            cls=linemap.get(line_no, ""),183                        )184                        for line_no, line in enumerate(f, start=1)185                    ]186                )187 188            response = string.Template(CONTENT_PAGE_TMPL).safe_substitute(189                path=self.path[1:], content=content190            )191 192            self.wfile.write(response.encode("UTF-8", "replace"))193        else:194            self.send_response(404)195            self.end_headers()196 197 198def main():199    parser = argparse.ArgumentParser(description="symcov report http server.")200    parser.add_argument("--host", default="127.0.0.1")201    parser.add_argument("--port", default=8001)202    parser.add_argument("--symcov", required=True, type=argparse.FileType("r"))203    parser.add_argument("--srcpath", required=True)204    args = parser.parse_args()205 206    print("Loading coverage...")207    symcov_json = json.load(args.symcov)208    ServerHandler.symcov_data = SymcovData(symcov_json)209    ServerHandler.src_path = args.srcpath210 211    socketserver.TCPServer.allow_reuse_address = True212    httpd = socketserver.TCPServer((args.host, args.port), ServerHandler)213    print("Serving at {host}:{port}".format(host=args.host, port=args.port))214    try:215        httpd.serve_forever()216    except KeyboardInterrupt:217        pass218    httpd.server_close()219 220 221if __name__ == "__main__":222    main()223