#! /usr/bin/env python import sys import figleaf import os import re from twisted.python import usage class RenderOptions(usage.Options): optParameters = [ ("exclude-patterns", "x", None, "file containing regexp patterns to exclude"), ("output-directory", "d", "html", "Directory for HTML output"), ("root", "r", None, "only pay attention to modules under this directory"), ] def opt_root(self, value): self["root"] = os.path.abspath(value) if not self["root"].endswith("/"): self["root"] += "/" def parseArgs(self, *filenames): self.filenames = [".figleaf"] if filenames: self.filenames = list(filenames) class Renderer: def run(self): self.opts = opts = RenderOptions() opts.parseOptions() ### load coverage = {} for filename in opts.filenames: d = figleaf.read_coverage(filename) coverage = figleaf.combine_coverage(coverage, d) if not coverage: sys.exit(-1) self.load_exclude_patterns(opts["exclude-patterns"]) ### make directory self.prepare_reportdir(opts["output-directory"]) self.report_as_html(coverage, opts["output-directory"], opts["root"]) def load_exclude_patterns(self, f): self.exclude_patterns = [] if not f: return for line in open(f, "r").readlines(): line = line.rstrip() if line and not line.startswith('#'): self.exclude_patterns.append(re.compile(line)) def prepare_reportdir(self, dirname='html'): try: os.mkdir(dirname) except OSError: # already exists pass def check_excludes(self, fn): for pattern in self.exclude_patterns: if pattern.search(fn): return True return False def make_display_filename(self, fn): root = self.opts["root"] if not root: return fn display_filename = fn[len(root):] assert not display_filename.startswith("/") assert display_filename.endswith(".py") display_filename = display_filename[:-3] # trim .py display_filename = display_filename.replace("/", ".") return display_filename def report_as_html(self, coverage, directory, root=None): ### now, output. keys = coverage.keys() info_dict = {} for k in keys: if self.check_excludes(k): continue if k.endswith('figleaf.py'): continue if not k.startswith("/"): continue display_filename = self.make_display_filename(k) info = self.process_file(k, display_filename, coverage) if info: info_dict[k] = info ### print a summary, too. #print info_dict info_dict_items = info_dict.items() def sort_by_pcnt(a, b): a = a[1][2] b = b[1][2] return -cmp(a,b) def sort_by_uncovered(a, b): a_uncovered = a[1][0] - a[1][1] b_uncovered = b[1][0] - b[1][1] return -cmp(a_uncovered, b_uncovered) info_dict_items.sort(sort_by_uncovered) summary_lines = sum([ v[0] for (k, v) in info_dict_items]) summary_cover = sum([ v[1] for (k, v) in info_dict_items]) summary_pcnt = 0 if summary_lines: summary_pcnt = float(summary_cover) * 100. / float(summary_lines) pcnts = [ float(v[1]) * 100. / float(v[0]) for (k, v) in info_dict_items if v[0] ] pcnt_90 = [ x for x in pcnts if x >= 90 ] pcnt_75 = [ x for x in pcnts if x >= 75 ] pcnt_50 = [ x for x in pcnts if x >= 50 ] stats_fp = open('%s/stats.out' % (directory,), 'w') stats_fp.write("total files: %d\n" % len(pcnts)) stats_fp.write("total source lines: %d\n" % summary_lines) stats_fp.write("total covered lines: %d\n" % summary_cover) stats_fp.write("total uncovered lines: %d\n" % (summary_lines - summary_cover)) stats_fp.write("total coverage percentage: %.1f\n" % summary_pcnt) stats_fp.close() ## index.html index_fp = open('%s/index.html' % (directory,), 'w') # summary info index_fp.write('
' % (len(pcnts), len(pcnt_90), len(pcnt_75), len(pcnt_50))) # sorted by number of lines that aren't covered index_fp.write('
\n') html_outfp.write("\n".join(output)) html_outfp.close() return (n_lines, n_covered, pcnt, display_filename) def emit_table(self, index_fp, items, show_totals, summary_lines, summary_cover, summary_pcnt): index_fp.write('
Filename | ' '# lines | # covered | ' '# uncovered | ' '% covered |
---|---|---|---|---|
totals: | ' '%d | ' '%d | ' '%d | ' '%.1f%% | ' '
%s | ' '%d | %d | %d | %.1f | ' '