Home | History | Annotate | Download | only in CodeCoverage
      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 import os, sys
     31 
     32 # from BitBake
     33 def mkdirhier(dir):
     34     """Create a directory like 'mkdir -p', but does not complain if
     35     directory already exists like os.makedirs
     36     """
     37     try:
     38         os.makedirs(dir)
     39     except OSError, e:
     40         if e.errno != 17: raise e
     41 
     42 def collect_base(src,match_array):
     43     """
     44     Collect all files that match the match_array.
     45     """
     46 
     47     sources = []
     48     for root, dirs, files in os.walk(src):
     49         if ".svn" in root:
     50             continue
     51 
     52         for file in files:
     53             base,ext = os.path.splitext(file)
     54             if ext in match_array:
     55                 sources.append( os.path.join(root, file) )
     56 
     57     return sources
     58 
     59 def collect_depends(src):
     60     return collect_base(src, [".d"])
     61 
     62 def parse_dependency_file(src, base_dir, black_list):
     63     """
     64     Parse the .d files of the gcc
     65 
     66     Wow, the first time os.path.join is doing the right thing. We might
     67     have a relative path in the depends using os.path.join(dirname of .d, dep)
     68     we will end up in 
     69     """
     70     file = open(src)
     71     file = file.read()
     72     file = file.replace('\\', '').replace('\n', '')
     73 
     74     # We now have object: dependencies splitted
     75     ar  = file.split(':', 1)
     76     obj = ar[0].strip()
     77     dir = os.path.dirname(obj)
     78     deps = ar[1].split(' ')
     79 
     80     # Remove files outside WebKit, make path absolute
     81     deps = filter(lambda x: base_dir in x, deps)
     82     deps = map(lambda x: os.path.abspath(os.path.join(dir, x)), deps)
     83     return (obj, dir, deps)
     84 
     85 def collect_cov(base_path,targets):
     86     """
     87     Collect gcov files, collect_sources is not used as it also creates
     88     dirs and needs to do substituting.
     89     Actually we will build a mapping from source file to gcov files of
     90     interest. This is because we could have bytestream.h in many different
     91     subdirectories. And we would endup with bla.cpp##bytestream.h and we
     92     do not know which bytestream file was tested
     93     """
     94     def find_source_file(root,cov_file):
     95         """ Find a Source line or crash
     96 
     97         '#Users#ich#projekte#src#threadmessage.cpp###space#dports#include#qt3#qstring.h.gcov'
     98         '#Users#ich#projekte#src#threadmessage.cpp##..#^#src#threadmessage.cpp.gcov'
     99 
    100         ### is absolute path
    101         ##..#^# is relative path... well a gcov bug as well
    102         ##  normal split file in the same directory
    103         """
    104         if '###' in cov_file:
    105             split = cov_file.split('###')
    106             if not len(split) == 2:
    107                 raise "Unexpected split result"
    108             filepath = split[1][:-5].replace('#',os.path.sep)
    109             return os.path.join(os.path.sep,filepath)
    110         elif '##..#^#' in cov_file: 
    111             split = cov_file.split('##..#^#')
    112             if not len(split) == 2:
    113                 raise "Unexpected split result"
    114             filepath = split[1][:-5].replace('#',os.path.sep)
    115             return os.path.abspath(os.path.join(root,os.path.pardir,os.path.pardir,filepath))
    116         elif '##' in cov_file:
    117             split = cov_file.split('##')
    118             if not len(split) == 2:
    119                 raise "Unexpected split result"
    120             filepath = split[1][:-5].replace('#',os.path.sep)
    121             return os.path.abspath(os.path.join(root,filepath))
    122         elif '#' in cov_file:
    123             # wow a not broken gcov on OSX
    124             basename=os.path.basename(cov_file).replace('#',os.path.sep)[:-5]
    125             return os.path.abspath(os.path.join(root,basename))
    126 
    127         else:
    128             raise "No source found %s" % cov_file
    129 
    130     def sanitize_path(path):
    131         """
    132         Well fix up paths once again /usr/lib/gcc/i486-linux-gnu/4.1.2/^/^/^/^/include/c++/4.1.2/bits/stl_pair.h
    133         according to gcov '^' is a relative path, we will now build one from this one. Somehow it depends
    134         on the gcov version if .. really gets replaced to ^....
    135         """
    136         import os
    137         split = path.split(os.path.sep)
    138         str = ""
    139         for part in split:
    140             if part == '':
    141                 str = os.path.sep
    142             elif part == '^':
    143                 str = "%s..%s" % (str,os.path.sep)
    144             else:
    145                 str = "%s%s%s" % (str,part,os.path.sep)
    146         return os.path.abspath(str)
    147 
    148 
    149     gcov = {}
    150     for root, dirs, files in os.walk(base_path):
    151         if ".svn" in root:
    152             continue
    153         for file in files:
    154             base,ext = os.path.splitext(file)
    155             if ext in [".gcov"]:
    156                 try:
    157                     cov = os.path.join(root, file)
    158                     src = find_source_file( root, cov )
    159                     src = sanitize_path( src )
    160 
    161                     if not src in gcov:
    162                         gcov[src] = []
    163                     gcov[src].append( cov )
    164                 except Exception,e:
    165                     print "Exception on ", e
    166                     #import sys
    167                     #sys.exit(0)
    168                     pass
    169 
    170     #print gcov
    171     return gcov
    172 
    173 def generate_covs(candidates):
    174     """
    175     Generate gcov files in the right directory
    176 
    177     candidtaes contains the directories we have used when
    178     building. Each directory contains a set of files we will
    179     try to generate gcov files for.
    180     """
    181     print candidates.keys()
    182     for dir in candidates.keys():
    183         print "Trying in %s" % (dir)
    184         for dep in candidates[dir].keys():
    185             cmd = "cd %s; gcov -p -l %s" % (dir, dep)
    186             os.system("%s > /dev/null 2>&1 " % cmd)
    187 
    188 
    189 def analyze_coverage(sources,data,dirs,runid,base):
    190     """
    191     sources actual source files relative to src_dir e.g kdelibs/kdecore/klibloader.cpp
    192     data    Where to put the stuff
    193     dirs    Where to take a look for gcov files
    194     base    The base directory for files. All files not inside base will be ignored
    195     """
    196     import cov
    197     print base
    198     gcov = collect_cov(base,dirs)
    199     result = cov.analyze_coverage(gcov, sources, runid, data, base)
    200     print result
    201 
    202 if __name__ == "__main__":
    203     #global targets
    204     if not len(sys.argv) == 3:
    205         print "This script needs three parameters"
    206         print "Call it with generate_cov RUNID ResultsDir"
    207         sys.exit(-1)
    208     runid = sys.argv[1]
    209     results = sys.argv[2]
    210 
    211     # create directories for out result
    212     mkdirhier(results)
    213 
    214     print "Collection Sources and preparing data tree"
    215     base_dir = os.path.abspath(os.path.curdir)
    216     depends = collect_depends(base_dir)
    217     candidates = map(lambda x: parse_dependency_file(x,base_dir,[]), depends)
    218 
    219     # Build a number of sources from the candidates. This is a Set for the poor
    220     # Two level dict. One for 
    221     dirs = {}
    222     files = {}
    223     for (_,dir,deps) in candidates:
    224         if not dir in dirs:
    225             dirs[dir] = {}
    226         for dep in deps:
    227             if not dep in dirs[dir]:
    228                 dirs[dir][dep] = dep
    229             if not dep in files:
    230                 files[dep] = dep
    231 
    232     sources = files.keys()
    233 
    234     print "Found %d candidates" % (len(sources))
    235     print "Will run inefficient generation of gcov files now"
    236     generate_covs(dirs)
    237 
    238     print "Analyzing Gcov"
    239     analyze_coverage(sources, results, dirs.keys(), runid, base_dir)
    240     print "Done"
    241