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))