1 # Copyright (C) 2004, 2005, 2006 Nathaniel Smith 2 # Copyright (C) 2006, 2007 Holger Hans Peter Freyther 3 # 4 # Redistribution and use in source and binary forms, with or without 5 # modification, are permitted provided that the following conditions 6 # are met: 7 # 8 # 1. Redistributions of source code must retain the above copyright 9 # notice, this list of conditions and the following disclaimer. 10 # 2. Redistributions in binary form must reproduce the above copyright 11 # notice, this list of conditions and the following disclaimer in the 12 # documentation and/or other materials provided with the distribution. 13 # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 # its contributors may be used to endorse or promote products derived 15 # from this software without specific prior written permission. 16 # 17 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 import csv 29 import time 30 import os.path 31 import shutil 32 33 def analyze_coverage(possible_gcov_files, source_files, runid, data_dir, base): 34 35 if not os.path.exists(data_dir): 36 os.makedirs(data_dir) 37 38 output = open(os.path.join(data_dir, runid + ".csv"), "w") 39 w = csv.writer(output) 40 # First row: id and time 41 w.writerow([runid, time.time()]) 42 43 results = scan_gcov_files(possible_gcov_files, source_files) 44 annotated_dir = os.path.join(data_dir, runid + ".annotated") 45 if os.path.exists(annotated_dir): 46 shutil.rmtree(annotated_dir) 47 48 keys = results.keys() 49 keys.sort() 50 for path in keys: 51 (total, covered, annotated_data) = results[path] 52 path = path[path.find(base)+len(base):] 53 # Rest of the rows: filename, total_lines, covered_lines 54 w.writerow([path, total, covered]) 55 56 if path[:1] == "/": 57 path = path[1:] 58 annotated_path = os.path.join(annotated_dir, path) 59 try: 60 os.makedirs(os.path.dirname(annotated_path)) 61 except OSError: 62 pass 63 a = open(annotated_path, "w") 64 a.write(annotated_data) 65 a.close() 66 67 68 # zecke's rewrite 69 STATE_NOT_CODE = -1 70 STATE_NOT_SEEN = -2 71 STATE_TEST_CODE = -3 72 73 def find_gcov(f, possible_gcovs): 74 """ 75 Find .gcov files that could be of interest for us 76 """ 77 try: 78 return possible_gcovs[f] 79 except: 80 return [] 81 82 83 def parse_source_file(file): 84 """ 85 Parse one source file and return a list of lines 86 """ 87 f_source_list = [] 88 init_state = STATE_NOT_SEEN 89 in_test_code = False 90 nesting = 0 91 92 for line in open(file, "r"): 93 code = line.split(":", 2)[-1] 94 if not in_test_code and code.startswith("#ifdef BUILD_UNIT_TESTS"): 95 in_test_code = 1 96 if in_test_code and code.startswith("#if"): 97 nesting += 1 98 if in_test_code and code.startswith("#endif"): 99 nesting -= 1 100 if not nesting: 101 in_test_code = True 102 if in_test_code: 103 init_state = STATE_TEST_CODE 104 else: 105 init_state = STATE_NOT_SEEN 106 f_source_list.append([init_state, line.split(":", 1)[1]]) 107 108 return f_source_list 109 110 # Runner-up, 3rd annual "write Python that looks like Perl" competition, 111 # Well, not really. It doesn't even use regexps. 112 # He is right so I'm cleaning it up (zecke) 113 def scan_gcov_files(possible_gcov_files, source_files): 114 """Takes a list of gcov filenames and a list of source filenames. 115 116 The gcov files should have names of the form foo.o##foo.cc.gcov, as 117 created by 'gcov -l'. 118 119 Returns a dict mapping source filenames to tuples 120 (total_lines, tested_lines, gcov_annotated_source) 121 which are a number, a number, and a very long string, respectively. 122 123 The fun bit is that we merge .gcov output generated by different object 124 files; this way we can provide accurate information for header files and 125 for monotone's current unit test system.""" 126 results = {} 127 for f in source_files: 128 possible_gcovs = find_gcov(f, possible_gcov_files) 129 base_name = os.path.splitext(os.path.basename(f))[0] 130 if len(possible_gcovs) == 0: 131 print "No gcov files found for: '%s' but it was compiled" % f 132 continue 133 134 (garbage,extension) = os.path.splitext(f) 135 if extension in [".cc", ".c", ".moc", ".cpp", ".cxx", ".m", ".mm"]: 136 lines = open(f, "r").readlines() 137 results[f] = (len(lines), 0, "".join(lines)) 138 continue 139 elif len(possible_gcovs) > 1: 140 print "More than one gcov file for %s %d" % (f,len(possible_gcovs)) 141 base_gcov_lines = parse_source_file(possible_gcovs[0]) 142 143 # Now we will try hard to merge the results with others 144 # Our requirement is that we have the same amount of lines as 145 # as the original file 146 for cov_file in possible_gcovs: 147 lines = open(cov_file, "r").readlines() 148 149 # e.g. with phonon we have visualisation.h and we can not know 150 # which header file (foldername) it is refering to. This is a gcov 151 # limitation and i have no workaround yet. We just hope we will pick 152 # the right header file... 153 if len(lines) != len(base_gcov_lines): 154 print "Error Base: %s and Target: %s have different amount of lines" % (possible_gcovs[0],cov_file) 155 continue 156 157 # now do the merging of the file. If it has the same basename 158 # and the same number of lines things might work out 159 # In the future take a look at the header of the file 160 i = 0 161 for line in lines: 162 accumulator = base_gcov_lines[i] 163 if accumulator[0] != STATE_TEST_CODE: 164 info = line.split(":", 1)[0] 165 if info.endswith("-"): 166 if accumulator[0] == STATE_NOT_SEEN: 167 accumulator[0] = STATE_NOT_CODE 168 else: 169 if info.endswith("#"): 170 num = 0 171 else: 172 num = int(info) 173 if accumulator[0] in (STATE_NOT_SEEN, STATE_NOT_CODE): 174 accumulator[0] = 0 175 accumulator[0] += num 176 i += 1 177 178 # post processing of ths file 179 (total_lines, total_covered) = (0, 0) 180 annotated_lines = [] 181 for state, line in base_gcov_lines: 182 if state == STATE_NOT_SEEN: 183 desc = "?????" 184 elif state == STATE_TEST_CODE: 185 desc = "+" 186 elif state == STATE_NOT_CODE: 187 desc = "-" 188 elif state == 0: 189 desc = "#####" 190 total_lines += 1 191 else: 192 desc = str(state) 193 total_lines += 1 194 total_covered += 1 195 annotated_lines.append(":".join([desc.rjust(9), line])) 196 results[f] = (total_lines, total_covered, "".join(annotated_lines)) 197 return results 198 199 200 201 return results 202