--- a/DebugClients/Python/coverage/html.py Fri Sep 02 19:08:02 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,438 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -"""HTML reporting for coverage.py.""" - -import datetime -import json -import os -import shutil - -import coverage -from coverage import env -from coverage.backward import iitems -from coverage.files import flat_rootname -from coverage.misc import CoverageException, Hasher, isolate_module -from coverage.report import Reporter -from coverage.results import Numbers -from coverage.templite import Templite - -os = isolate_module(os) - - -# 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 sub-directory. - - """ - tried = [] - for static_dir in STATIC_PATH: - static_filename = os.path.join(static_dir, fname) - if os.path.exists(static_filename): - return static_filename - else: - tried.append(static_filename) - if pkgdir: - static_filename = os.path.join(static_dir, pkgdir, fname) - if os.path.exists(static_filename): - return static_filename - else: - tried.append(static_filename) - raise CoverageException( - "Couldn't find static file %r from %r, tried: %r" % (fname, os.getcwd(), tried) - ) - - -def read_data(fname): - """Return the contents of a data file of ours.""" - with open(data_filename(fname)) as data_file: - return data_file.read() - - -def write_html(fname, html): - """Write `html` to `fname`, properly encoded.""" - with open(fname, "wb") as fout: - fout.write(html.encode('ascii', 'xmlcharrefreplace')) - - -class HtmlReporter(Reporter): - """HTML reporting.""" - - # These files will be copied from the htmlfiles directory to the output - # directory. - STATIC_FILES = [ - ("style.css", ""), - ("jquery.min.js", "jquery"), - ("jquery.debounce.min.js", "jquery-debounce"), - ("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, config): - super(HtmlReporter, self).__init__(cov, config) - self.directory = None - title = self.config.html_title - if env.PY2: - title = title.decode("utf8") - self.template_globals = { - 'escape': escape, - 'pair': pair, - 'title': title, - '__url__': coverage.__url__, - '__version__': coverage.__version__, - } - self.source_tmpl = Templite(read_data("pyfile.html"), self.template_globals) - - self.coverage = cov - - self.files = [] - self.has_arcs = self.coverage.data.has_arcs() - self.status = HtmlStatus() - self.extra_css = None - self.totals = Numbers() - self.time_stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M') - - def report(self, morfs): - """Generate an HTML report for `morfs`. - - `morfs` is a list of modules or file names. - - """ - assert self.config.html_dir, "must give a directory for html reporting" - - # Read the status data. - self.status.read(self.config.html_dir) - - # Check that this run used the same settings as the last run. - m = Hasher() - m.update(self.config) - these_settings = m.hexdigest() - 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, self.config.html_dir) - - if not self.files: - raise CoverageException("No data to report.") - - # Write the index file. - self.index_file() - - self.make_local_static_report_files() - return self.totals.n_statements and 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(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 file_hash(self, source, fr): - """Compute a hash that changes if the file needs to be re-reported.""" - m = Hasher() - m.update(source) - self.coverage.data.add_to_hash(fr.filename, m) - return m.hexdigest() - - def html_file(self, fr, analysis): - """Generate an HTML file for one source file.""" - source = fr.source() - - # Find out if the file on disk is already correct. - rootname = flat_rootname(fr.relative_filename()) - this_hash = self.file_hash(source.encode('utf-8'), fr) - that_hash = self.status.file_hash(rootname) - if this_hash == that_hash: - # Nothing has changed to require the file to be reported again. - self.files.append(self.status.index_info(rootname)) - return - - self.status.set_file_hash(rootname, this_hash) - - # Get the numbers for this file. - nums = analysis.numbers - - if self.has_arcs: - missing_branch_arcs = analysis.missing_branch_arcs() - arcs_executed = analysis.arcs_executed() - - # These classes determine which lines are highlighted by default. - c_run = "run hide_run" - c_exc = "exc" - c_mis = "mis" - c_par = "par " + c_run - - lines = [] - - for lineno, line in enumerate(fr.source_token_lines(), start=1): - # Figure out how to mark this line. - line_class = [] - annotate_html = "" - annotate_long = "" - if lineno in analysis.statements: - line_class.append("stm") - if lineno in analysis.excluded: - line_class.append(c_exc) - elif lineno in analysis.missing: - line_class.append(c_mis) - elif self.has_arcs and lineno in missing_branch_arcs: - line_class.append(c_par) - shorts = [] - longs = [] - for b in missing_branch_arcs[lineno]: - if b < 0: - shorts.append("exit") - else: - shorts.append(b) - longs.append(fr.missing_arc_description(lineno, b, arcs_executed)) - # 202F is NARROW NO-BREAK SPACE. - # 219B is RIGHTWARDS ARROW WITH STROKE. - short_fmt = "%s ↛ %s" - annotate_html = ", ".join(short_fmt % (lineno, d) for d in shorts) - - if len(longs) == 1: - annotate_long = longs[0] - else: - annotate_long = "%d missed branches: %s" % ( - len(longs), - ", ".join("%d) %s" % (num, ann_long) - for num, ann_long in enumerate(longs, start=1)), - ) - elif lineno in analysis.statements: - line_class.append(c_run) - - # Build the HTML for the line. - html = [] - for tok_type, tok_text in line: - if tok_type == "ws": - html.append(escape(tok_text)) - else: - tok_html = escape(tok_text) or ' ' - html.append( - '<span class="%s">%s</span>' % (tok_type, tok_html) - ) - - lines.append({ - 'html': ''.join(html), - 'number': lineno, - 'class': ' '.join(line_class) or "pln", - 'annotate': annotate_html, - 'annotate_long': annotate_long, - }) - - # Write the HTML page for this file. - html = self.source_tmpl.render({ - 'c_exc': c_exc, - 'c_mis': c_mis, - 'c_par': c_par, - 'c_run': c_run, - 'has_arcs': self.has_arcs, - 'extra_css': self.extra_css, - 'fr': fr, - 'nums': nums, - 'lines': lines, - 'time_stamp': self.time_stamp, - }) - - html_filename = rootname + ".html" - html_path = os.path.join(self.directory, html_filename) - write_html(html_path, html) - - # Save this file's information for the index file. - index_info = { - 'nums': nums, - 'html_filename': html_filename, - 'relative_filename': fr.relative_filename(), - } - self.files.append(index_info) - self.status.set_index_info(rootname, index_info) - - def index_file(self): - """Write the index.html file for this report.""" - index_tmpl = Templite(read_data("index.html"), self.template_globals) - - self.totals = sum(f['nums'] for f in self.files) - - html = index_tmpl.render({ - 'has_arcs': self.has_arcs, - 'extra_css': self.extra_css, - 'files': self.files, - 'totals': self.totals, - 'time_stamp': self.time_stamp, - }) - - write_html(os.path.join(self.directory, "index.html"), html) - - # Write the latest hashes for next time. - self.status.write(self.directory) - - -class HtmlStatus(object): - """The status information we keep to support incremental reporting.""" - - STATUS_FILE = "status.json" - STATUS_FORMAT = 1 - - # pylint: disable=wrong-spelling-in-comment,useless-suppression - # The data looks like: - # - # { - # 'format': 1, - # 'settings': '540ee119c15d52a68a53fe6f0897346d', - # 'version': '4.0a1', - # 'files': { - # 'cogapp___init__': { - # 'hash': 'e45581a5b48f879f301c0f30bf77a50c', - # 'index': { - # 'html_filename': 'cogapp___init__.html', - # 'name': 'cogapp/__init__', - # 'nums': <coverage.results.Numbers object at 0x10ab7ed0>, - # } - # }, - # ... - # 'cogapp_whiteutils': { - # 'hash': '8504bb427fc488c4176809ded0277d51', - # 'index': { - # 'html_filename': 'cogapp_whiteutils.html', - # 'name': 'cogapp/whiteutils', - # 'nums': <coverage.results.Numbers object at 0x10ab7d90>, - # } - # }, - # }, - # } - - def __init__(self): - self.reset() - - def reset(self): - """Initialize to empty.""" - self.settings = '' - self.files = {} - - def read(self, directory): - """Read the last status in `directory`.""" - usable = False - try: - status_file = os.path.join(directory, self.STATUS_FILE) - with open(status_file, "r") as fstatus: - status = json.load(fstatus) - except (IOError, ValueError): - usable = False - else: - usable = True - if status['format'] != self.STATUS_FORMAT: - usable = False - elif status['version'] != coverage.__version__: - usable = False - - if usable: - self.files = {} - for filename, fileinfo in iitems(status['files']): - fileinfo['index']['nums'] = Numbers(*fileinfo['index']['nums']) - self.files[filename] = fileinfo - self.settings = status['settings'] - else: - self.reset() - - def write(self, directory): - """Write the current status to `directory`.""" - status_file = os.path.join(directory, self.STATUS_FILE) - files = {} - for filename, fileinfo in iitems(self.files): - fileinfo['index']['nums'] = fileinfo['index']['nums'].init_args() - files[filename] = fileinfo - - status = { - 'format': self.STATUS_FORMAT, - 'version': coverage.__version__, - 'settings': self.settings, - 'files': files, - } - with open(status_file, "w") as fout: - json.dump(status, fout) - - # Older versions of ShiningPanda look for the old name, status.dat. - # Accomodate them if we are running under Jenkins. - # https://issues.jenkins-ci.org/browse/JENKINS-28428 - if "JENKINS_URL" in os.environ: - with open(os.path.join(directory, "status.dat"), "w") as dat: - dat.write("https://issues.jenkins-ci.org/browse/JENKINS-28428\n") - - def settings_hash(self): - """Get the hash of the coverage.py settings.""" - return self.settings - - def set_settings_hash(self, settings): - """Set the hash of the coverage.py settings.""" - self.settings = settings - - def file_hash(self, fname): - """Get the hash of `fname`'s contents.""" - return self.files.get(fname, {}).get('hash', '') - - def set_file_hash(self, fname, val): - """Set the hash of `fname`'s contents.""" - self.files.setdefault(fname, {})['hash'] = val - - def index_info(self, fname): - """Get the information for index.html for `fname`.""" - return self.files.get(fname, {}).get('index', {}) - - def set_index_info(self, fname, info): - """Set the information for index.html for `fname`.""" - self.files.setdefault(fname, {})['index'] = info - - -# Helpers for templates and generating HTML - -def escape(t): - """HTML-escape the text in `t`. - - This is only suitable for HTML text, not attributes. - - """ - # Convert HTML special chars into HTML entities. - return t.replace("&", "&").replace("<", "<") - - -def pair(ratio): - """Format a pair of numbers so JavaScript can read them in an attribute.""" - return "%s %s" % ratio - -# -# eflag: FileType = Python2