webengine/osswebengine/WebKitTools/CodeCoverage/regenerate-coverage-display
changeset 0 dd21522fd290
equal deleted inserted replaced
-1:000000000000 0:dd21522fd290
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright (C) 2004, 2005, 2006 Nathaniel Smith
       
     4 # Copyright (C) 2007 Holger Hans Peter Freyther
       
     5 #
       
     6 # Redistribution and use in source and binary forms, with or without
       
     7 # modification, are permitted provided that the following conditions
       
     8 # are met:
       
     9 #
       
    10 # 1.  Redistributions of source code must retain the above copyright
       
    11 #     notice, this list of conditions and the following disclaimer. 
       
    12 # 2.  Redistributions in binary form must reproduce the above copyright
       
    13 #     notice, this list of conditions and the following disclaimer in the
       
    14 #     documentation and/or other materials provided with the distribution. 
       
    15 # 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
       
    16 #     its contributors may be used to endorse or promote products derived
       
    17 #     from this software without specific prior written permission. 
       
    18 #
       
    19 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
       
    20 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
    21 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
       
    22 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
       
    23 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
       
    24 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
       
    25 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
       
    26 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
       
    28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    29 
       
    30 #
       
    31 # HTML output inspired by the output of lcov as found on the GStreamer
       
    32 # site. I assume this is not copyrightable.
       
    33 #
       
    34 
       
    35 
       
    36 #
       
    37 # Read all CSV files and
       
    38 #  Create an overview file
       
    39 #
       
    40 #
       
    41 
       
    42 
       
    43 import sys
       
    44 import csv
       
    45 import glob
       
    46 import time
       
    47 import os
       
    48 import os.path
       
    49 import datetime
       
    50 import shutil
       
    51 
       
    52 os.environ["TTFPATH"] = ":".join(["/usr/share/fonts/truetype/" + d
       
    53                                   for d in "ttf-bitstream-vera",
       
    54                                            "freefont",
       
    55                                            "msttcorefonts"])
       
    56 import matplotlib
       
    57 matplotlib.use("Agg")
       
    58 import matplotlib.pylab as m
       
    59 
       
    60 level_LOW    = 10
       
    61 level_MEDIUM = 70
       
    62 
       
    63 def copy_files(dest_dir):
       
    64     """
       
    65     Copy the CSS and the png's to the destination directory
       
    66     """
       
    67     images = ["amber.png", "emerald.png", "glass.png", "ruby.png", "snow.png"]
       
    68     css    = "gcov.css"
       
    69     (base_path, name) = os.path.split(__file__)
       
    70     base_path = os.path.abspath(base_path)
       
    71 
       
    72     shutil.copyfile(os.path.join(base_path,css), os.path.join(dest_dir,css))
       
    73     map(lambda x: shutil.copyfile(os.path.join(base_path,x), os.path.join(dest_dir,x)), images)
       
    74 
       
    75 def sumcov(cov):
       
    76         return "%.2f%% (%s/%s)" % (cov[1] * 100.0 / (cov[0] or 1), cov[1], cov[0])
       
    77 
       
    78 def create_page(dest_dir, name):
       
    79     index = open(os.path.join(dest_dir, name), "w")
       
    80     index.write("""<HTML>
       
    81     <HEAD>
       
    82         <TITLE>WebKit test coverage information</TITLE>
       
    83         <link rel="stylesheet" type="text/css" href="gcov.css">
       
    84     </HEAD>
       
    85     <BODY>
       
    86     """)
       
    87     return index
       
    88 
       
    89 def generate_header(file, last_time, total_lines, total_executed, path, image):
       
    90     product = "WebKit"
       
    91     date = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(last_time))
       
    92     covered_lines = sumcov((total_lines, total_executed))
       
    93 
       
    94     file.write("""<table width="100%%" border=0 cellspacing=0 cellpadding=0>
       
    95     <tr><td class="title">GCOV code coverage report</td></tr>
       
    96     <tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
       
    97 
       
    98     <tr>
       
    99       <td width="100%%">
       
   100         <table cellpadding=1 border=0 width="100%%">
       
   101         <tr>
       
   102           <td class="headerItem" width="20%%">Current&nbsp;view:</td>
       
   103           <td class="headerValue" width="80%%" colspan=4>%(path)s</td>
       
   104         </tr>
       
   105         <tr>
       
   106           <td class="headerItem" width="20%%">Test:</td>
       
   107           <td class="headerValue" width="80%%" colspan=4>%(product)s</td>
       
   108         </tr>
       
   109         <tr>
       
   110           <td class="headerItem" width="20%%">Date:</td>
       
   111           <td class="headerValue" width="20%%">%(date)s</td>
       
   112           <td width="20%%"></td>
       
   113           <td class="headerItem" width="20%%">Instrumented&nbsp;lines:</td>
       
   114           <td class="headerValue" width="20%%">%(total_lines)s</td>
       
   115         </tr>
       
   116         <tr>
       
   117           <td class="headerItem" width="20%%">Code&nbsp;covered:</td>
       
   118           <td class="headerValue" width="20%%">%(covered_lines)s</td>
       
   119           <td width="20%%"></td>
       
   120           <td class="headerItem" width="20%%">Executed&nbsp;lines:</td>
       
   121           <td class="headerValue" width="20%%">%(total_executed)s</td>
       
   122         </tr>
       
   123         </table>
       
   124       </td>
       
   125     </tr>
       
   126     <tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
       
   127   </table>""" % vars())
       
   128     # disabled for now <tr><td><img src="%(image)s"></td></tr>
       
   129 
       
   130 def generate_table_item(file, name, total_lines, covered_lines):
       
   131     covered_precise = (covered_lines*100.0)/(total_lines or 1.0)
       
   132     covered = int(round(covered_precise))
       
   133     remainder = 100-covered
       
   134     (image,perClass,numClass) = coverage_icon(covered_precise)
       
   135     site = "%s.html" % name.replace(os.path.sep,'__')
       
   136     file.write("""
       
   137         <tr>
       
   138       <td class="coverFile"><a href="%(site)s">%(name)s</a></td>
       
   139       <td class="coverBar" align="center">
       
   140         <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="%(image)s" width=%(covered)s height=10 alt="%(covered_precise).2f"><img src="snow.png" width=%(remainder)s height=10 alt="%(covered_precise).2f"></td></tr></table>
       
   141       </td>
       
   142       <td class="%(perClass)s">%(covered_precise).2f&nbsp;%%</td>
       
   143       <td class="%(numClass)s">%(covered_lines)s&nbsp;/&nbsp;%(total_lines)s&nbsp;lines</td>
       
   144     </tr>
       
   145     """ % vars())
       
   146 
       
   147 def generate_table_header_start(file):
       
   148   file.write("""<center>
       
   149   <table width="80%%" cellpadding=2 cellspacing=1 border=0>
       
   150 
       
   151     <tr>
       
   152       <td width="50%%"><br></td>
       
   153       <td width="15%%"></td>
       
   154       <td width="15%%"></td>
       
   155       <td width="20%%"></td>
       
   156     </tr>
       
   157 
       
   158     <tr>
       
   159       <td class="tableHead">Directory&nbsp;name</td>
       
   160       <td class="tableHead" colspan=3>Coverage</td>
       
   161     </tr>
       
   162     """)
       
   163 
       
   164 def coverage_icon(percent):
       
   165     if percent < level_LOW:
       
   166         return ("ruby.png", "coverPerLo", "coverNumLo")
       
   167     elif percent < level_MEDIUM:
       
   168         return ("amber.png", "coverPerMed", "coverNumMed")
       
   169     else:
       
   170         return ("emerald.png", "coverPerHi", "coverNumHi")
       
   171 
       
   172 def replace(text, *pairs):
       
   173     """
       
   174     From pydoc... almost identical at least
       
   175     """
       
   176     from string import split, join
       
   177     while pairs:
       
   178         (a,b) = pairs[0]
       
   179         text = join(split(text, a), b)
       
   180         pairs = pairs[1:]
       
   181     return text
       
   182 
       
   183 def escape(text):
       
   184     """
       
   185     Escape string to be conform HTML
       
   186     """
       
   187     return replace(text,
       
   188                         ('&', '&amp;'),
       
   189                         ('<', '&lt;' ),
       
   190                         ('>', '&gt;' ) )
       
   191 
       
   192 def generate_table_header_end(file):
       
   193     file.write("""</table>
       
   194     </center>""")
       
   195 
       
   196 def write_title_page(dest_dir,plot_files, last_time, last_tot_lines, last_tot_covered, dir_series):
       
   197     """
       
   198     Write the index.html with a overview of each directory
       
   199     """
       
   200     index= create_page(dest_dir, "index.html")
       
   201     generate_header(index, last_time, last_tot_lines, last_tot_covered, "directory", "images/Total.png")
       
   202     # Create the directory overview
       
   203     generate_table_header_start(index)
       
   204     dirs = dir_series.keys()
       
   205     dirs.sort()
       
   206     for dir in dirs:
       
   207         (dir_files, total_lines, covered_lines,_) = dir_series[dir][-1]
       
   208         generate_table_item(index, dir, total_lines, covered_lines)
       
   209     generate_table_header_end(index)
       
   210 
       
   211     index.write("""</BODY></HTML>""")
       
   212     index.close()
       
   213 
       
   214 def write_directory_site(dest_dir, plot_files, dir_name, last_time, dir_series, file_series):
       
   215     escaped_dir = dir_name.replace(os.path.sep,'__')
       
   216     site = create_page(dest_dir, "%s.html" % escaped_dir)
       
   217     (_,tot_lines,tot_covered,files) = dir_series[dir_name][-1]
       
   218     generate_header(site, last_time, tot_lines, tot_covered, "directory - %s" % dir_name, "images/%s.png" % escaped_dir)
       
   219 
       
   220     files.sort()
       
   221 
       
   222     generate_table_header_start(site)
       
   223     for file in files:
       
   224         (lines,covered) = file_series[file][-1]
       
   225         generate_table_item(site, file, lines, covered)
       
   226 
       
   227     generate_table_header_end(site)
       
   228     site.write("""</BODY></HTML>""")
       
   229     site.close()
       
   230 
       
   231 def write_file_site(dest_dir, plot_files, file_name, last_time, data_dir, last_id, file_series):
       
   232     escaped_name = file_name.replace(os.path.sep,'__')
       
   233     site = create_page(dest_dir, "%s.html" % escaped_name)
       
   234     (tot_lines,tot_covered) = file_series[file_name][-1]
       
   235     generate_header(site, last_time, tot_lines, tot_covered, "file - %s" % file_name, "images/%s.png" % escaped_name)
       
   236 
       
   237     path = "%s/%s.annotated%s" % (data_dir,last_id,file_name)
       
   238 
       
   239     # In contrast to the lcov we want to show files that have been compiled
       
   240     # but have not been tested at all. This means we have sourcefiles with 0
       
   241     # lines covered in the path but they are not lcov files.
       
   242     # To identify them we check the first line now. If we see that we can
       
   243     # continue
       
   244     #         -:    0:Source:
       
   245     try:
       
   246         file = open(path, "r")
       
   247     except:
       
   248         return
       
   249     all_lines = file.read().split("\n")
       
   250 
       
   251     # Convert the gcov file to HTML if we have a chanche to do so
       
   252     # Scan each line and see if it was covered or not and escape the
       
   253     # text
       
   254     if len(all_lines) == 0 or not "-:    0:Source:" in all_lines[0]:
       
   255         site.write("<p>The file was not excercised</p>")
       
   256     else:
       
   257         site.write("""</br><table cellpadding=0 cellspacing=0 border=0>
       
   258     <tr>
       
   259       <td><br></td>
       
   260     </tr>
       
   261     <tr>
       
   262       <td><pre class="source">
       
   263     """)
       
   264         for line in all_lines:
       
   265             split_line = line.split(':',2)
       
   266             # e.g. at the EOF
       
   267             if len(split_line) == 1:
       
   268                 continue
       
   269             line_number = split_line[1].strip()
       
   270             if line_number == "0":
       
   271                 continue
       
   272             covered = 15*" "
       
   273             end = ""
       
   274             if "#####" in split_line[0]:
       
   275                 covered = '<span class="lineNoCov">%15s' % "0"
       
   276                 end = "</span>"
       
   277             elif split_line[0].strip() != "-":
       
   278                 covered = '<span class="lineCov">%15s' % split_line[0].strip()
       
   279                 end = "</span>"
       
   280 
       
   281             escaped_line = escape(split_line[2])
       
   282             str = '<span class="lineNum">%(line_number)10s </span>%(covered)s: %(escaped_line)s%(end)s\n' % vars()
       
   283             site.write(str)
       
   284         site.write("</pre></td></tr></table>")
       
   285     site.write("</BODY></HTML>")
       
   286     site.close()
       
   287 
       
   288 def main(progname, args):
       
   289     if len(args) != 2:
       
   290         sys.exit("Usage: %s DATADIR OUTDIR" % progname)
       
   291 
       
   292     branch = "WebKit from trunk"
       
   293     datadir, outdir = args
       
   294 
       
   295     # First, load in all data from the data directory.
       
   296     data = []
       
   297     for datapath in glob.glob(os.path.join(datadir, "*.csv")):
       
   298         data.append(read_csv(datapath))
       
   299     # Sort by time
       
   300     data.sort()
       
   301 
       
   302     # Calculate time series for each file.
       
   303     times = [sample[0] for sample in data]
       
   304     times = [datetime.datetime.utcfromtimestamp(t) for t in times]
       
   305     times = m.date2num(times)
       
   306     all_files = {}
       
   307     all_dirs  = {}
       
   308     for sample in data:
       
   309         t, i, tot_line, tot_cover, per_file, per_dir = sample
       
   310         all_files.update(per_file)
       
   311         all_dirs.update(per_dir)
       
   312     total_series = []
       
   313     file_serieses = dict([[k, [(0, 0)] * len(times)] for k in all_files.keys()])
       
   314     dir_serieses  = dict([[k, [(0, 0, 0, [])] * len(times)] for k in all_dirs.keys()])
       
   315     data_idx = 0
       
   316     for sample in data:
       
   317         t, i, tot_line, tot_cover, per_file, per_dir = sample
       
   318         total_series.append([tot_line, tot_cover])
       
   319         for f, covinfo in per_file.items():
       
   320             file_serieses[f][data_idx] = covinfo
       
   321         for f, covinfo in per_dir.items():
       
   322             dir_serieses[f][data_idx] = covinfo
       
   323         data_idx += 1
       
   324 
       
   325 
       
   326     # Okay, ready to start outputting.  First make sure our directories
       
   327     # exist.
       
   328     if not os.path.exists(outdir):
       
   329         os.makedirs(outdir)
       
   330     rel_imgdir = "images"
       
   331     imgdir = os.path.join(outdir, rel_imgdir)
       
   332     if not os.path.exists(imgdir):
       
   333         os.makedirs(imgdir)
       
   334 
       
   335     # Now plot the actual graphs
       
   336     plot_files = {}
       
   337     #plot_files["Total"] = plot_coverage(times, total_series, imgdir, "Total")
       
   338     #for dir, series in dir_serieses.items():
       
   339     #    plot_files[dir] = plot_coverage(times, map(lambda (a,b,c,d):(b,c), series), imgdir, dir)
       
   340     #for f, series in file_serieses.items():
       
   341     #    plot_files[f] = plot_coverage(times, series, imgdir, f)
       
   342 
       
   343     # And look up the latest revision id, and coverage information
       
   344     last_time, last_id, last_tot_lines, last_tot_covered = data[-1][:4]
       
   345 
       
   346     # Now start generating our html file
       
   347     copy_files(outdir)
       
   348     write_title_page(outdir, plot_files, last_time, last_tot_lines, last_tot_covered, dir_serieses)
       
   349 
       
   350     dir_keys = dir_serieses.keys()
       
   351     dir_keys.sort()
       
   352     for dir_name in dir_keys:
       
   353         write_directory_site(outdir, plot_files, dir_name, last_time, dir_serieses, file_serieses)
       
   354 
       
   355     file_keys = file_serieses.keys()
       
   356     for file_name in file_keys:
       
   357         write_file_site(outdir, plot_files, file_name, last_time, datadir, last_id, file_serieses)
       
   358 
       
   359 def read_csv(path):
       
   360     r = csv.reader(open(path, "r"))
       
   361     # First line is id, time
       
   362     for row in r:
       
   363         id, time_str = row
       
   364         break
       
   365     time = int(float(time_str))
       
   366     # Rest of lines are path, total_lines, covered_lines
       
   367     per_file = {}
       
   368     per_dir  = {}
       
   369     grand_total_lines, grand_covered_lines = 0, 0
       
   370     for row in r:
       
   371         path, total_lines_str, covered_lines_str = row
       
   372         total_lines = int(total_lines_str)
       
   373         covered_lines = int(covered_lines_str)
       
   374         grand_total_lines += total_lines
       
   375         grand_covered_lines += covered_lines
       
   376         per_file[path] = [total_lines, covered_lines]
       
   377 
       
   378         # Update dir statistics
       
   379         dirname = os.path.dirname(path)
       
   380         if not dirname in per_dir:
       
   381             per_dir[dirname] = (0,0,0,[])
       
   382         (dir_files,dir_total_lines,dir_covered_lines, files) = per_dir[dirname]
       
   383         dir_files += 1
       
   384         dir_total_lines += total_lines
       
   385         dir_covered_lines += covered_lines
       
   386         files.append(path)
       
   387         per_dir[dirname] = (dir_files,dir_total_lines,dir_covered_lines,files)
       
   388     return [time, id, grand_total_lines, grand_covered_lines, per_file, per_dir]
       
   389 
       
   390 
       
   391 def plot_coverage(times, series, imgdir, name):
       
   392     percentages = [cov * 100.0 / (tot or 1) for tot, cov in series]
       
   393     m.plot_date(times, percentages, "b-")
       
   394     m.plot_date(times, percentages, "bo")
       
   395     m.title(name)
       
   396     m.ylim(0, 100)
       
   397     m.xlabel("Date")
       
   398     m.ylabel("Statement Coverage (%)")
       
   399     outfile_base = name.replace("/", "__") + ".png"
       
   400     outfile = os.path.join(imgdir, outfile_base)
       
   401     m.savefig(outfile, dpi=75)
       
   402     m.close()
       
   403     return outfile_base
       
   404 
       
   405 
       
   406 if __name__ == "__main__":
       
   407     import sys
       
   408     main(sys.argv[0], sys.argv[1:])