def diff_coverage_data(data_a, data_b, cfg): """ """ files_a = set(data_a.get_data().measured_files()) files_b = set(data_b.get_data().measured_files()) common_files = files_a.intersection(files_b) for fname in common_files: a = create_analysis(data_a, fname) b = create_analysis(data_b, fname) a_has = [] b_has = [] if a.statements != b.statements: click.echo("{}: statement mismatch".format(fname)) else: for x in a.statements: if x in a.missing: if x not in b.missing: b_has.append(x) else: if x in b.missing: a_has.append(x) if a_has != [] or b_has != []: click.echo( "{}: {} vs. {}".format( fname, click.style(','.join([str(s) for s in a_has]), fg='red'), click.style(','.join([str(s) for s in b_has]), fg='green'), ) )
def next(ctx, ignore, line_numbers, fnames): """ Display the next uncovered chunk. This finds the next file that has some uncovered lines and then runs: cuv lessopen <filename> | less -p \u258c -j 4 ..which means you can keep pressing 'n' to see the next uncovered line. If you include an argument, only files with that arg in the name are considered as "next". """ cfg = ctx.obj anything_found = False for fname in sorted(cfg.measured_filenames()): if any([ign in fname for ign in ignore]): continue if fnames and not any([fn in fname for fn in fnames]): continue anything_found = True data = create_analysis(cfg.data, fname) if data.missing: subprocess.call( u'cuv lessopen {} | less -R {} -p \u258c -j 4'.format( fname, '-N' if line_numbers else ''), shell=True, ) return if fnames and not anything_found: click.echo(u"No filenames matched any keywords: {}".format( ', '.join(fnames)))
def graph_coverage(keywords, cfg): file_coverage = [] start_time = time.time() file_coverage = list(cfg.measured_filenames(keywords)) file_coverage.sort() diff = time.time() - start_time common = file_coverage[0] for fname in file_coverage[1:]: common = ''.join([x[0] for x in zip(common, fname) if x[0] == x[1]]) click.echo("Coverage in: {}".format(common)) common = len(common) if True: lines_per_col = 8 else: max_statements = max([len(d.statements) for name, d in file_coverage]) lines_per_col = math.ceil(float(max_statements) / float(graph_width)) if lines_per_col < 8: lines_per_col = 8 max_fname = max([len(nm) - common for nm in file_coverage]) width = click.get_terminal_size()[0] graph_width = width - 5 - max_fname def percent_to_bar(prct): # 0x2581 is _ like bar # 0x2588 is completely solid if prct < 0.125: return click.style(u' ', fg='red', bg='green') return click.style( six.unichr(0x2580 + int(prct / 0.125)), fg='red', bg='green' ) last_fname = None last_prefix = 0 for fname in file_coverage: try: data = create_analysis(cfg.data, fname) except Exception as e: click.echo(u"error: {}: {}".format(fname, e)) short = fname[common:] graph = '' glyphs = 0 printed_fname = False bad = total = 0 percent = 100.0 # if no statements, it's all covered, right? if data.statements: percent = (1.0 - (len(data.missing) / float(len(data.statements)))) * 100.0 if last_fname is not None: last_prefix = 0 for (a, b) in zip(last_fname.split('/'), short.split('/')): if a != b: break last_prefix += len(a) + 1 if last_prefix > 0: last_prefix -= 1 last_fname = short for statement in data.statements: total += 1 if statement in data.missing: bad += 1 if total == lines_per_col: graph += percent_to_bar(float(bad) / float(total)) glyphs += 1 bad = total = 0 if glyphs >= graph_width: if printed_fname: click.echo(u'{} {}'.format(u' ' * max_fname, graph), color=True) else: printed_fname = True thisname = (u' ' * last_prefix) + short[last_prefix:] thisname = click.style(fname[common:common + last_prefix], fg='black') + click.style(short[last_prefix:], bold=True) click.echo( u'{}{} {} {}'.format( thisname, u' ' * (max_fname - len(short)), click.style( u'{:3d}'.format(int(percent)), fg='red' if percent < 60 else 'magenta' if percent < 80 else 'green', ), graph, ), color=True, ) last_prefix = 0 graph = '' glyphs = 0 if total > 0: graph += percent_to_bar(float(bad) / float(total)) glyphs += 1 if glyphs == 0: graph = click.style('no statements', dim=True, fg='black') if printed_fname: click.echo(u'{} {}'.format(u' ' * max_fname, graph), color=True) else: printed_fname = True thisname = (u' ' * last_prefix) + short[last_prefix:] thisname = click.style(fname[common:common + last_prefix], fg='black') + click.style(short[last_prefix:], bold=True) click.echo( u'{}{} {} {}'.format( thisname, u' ' * (max_fname - len(short)), click.style( u'{:3d}'.format(int(percent)), fg='red' if percent < 60 else 'magenta' if percent < 80 else 'green', ), graph, ), color=True, ) graph = '' glyphs = 0
def pixel_vis(cfg, pixel_size, height, show_image): target_fname = 'coverage_pixel.png' randomize_brightness = False show_comments = True header_size = 8 column_chars = 80 maximum_height = height biggest_prefix = None total_statements = 0 total_missing = 0 total_files = 0 total_covered = 0 frmt = None coverage_data = [] for fname in cfg.measured_filenames(): try: covdata = create_analysis(cfg.data, fname) except Exception as e: click.echo("{}: {}".format(fname, e)) continue if biggest_prefix is None: biggest_prefix = fname else: for (i, ch) in enumerate(fname[:len(biggest_prefix)]): if ch != biggest_prefix[i]: biggest_prefix = biggest_prefix[:i] break if len(covdata.statements) == 0: continue # can't we derive this info. from stuff in coverage? # "statements"/n_statements isn't every line if show_comments: lines = len(open(covdata.fname).readlines()) else: # actually, ignoring comments etc seems more-sane sometimes # ...but then the code doesn't "look" right :/ lines = len(covdata.statements) total_statements += lines total_missing += len(covdata.missing) total_files += 1 total_covered += (len(covdata.statements) - len(covdata.missing)) coverage_data.append((fname, covdata, lines)) fnt = ImageFont.truetype( pkg_resources.resource_filename("cuv", "source-code-pro.ttf"), header_size, ) fnt_height = fnt.getsize('0')[1] column_width = (column_chars * pixel_size[0]) + 2 lines_per_column = maximum_height / pixel_size[1] total_pixel_height = (pixel_size[1] * total_statements) + (len(coverage_data) * (fnt_height + 2)) num_columns = int(math.ceil(total_pixel_height / float(maximum_height))) click.echo(u"need {} columns for {} lines".format(num_columns, total_statements)) # 3 for vertical 'total-coverage' bar on left width = (num_columns * column_width) + 3 height = maximum_height click.echo(u"size: {}x{}".format(width, height)) img = Image.new('RGBA', (width, height), (0, 0, 0, 255)) draw = ImageDraw.Draw(img) if False: # do the biggest data first coverage_data.sort(lambda a, b: cmp(len(a[1].statements), len(b[1].statements))) coverage_data.reverse() from pygments import highlight from pygments.token import Token from pygments.lexers import get_lexer_by_name from pygments.formatter import Formatter def parse_color(col): r = int(col[:2], 16) g = int(col[2:4], 16) b = int(col[4:6], 16) return (r, g, b, 64) class Position(object): def __init__(self, x=0, y=0): self.x = x self.y = y pos = Position(3, 0) class ImageFormatter(Formatter): def __init__(self, draw, covdata, origin, **kw): Formatter.__init__(self, **kw) self.draw = draw self.covdata = covdata self.origin = origin #: maps Token -> r,g,b triple self.token_color = {} for (token, style) in self.style: if 'color' in style and style['color']: c = parse_color(style['color']) self.token_color[token] = c def draw_line_background(self, line_index, alpha=255): bg = (32, 32, 32, alpha) if (line_index + 1) in self.covdata.missing: bg = (164, 32, 32, alpha) # elif (line_index + 1) in self.covdata.branch_lines(): # bg = (128, 128, 32, alpha) elif (line_index + 1) in self.covdata.statements: bg = (32, 44, 32, alpha) self.draw.rectangle((self.origin.x, pos.y, self.origin.x + column_width - 2, pos.y + pixel_size[1]), fill=bg) return bg def format(self, tokensource, outfile): index = 0 bg = self.draw_line_background(index) for ttype, value in tokensource: try: color = self.token_color[ttype] except KeyError: color = (255, 255, 0, 255) text = u'{}'.format(value) fg_amt = 1 bg_amt = 7 color = ( int(((bg[0] * bg_amt) + (color[0] * fg_amt)) / (fg_amt + bg_amt)), int(((bg[1] * bg_amt) + (color[1] * fg_amt)) / (fg_amt + bg_amt)), int(((bg[2] * bg_amt) + (color[2] * fg_amt)) / (fg_amt + bg_amt)), 255 ) for snip in text.splitlines(True): self.draw_text(snip, color, index) assert snip.count('\n') <= 1 if '\n' in snip: index += 1 bg = self.draw_line_background(index) def draw_text(self, text, color, line_index): assert text.count('\n') <= 1 for ch in text: if pos.x >= self.origin.x + column_width: pos.x = self.origin.x break if ch not in ' \t\n\r': if randomize_brightness: dimness = 16 - random.randrange(32) c = [x + dimness for x in color] c[3] = 255 c = tuple(c) else: c = color self.draw.rectangle((pos.x, pos.y, pos.x + pixel_size[0], pos.y + pixel_size[1]), fill=c) pos.x += pixel_size[0] # we ensure there's at most one newline in "format" if u'\n' in text: pos.y += pixel_size[1] pos.x = self.origin.x if pos.y > maximum_height: pos.y = 0 pos.x = self.origin.x + column_width self.origin = Position(pos.x, pos.y) column = row = 0 for (fname, covdata, lines) in coverage_data: percent = float(lines - len(covdata.missing)) / lines barmax = column_width - 2 pwid = math.ceil(barmax * percent) if True: pos.y += pixel_size[1] + 1 draw.rectangle([pos.x, pos.y, pos.x + pwid, pos.y + fnt_height - 1], fill=(32, 96, 32, 255)) draw.rectangle([pos.x + pwid, pos.y, pos.x + barmax, pos.y + fnt_height - 1], fill=(96, 32, 32, 255)) trunc_fname = fname while fnt.getsize(trunc_fname)[0] > column_width: trunc_fname = trunc_fname[1:] text_x = column_width - fnt.getsize(trunc_fname)[0] # what if negative? text_x += pos.x draw.text((text_x, pos.y - 2), trunc_fname, fill=(255, 255, 255, 128), font=fnt) pos.y += fnt_height if True: new_x = pos.x if frmt: new_x = frmt.origin.x frmt = ImageFormatter(draw, covdata, Position(new_x, pos.y), style='monokai') highlight(open(fname, 'r').read(), get_lexer_by_name('python'), formatter=frmt) prct = float(total_statements - total_missing) / total_statements click.echo(u"total coverage: {}%".format(int(prct * 100))) prct_y = height * prct draw.rectangle([0, 0, 1, prct_y], fill=(0, 200, 0, 255)) draw.rectangle([0, prct_y + 1, 1, height], fill=(200, 0, 0, 255)) fname = 'coverage_cascade_pixel.png' img.save(fname) click.echo(u"wrote '{}'".format(fname)) if show_image: img.show()
def graph_coverage(keywords, cfg): file_coverage = [] start_time = time.time() file_coverage = list(cfg.measured_filenames(keywords)) file_coverage.sort() diff = time.time() - start_time common = len(common_root_path(file_coverage)) if True: lines_per_col = 8 else: max_statements = max([len(d.statements) for name, d in file_coverage]) lines_per_col = math.ceil(float(max_statements) / float(graph_width)) if lines_per_col < 8: lines_per_col = 8 max_fname = max([len(nm) - common for nm in file_coverage]) width = click.get_terminal_size()[0] graph_width = width - 13 - max_fname def percent_to_bar(prct): # 0x2581 is _ like bar # 0x2588 is completely solid if prct < 0.125: return click.style(u' ', fg='red', bg='green') return click.style(six.unichr(0x2580 + int(prct / 0.125)), fg='red', bg='green') last_fname = None last_prefix = 0 format_str = u'{:^%d} percent missing' % (max_fname + len('filename') - 4, ) click.echo(format_str.format(click.style('filename', bold=True))) total_lines = 0 total_missing = 0 for fname in file_coverage: try: data = create_analysis(cfg.data, fname) except Exception as e: click.echo(u"error: {}: {}".format(fname, e)) short = fname[common:] graph = '' glyphs = 0 printed_fname = False bad = total = 0 percent = 100.0 # if no statements, it's all covered, right? if data.statements: percent = ( 1.0 - (len(data.missing) / float(len(data.statements)))) * 100.0 if last_fname is not None: last_prefix = 0 for (a, b) in zip(last_fname.split('/'), short.split('/')): if a != b: break last_prefix += len(a) + 1 if last_prefix > 0: last_prefix -= 1 last_fname = short total_lines += len(data.statements) total_missing += len(data.missing) # compute each glyph's total (i.e. each chunk of ~8 lines) for statement in data.statements: total += 1 if statement in data.missing: bad += 1 if total == lines_per_col: graph += percent_to_bar(float(bad) / float(total)) glyphs += 1 bad = total = 0 if glyphs >= graph_width: if printed_fname: click.echo(u'{} {}'.format(u' ' * max_fname, graph), color=True) else: printed_fname = True thisname = (u' ' * last_prefix) + short[last_prefix:] thisname = click.style(fname[common:common + last_prefix], fg='black') + click.style( short[last_prefix:], bold=True) # XXX also, all this duplicated code :( if len(data.missing) > 0: nice_missing = u' ({})'.format( click.style(u'{:5d}'.format(-len(data.missing)), fg='red')) else: nice_missing = u' ' * 8 # XXX unit-tests!! this only gets hit on really-long files click.echo( u'{}{} {}{} {}'.format( thisname, u' ' * (max_fname - len(short)), click.style( u'{:3d}'.format(int(percent)), fg='red' if percent < 60 else 'magenta' if percent < 80 else 'green', ), nice_missing, graph, ), color=True, ) last_prefix = 0 graph = '' glyphs = 0 if total > 0: graph += percent_to_bar(float(bad) / float(total)) glyphs += 1 if glyphs == 0: graph = click.style('no statements', dim=True, fg='black') if printed_fname: click.echo(u'{} {}'.format(u' ' * (max_fname + 12), graph), color=True) else: printed_fname = True thisname = (u' ' * last_prefix) + short[last_prefix:] thisname = click.style(fname[common:common + last_prefix], fg='black') + click.style( short[last_prefix:], bold=True) # XXX code nearly identical to this above... if len(data.missing) > 0: nice_missing = u' ({})'.format( click.style(u'{:5d}'.format(-len(data.missing)), fg='red')) else: nice_missing = u' ' * 8 click.echo( u'{}{} {}{} {}'.format( thisname, u' ' * (max_fname - len(short)), click.style( u'{:3d}'.format(int(percent)), fg='red' if percent < 60 else 'magenta' if percent < 80 else 'green', ), #click.style(u' ({:5d})'.format(-len(data.missing)), fg='red'), nice_missing, graph, ), color=True, ) graph = '' glyphs = 0 covered_percent = (float(total_lines - total_missing) / total_lines) * 100.0 uncovered_percent = (float(total_missing) / total_lines) * 100.0 click.echo(u'From {} files: {} total lines, {} missing ({} {})'.format( len(file_coverage), click.style(str(total_lines), fg='green'), click.style(str(total_missing), fg='red'), click.style('{:3.3}%'.format(covered_percent), fg='green'), click.style('{:3.3}%'.format(uncovered_percent), fg='red'), ))