github.com/braveheart12/insolar-09-08-19@v0.8.7/scripts/dev/codecov.py (about) 1 #!/usr/bin/env python3 2 # 3 # codecov.py is a try to implement https://codecov.io/ coverage algo for Go. 4 # 5 # Usage examples: 6 # 7 # ./scripts/dev/codecov.py [coverage.txt] 8 # grep 'FILTER' coverage.txt | ./scripts/dev/codecov.py 9 # 10 # Official info about used algo: 11 # https://docs.codecov.io/docs/frequently-asked-questions#section-how-is-coverage-calculated- 12 # round((hits / (hits + partials + misses)) * 100, 5) = Coverage Ratio 13 # 14 # https://docs.codecov.io/docs/about-code-coverage 15 16 import argparse 17 import re 18 import sys 19 20 parser = argparse.ArgumentParser() 21 parser.add_argument('cover-file', nargs='?', default='coverage.txt', help="coverage file") 22 # https://docs.codecov.io/docs/fixing-reports 23 parser.add_argument("--fix", action="store_true", help="try to behave like fix") 24 args = parser.parse_args() 25 26 # stat per file with parsed data 27 stats = {} 28 29 def load_fixlines(fix_file_path): 30 path_parts = fix_file_path.split('/') 31 fix_file = '/'.join(path_parts[3:]) 32 # mark empty lines, comments, and lines with '}' only 33 fixes = [] 34 with open(fix_file, 'r') as f: 35 n = 0 36 for line in f: 37 n += 1 38 line = line.rstrip("\n\r") 39 analyze = line 40 analyze = re.sub( r'^(.*)//.*', r'\1', analyze) 41 analyze = re.sub( r'\s+', r'', analyze, 0, re.S) 42 if (analyze == '}') or (len(analyze) == 0): 43 fixes.append(n) 44 return fixes 45 46 47 def code_line(offset_pair): 48 return int(offset_pair.split('.', 2)[0]) 49 50 cover_file = vars(args)['cover-file'] 51 52 def append_pair_to_list(pairs_list, pair): 53 for p in pairs_list: 54 # pair starts on end of interval 55 if pair[0] == p[1]: 56 p[1] = pair[1] 57 return 58 # pair ends on start of interval 59 if pair[1] == p[0]: 60 p[0] = pair[0] 61 return 62 pairs_list.append(pair) 63 64 def parse_cover(f): 65 for line in f: 66 key, val = line.split(':', 2) 67 if not key.endswith(".go"): 68 continue 69 70 cover_file = key 71 st = stats.get(cover_file, None) 72 if st is None: 73 st = stats[cover_file] = { 74 "hits": 0, 75 "misses": 0, 76 77 "fix_lines": load_fixlines(cover_file), 78 "hits_borders": [], 79 "miss_borders": [], 80 "partials": [], 81 } 82 # parse "27.47,28.10 1 1" 83 cover_raw = val.split() 84 line_offsets = cover_raw[0].split(',', 2) 85 start, end = line_offsets[0], line_offsets[1] 86 # num_statements are used in std coverage tool 87 # https://github.com/golang/go/blob/50bd1c4d4eb4fac8ddeb5f063c099daccfb71b26/src/cmd/cover/func.go#L67 88 # https://github.com/golang/go/blob/50bd1c4d4eb4fac8ddeb5f063c099daccfb71b26/src/cmd/cover/func.go#L135:22 89 # but not in codecov 90 # num_statements = int(cover_raw[1]) 91 not_covered = cover_raw[2] == '0' 92 93 start_n, end_n = code_line(start), code_line(end) 94 if not_covered: 95 append_pair_to_list(st["miss_borders"], [start_n, end_n]) 96 continue 97 append_pair_to_list(st["hits_borders"], [start_n, end_n]) 98 99 if not sys.stdin.isatty(): 100 parse_cover(sys.stdin) 101 else: 102 parse_cover(open(cover_file, 'r')) 103 104 def calc_partials(file_stat): 105 for hit_pair in file_stat["hits_borders"]: 106 for miss_pair in file_stat["miss_borders"]: 107 partial = None 108 # check only borders (can't overlap) 109 if (hit_pair[0] in miss_pair): 110 file_stat["partials"].append(hit_pair[0]) 111 file_stat["hits"] -= 1 112 break 113 if (hit_pair[1] in miss_pair): 114 file_stat["partials"].append(hit_pair[1]) 115 file_stat["hits"] -= 1 116 break 117 118 def sort_borders(borders): 119 return sorted(borders, key=lambda x: x[0]) 120 121 def apply_fixes(borders, fixes): 122 result = borders 123 i = 0 124 applied_fixes = [] 125 for n in fixes: 126 remove_pair = False 127 splitted = [] 128 while i < len(result): 129 pair = result[i] 130 if n < pair[0]: 131 break 132 i += 1 133 if n > pair[1]: 134 # try next pair 135 continue 136 if n == pair[0] and n == pair[1]: 137 remove_pair = True 138 break 139 if n-1 >= pair[0]: 140 splitted.append([pair[0], n-1]) 141 if n+1<= pair[1]: 142 splitted.append([n+1, pair[1]]) 143 break 144 if len(splitted) > 0 or remove_pair: 145 applied_fixes.append(n) 146 result = result[:i-1] + splitted + result[i:] 147 i -= 1 148 return result 149 150 def recalc_hits_and_misses(file_stat): 151 st = file_stat 152 st["hits"], st["misses"] = 0, 0 153 for pair in st["hits_borders"]: 154 st["hits"] += pair[1] - pair[0] + 1 155 for pair in st["miss_borders"]: 156 st["misses"] += pair[1] - pair[0] + 1 157 158 # summary stats 159 t_hits, t_partials, t_misses = 0, 0, 0 160 components_stat = {} 161 162 # calc anf print per file stats 163 files = sorted(stats.keys()) 164 max_file_length = len(max(files, key=lambda x: len(x))) 165 for file in files: 166 st = stats[file] 167 168 st["hits_borders"] = sort_borders(st["hits_borders"]) 169 st["miss_borders"] = sort_borders(st["miss_borders"]) 170 # DEBUG: 171 # print("{}: before={}".format(file, st)) 172 if not args.fix: 173 st["hits_borders"] = apply_fixes(st["hits_borders"], st["fix_lines"]) 174 st["miss_borders"] = apply_fixes(st["miss_borders"], st["fix_lines"]) 175 176 recalc_hits_and_misses(st) 177 calc_partials(st) 178 # DEBUG: 179 # print("{}: {}".format(file, st)) 180 181 partials = len(st["partials"]) 182 hits = st["hits"] 183 misses = st["misses"] - partials 184 percents = hits/ (hits + partials + misses) 185 186 fmt_string = "{:<" + str(max_file_length) + "}\t [{}, {}, {}]\t{:.2f}%" 187 print(fmt_string.format( 188 file, 189 hits, partials, misses, 190 round(percents, 5) * 100)) 191 192 # update global stat 193 t_hits += hits 194 t_partials += partials 195 t_misses += misses 196 197 # update per component stat 198 path_parts = file.split('/') 199 component = path_parts[3] 200 comp_st = components_stat.get(component, None) 201 if comp_st is None: 202 comp_st = components_stat[component] = {"hits": 0, "partials": 0, "misses": 0} 203 comp_st["hits"] += hits 204 comp_st["partials"] += partials 205 comp_st["misses"] += misses 206 207 # print per component stats 208 max_comp_length = len(max(components_stat.keys(), key=lambda x: len(x))) 209 print("-" * 75) 210 for comp in sorted(components_stat): 211 st = components_stat[comp] 212 perc = round(st["hits"]/(st["hits"]+st["partials"]+st["misses"]), 5) * 100 213 line_stat = "[{}, {}, {}]".format( 214 st["hits"], st["partials"], st["misses"], 215 ) 216 fmt_string = "{:<" + str(max_comp_length) + "}\t{:<32} {:.02f}%" 217 print(fmt_string.format(comp, line_stat, perc)) 218 219 # print total stats 220 print("=" * 75) 221 print("Total:\t[{}, {}, {}] {:.2f}%".format( 222 t_hits, t_partials, t_misses, 223 round(t_hits/(t_hits+t_partials+t_misses), 5) * 100))