DebugClients/Python3/coverage/html.py

changeset 29
391dc0bc4ae5
parent 0
de9c2efb9d02
child 3495
fac17a82b431
equal deleted inserted replaced
28:dde24fc7f7ba 29:391dc0bc4ae5
1 """HTML reporting for Coverage.""" 1 """HTML reporting for Coverage."""
2 2
3 import os, re, shutil 3 import os, re, shutil
4 from . import __version__ # pylint: disable-msg=W0611 4
5 from . import __url__, __version__ # pylint: disable-msg=W0611
6 from .phystokens import source_token_lines
5 from .report import Reporter 7 from .report import Reporter
6 from .templite import Templite 8 from .templite import Templite
7 9
8 # Disable pylint msg W0612, because a bunch of variables look unused, but 10 # Disable pylint msg W0612, because a bunch of variables look unused, but
9 # they're accessed in a templite context via locals(). 11 # they're accessed in a templite context via locals().
14 return os.path.join(os.path.split(__file__)[0], fname) 16 return os.path.join(os.path.split(__file__)[0], fname)
15 17
16 def data(fname): 18 def data(fname):
17 """Return the contents of a data file of ours.""" 19 """Return the contents of a data file of ours."""
18 return open(data_filename(fname)).read() 20 return open(data_filename(fname)).read()
19 21
20 22
21 class HtmlReporter(Reporter): 23 class HtmlReporter(Reporter):
22 """HTML reporting.""" 24 """HTML reporting."""
23 25
24 def __init__(self, coverage, ignore_errors=False): 26 def __init__(self, coverage, ignore_errors=False):
25 super(HtmlReporter, self).__init__(coverage, ignore_errors) 27 super(HtmlReporter, self).__init__(coverage, ignore_errors)
26 self.directory = None 28 self.directory = None
27 self.source_tmpl = Templite(data("htmlfiles/pyfile.html"), globals()) 29 self.source_tmpl = Templite(data("htmlfiles/pyfile.html"), globals())
28 30
29 self.files = [] 31 self.files = []
32 self.arcs = coverage.data.has_arcs()
30 33
31 def report(self, morfs, directory, omit_prefixes=None): 34 def report(self, morfs, directory, omit_prefixes=None):
32 """Generate an HTML report for `morfs`. 35 """Generate an HTML report for `morfs`.
33 36
34 `morfs` is a list of modules or filenames. `directory` is where to put 37 `morfs` is a list of modules or filenames. `directory` is where to put
35 the HTML files. `omit_prefixes` is a list of strings, prefixes of 38 the HTML files. `omit_prefixes` is a list of strings, prefixes of
36 modules to omit from the report. 39 modules to omit from the report.
37 40
38 """ 41 """
39 assert directory, "must provide a directory for html reporting" 42 assert directory, "must provide a directory for html reporting"
40 43
41 # Process all the files. 44 # Process all the files.
42 self.report_files(self.html_file, morfs, directory, omit_prefixes) 45 self.report_files(self.html_file, morfs, directory, omit_prefixes)
43 46
44 # Write the index file. 47 # Write the index file.
45 self.index_file() 48 self.index_file()
46 49
47 # Create the once-per-directory files. 50 # Create the once-per-directory files.
48 shutil.copyfile( 51 for static in [
49 data_filename("htmlfiles/style.css"), 52 "style.css", "coverage_html.js",
50 os.path.join(directory, "style.css") 53 "jquery-1.3.2.min.js", "jquery.tablesorter.min.js"
51 ) 54 ]:
52 shutil.copyfile( 55 shutil.copyfile(
53 data_filename("htmlfiles/jquery-1.3.2.min.js"), 56 data_filename("htmlfiles/" + static),
54 os.path.join(directory, "jquery-1.3.2.min.js") 57 os.path.join(directory, static)
55 ) 58 )
56 59
57 def html_file(self, cu, statements, excluded, missing): 60 def html_file(self, cu, analysis):
58 """Generate an HTML file for one source file.""" 61 """Generate an HTML file for one source file."""
59 62
60 source = cu.source_file() 63 source = cu.source_file().read()
61 source_lines = source.readlines() 64
62 65 nums = analysis.numbers
63 n_lin = len(source_lines) 66
64 n_stm = len(statements) 67 missing_branch_arcs = analysis.missing_branch_arcs()
65 n_exc = len(excluded) 68 n_par = 0 # accumulated below.
66 n_mis = len(missing) 69 arcs = self.arcs
67 n_run = n_stm - n_mis
68 if n_stm > 0:
69 pc_cov = 100.0 * n_run / n_stm
70 else:
71 pc_cov = 100.0
72 70
73 # These classes determine which lines are highlighted by default. 71 # These classes determine which lines are highlighted by default.
74 c_run = " run hide" 72 c_run = " run hide_run"
75 c_exc = " exc" 73 c_exc = " exc"
76 c_mis = " mis" 74 c_mis = " mis"
77 75 c_par = " par" + c_run
76
78 lines = [] 77 lines = []
79 for lineno, line in enumerate(source_lines): 78
80 lineno += 1 # enum is 0-based, lines are 1-based. 79 for lineno, line in enumerate(source_token_lines(source)):
81 80 lineno += 1 # 1-based line numbers.
82 81 # Figure out how to mark this line.
83 css_class = "" 82 line_class = ""
84 if lineno in statements: 83 annotate_html = ""
85 css_class += " stm" 84 annotate_title = ""
86 if lineno not in missing and lineno not in excluded: 85 if lineno in analysis.statements:
87 css_class += c_run 86 line_class += " stm"
88 if lineno in excluded: 87 if lineno in analysis.excluded:
89 css_class += c_exc 88 line_class += c_exc
90 if lineno in missing: 89 elif lineno in analysis.missing:
91 css_class += c_mis 90 line_class += c_mis
92 91 elif self.arcs and lineno in missing_branch_arcs:
93 lineinfo = { 92 line_class += c_par
94 'text': line, 93 n_par += 1
94 annlines = []
95 for b in missing_branch_arcs[lineno]:
96 if b == -1:
97 annlines.append("exit")
98 else:
99 annlines.append(str(b))
100 annotate_html = "   ".join(annlines)
101 if len(annlines) > 1:
102 annotate_title = "no jumps to these line numbers"
103 elif len(annlines) == 1:
104 annotate_title = "no jump to this line number"
105 elif lineno in analysis.statements:
106 line_class += c_run
107
108 # Build the HTML for the line
109 html = ""
110 for tok_type, tok_text in line:
111 if tok_type == "ws":
112 html += escape(tok_text)
113 else:
114 tok_html = escape(tok_text) or ' '
115 html += "<span class='%s'>%s</span>" % (tok_type, tok_html)
116
117 lines.append({
118 'html': html,
95 'number': lineno, 119 'number': lineno,
96 'class': css_class.strip() or "pln" 120 'class': line_class.strip() or "pln",
97 } 121 'annotate': annotate_html,
98 lines.append(lineinfo) 122 'annotate_title': annotate_title,
123 })
99 124
100 # Write the HTML page for this file. 125 # Write the HTML page for this file.
101 html_filename = cu.flat_rootname() + ".html" 126 html_filename = cu.flat_rootname() + ".html"
102 html_path = os.path.join(self.directory, html_filename) 127 html_path = os.path.join(self.directory, html_filename)
103 html = spaceless(self.source_tmpl.render(locals())) 128 html = spaceless(self.source_tmpl.render(locals()))
105 fhtml.write(html) 130 fhtml.write(html)
106 fhtml.close() 131 fhtml.close()
107 132
108 # Save this file's information for the index file. 133 # Save this file's information for the index file.
109 self.files.append({ 134 self.files.append({
110 'stm': n_stm, 135 'nums': nums,
111 'run': n_run, 136 'par': n_par,
112 'exc': n_exc,
113 'mis': n_mis,
114 'pc_cov': pc_cov,
115 'html_filename': html_filename, 137 'html_filename': html_filename,
116 'cu': cu, 138 'cu': cu,
117 }) 139 })
118 140
119 def index_file(self): 141 def index_file(self):
120 """Write the index.html file for this report.""" 142 """Write the index.html file for this report."""
121 index_tmpl = Templite(data("htmlfiles/index.html"), globals()) 143 index_tmpl = Templite(data("htmlfiles/index.html"), globals())
122 144
123 files = self.files 145 files = self.files
124 146 arcs = self.arcs
125 total_stm = sum([f['stm'] for f in files]) 147
126 total_run = sum([f['run'] for f in files]) 148 totals = sum([f['nums'] for f in files])
127 total_exc = sum([f['exc'] for f in files])
128 if total_stm:
129 total_cov = 100.0 * total_run / total_stm
130 else:
131 total_cov = 100.0
132 149
133 fhtml = open(os.path.join(self.directory, "index.html"), "w") 150 fhtml = open(os.path.join(self.directory, "index.html"), "w")
134 fhtml.write(index_tmpl.render(locals())) 151 fhtml.write(index_tmpl.render(locals()))
135 fhtml.close() 152 fhtml.close()
136 153
137 154
138 # Helpers for templates 155 # Helpers for templates and generating HTML
139 156
140 def escape(t): 157 def escape(t):
141 """HTML-escape the text in t.""" 158 """HTML-escape the text in t."""
142 return (t 159 return (t
143 # Change all tabs to 4 spaces.
144 .expandtabs(4)
145 # Convert HTML special chars into HTML entities. 160 # Convert HTML special chars into HTML entities.
146 .replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;") 161 .replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
147 .replace("'", "&#39;").replace('"', "&quot;") 162 .replace("'", "&#39;").replace('"', "&quot;")
148 # Convert runs of spaces: " " -> "&nbsp; &nbsp; &nbsp; " 163 # Convert runs of spaces: "......" -> "&nbsp;.&nbsp;.&nbsp;."
149 .replace(" ", "&nbsp; ") 164 .replace(" ", "&nbsp; ")
150 # To deal with odd-length runs, convert the final pair of spaces 165 # To deal with odd-length runs, convert the final pair of spaces
151 # so that " " -> "&nbsp; &nbsp;&nbsp; " 166 # so that "....." -> "&nbsp;.&nbsp;&nbsp;."
152 .replace(" ", "&nbsp; ") 167 .replace(" ", "&nbsp; ")
153 ) 168 )
154 169
155 def not_empty(t):
156 """Make sure HTML content is not completely empty."""
157 return t or "&nbsp;"
158
159 def format_pct(p): 170 def format_pct(p):
160 """Format a percentage value for the HTML reports.""" 171 """Format a percentage value for the HTML reports."""
161 return "%.0f" % p 172 return "%.0f" % p
162 173
163 def spaceless(html): 174 def spaceless(html):
164 """Squeeze out some annoying extra space from an HTML string. 175 """Squeeze out some annoying extra space from an HTML string.
165 176
166 Nicely-formatted templates mean lots of extra space in the result. Get 177 Nicely-formatted templates mean lots of extra space in the result. Get
167 rid of some. 178 rid of some.
168 179
169 """ 180 """
170 html = re.sub(">\s+<p ", ">\n<p ", html) 181 html = re.sub(">\s+<p ", ">\n<p ", html)
171 return html 182 return html

eric ide

mercurial