summaryrefslogtreecommitdiff
path: root/python/helpers/coverage/html.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/helpers/coverage/html.py')
-rw-r--r--python/helpers/coverage/html.py177
1 files changed, 124 insertions, 53 deletions
diff --git a/python/helpers/coverage/html.py b/python/helpers/coverage/html.py
index fffd9b45b41a..5242236c1ed9 100644
--- a/python/helpers/coverage/html.py
+++ b/python/helpers/coverage/html.py
@@ -1,21 +1,45 @@
"""HTML reporting for Coverage."""
-import os, re, shutil
+import os, re, shutil, sys
import coverage
from coverage.backward import pickle
from coverage.misc import CoverageException, Hasher
-from coverage.phystokens import source_token_lines
+from coverage.phystokens import source_token_lines, source_encoding
from coverage.report import Reporter
+from coverage.results import Numbers
from coverage.templite import Templite
-# Disable pylint msg W0612, because a bunch of variables look unused, but
-# they're accessed in a Templite context via locals().
-# pylint: disable=W0612
-def data_filename(fname):
- """Return the path to a data file of ours."""
- return os.path.join(os.path.split(__file__)[0], fname)
+# Static files are looked for in a list of places.
+STATIC_PATH = [
+ # The place Debian puts system Javascript libraries.
+ "/usr/share/javascript",
+
+ # Our htmlfiles directory.
+ os.path.join(os.path.dirname(__file__), "htmlfiles"),
+]
+
+def data_filename(fname, pkgdir=""):
+ """Return the path to a data file of ours.
+
+ The file is searched for on `STATIC_PATH`, and the first place it's found,
+ is returned.
+
+ Each directory in `STATIC_PATH` is searched as-is, and also, if `pkgdir`
+ is provided, at that subdirectory.
+
+ """
+ for static_dir in STATIC_PATH:
+ static_filename = os.path.join(static_dir, fname)
+ if os.path.exists(static_filename):
+ return static_filename
+ if pkgdir:
+ static_filename = os.path.join(static_dir, pkgdir, fname)
+ if os.path.exists(static_filename):
+ return static_filename
+ raise CoverageException("Couldn't find static file %r" % fname)
+
def data(fname):
"""Return the contents of a data file of ours."""
@@ -31,26 +55,27 @@ class HtmlReporter(Reporter):
# These files will be copied from the htmlfiles dir to the output dir.
STATIC_FILES = [
- "style.css",
- "jquery-1.4.3.min.js",
- "jquery.hotkeys.js",
- "jquery.isonscreen.js",
- "jquery.tablesorter.min.js",
- "coverage_html.js",
- "keybd_closed.png",
- "keybd_open.png",
+ ("style.css", ""),
+ ("jquery.min.js", "jquery"),
+ ("jquery.hotkeys.js", "jquery-hotkeys"),
+ ("jquery.isonscreen.js", "jquery-isonscreen"),
+ ("jquery.tablesorter.min.js", "jquery-tablesorter"),
+ ("coverage_html.js", ""),
+ ("keybd_closed.png", ""),
+ ("keybd_open.png", ""),
]
- def __init__(self, cov, ignore_errors=False):
- super(HtmlReporter, self).__init__(cov, ignore_errors)
+ def __init__(self, cov, config):
+ super(HtmlReporter, self).__init__(cov, config)
self.directory = None
self.template_globals = {
'escape': escape,
+ 'title': self.config.html_title,
'__url__': coverage.__url__,
'__version__': coverage.__version__,
}
self.source_tmpl = Templite(
- data("htmlfiles/pyfile.html"), self.template_globals
+ data("pyfile.html"), self.template_globals
)
self.coverage = cov
@@ -58,29 +83,34 @@ class HtmlReporter(Reporter):
self.files = []
self.arcs = self.coverage.data.has_arcs()
self.status = HtmlStatus()
+ self.extra_css = None
+ self.totals = Numbers()
- def report(self, morfs, config=None):
+ def report(self, morfs):
"""Generate an HTML report for `morfs`.
- `morfs` is a list of modules or filenames. `config` is a
- CoverageConfig instance.
+ `morfs` is a list of modules or filenames.
"""
- assert config.html_dir, "must provide a directory for html reporting"
+ assert self.config.html_dir, "must give a directory for html reporting"
# Read the status data.
- self.status.read(config.html_dir)
+ self.status.read(self.config.html_dir)
# Check that this run used the same settings as the last run.
m = Hasher()
- m.update(config)
+ m.update(self.config)
these_settings = m.digest()
if self.status.settings_hash() != these_settings:
self.status.reset()
self.status.set_settings_hash(these_settings)
+ # The user may have extra CSS they want copied.
+ if self.config.extra_css:
+ self.extra_css = os.path.basename(self.config.extra_css)
+
# Process all the files.
- self.report_files(self.html_file, morfs, config, config.html_dir)
+ self.report_files(self.html_file, morfs, self.config.html_dir)
if not self.files:
raise CoverageException("No data to report.")
@@ -88,13 +118,34 @@ class HtmlReporter(Reporter):
# Write the index file.
self.index_file()
- # Create the once-per-directory files.
- for static in self.STATIC_FILES:
+ self.make_local_static_report_files()
+
+ return self.totals.pc_covered
+
+ def make_local_static_report_files(self):
+ """Make local instances of static files for HTML report."""
+ # The files we provide must always be copied.
+ for static, pkgdir in self.STATIC_FILES:
shutil.copyfile(
- data_filename("htmlfiles/" + static),
+ data_filename(static, pkgdir),
os.path.join(self.directory, static)
)
+ # The user may have extra CSS they want copied.
+ if self.extra_css:
+ shutil.copyfile(
+ self.config.extra_css,
+ os.path.join(self.directory, self.extra_css)
+ )
+
+ def write_html(self, fname, html):
+ """Write `html` to `fname`, properly encoded."""
+ fout = open(fname, "wb")
+ try:
+ fout.write(html.encode('ascii', 'xmlcharrefreplace'))
+ finally:
+ fout.close()
+
def file_hash(self, source, cu):
"""Compute a hash that changes if the file needs to be re-reported."""
m = Hasher()
@@ -121,11 +172,20 @@ class HtmlReporter(Reporter):
self.status.set_file_hash(flat_rootname, this_hash)
+ # If need be, determine the encoding of the source file. We use it
+ # later to properly write the HTML.
+ if sys.version_info < (3, 0):
+ encoding = source_encoding(source)
+ # Some UTF8 files have the dreaded UTF8 BOM. If so, junk it.
+ if encoding.startswith("utf-8") and source[:3] == "\xef\xbb\xbf":
+ source = source[3:]
+ encoding = "utf-8"
+
+ # Get the numbers for this file.
nums = analysis.numbers
- missing_branch_arcs = analysis.missing_branch_arcs()
- n_par = 0 # accumulated below.
- arcs = self.arcs
+ if self.arcs:
+ missing_branch_arcs = analysis.missing_branch_arcs()
# These classes determine which lines are highlighted by default.
c_run = "run hide_run"
@@ -149,7 +209,6 @@ class HtmlReporter(Reporter):
line_class.append(c_mis)
elif self.arcs and lineno in missing_branch_arcs:
line_class.append(c_par)
- n_par += 1
annlines = []
for b in missing_branch_arcs[lineno]:
if b < 0:
@@ -184,19 +243,22 @@ class HtmlReporter(Reporter):
})
# Write the HTML page for this file.
+ html = spaceless(self.source_tmpl.render({
+ 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run,
+ 'arcs': self.arcs, 'extra_css': self.extra_css,
+ 'cu': cu, 'nums': nums, 'lines': lines,
+ }))
+
+ if sys.version_info < (3, 0):
+ html = html.decode(encoding)
+
html_filename = flat_rootname + ".html"
html_path = os.path.join(self.directory, html_filename)
- html = spaceless(self.source_tmpl.render(locals()))
- fhtml = open(html_path, 'w')
- try:
- fhtml.write(html)
- finally:
- fhtml.close()
+ self.write_html(html_path, html)
# Save this file's information for the index file.
index_info = {
'nums': nums,
- 'par': n_par,
'html_filename': html_filename,
'name': cu.name,
}
@@ -206,19 +268,24 @@ class HtmlReporter(Reporter):
def index_file(self):
"""Write the index.html file for this report."""
index_tmpl = Templite(
- data("htmlfiles/index.html"), self.template_globals
+ data("index.html"), self.template_globals
)
- files = self.files
- arcs = self.arcs
-
- totals = sum([f['nums'] for f in files])
+ self.totals = sum([f['nums'] for f in self.files])
- fhtml = open(os.path.join(self.directory, "index.html"), "w")
- try:
- fhtml.write(index_tmpl.render(locals()))
- finally:
- fhtml.close()
+ html = index_tmpl.render({
+ 'arcs': self.arcs,
+ 'extra_css': self.extra_css,
+ 'files': self.files,
+ 'totals': self.totals,
+ })
+
+ if sys.version_info < (3, 0):
+ html = html.decode("utf-8")
+ self.write_html(
+ os.path.join(self.directory, "index.html"),
+ html
+ )
# Write the latest hashes for next time.
self.status.write(self.directory)
@@ -243,8 +310,12 @@ class HtmlStatus(object):
usable = False
try:
status_file = os.path.join(directory, self.STATUS_FILE)
- status = pickle.load(open(status_file, "rb"))
- except IOError:
+ fstatus = open(status_file, "rb")
+ try:
+ status = pickle.load(fstatus)
+ finally:
+ fstatus.close()
+ except (IOError, ValueError):
usable = False
else:
usable = True
@@ -321,5 +392,5 @@ def spaceless(html):
Get rid of some.
"""
- html = re.sub(">\s+<p ", ">\n<p ", html)
+ html = re.sub(r">\s+<p ", ">\n<p ", html)
return html