| # HTMLReport.py |
| # |
| # Copyright (C) 2012 Carlos Garcia Campos <carlosgc@gnome.org> |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| from __future__ import absolute_import, division, print_function |
| |
| from backends import get_backend, get_all_backends |
| from Config import Config |
| import os |
| import errno |
| import subprocess |
| |
| class HTMLPrettyDiff: |
| |
| def write(self, test, outdir, actual, expected, diff): |
| raise NotImplementedError |
| |
| def _create_diff_for_test(self, outdir, test): |
| diffdir = os.path.join(outdir, 'html', test) |
| try: |
| os.makedirs(diffdir) |
| except OSError as e: |
| if e.errno != errno.EEXIST: |
| raise |
| except: |
| raise |
| return diffdir |
| |
| class HTMLPrettyDiffImage(HTMLPrettyDiff): |
| |
| def write(self, test, outdir, result, actual, expected, diff): |
| def get_relative_path(path): |
| return '../' * len(path.split('/')) + path |
| |
| html = """ |
| <html> |
| <head> |
| <title>%s</title> |
| <style>.label{font-weight:bold}</style> |
| </head> |
| <body> |
| Difference between images: <a href="%s">diff</a><br> |
| <div class=imageText></div> |
| <div class=imageContainer |
| actual="%s" |
| expected="%s">Loading...</div> |
| <script> |
| (function() { |
| var preloadedImageCount = 0; |
| function preloadComplete() { |
| ++preloadedImageCount; |
| if (preloadedImageCount < 2) |
| return; |
| toggleImages(); |
| setInterval(toggleImages, 2000) |
| } |
| |
| function preloadImage(url) { |
| image = new Image(); |
| image.addEventListener('load', preloadComplete); |
| image.src = url; |
| return image; |
| } |
| |
| function toggleImages() { |
| if (text.textContent == 'Expected Image') { |
| text.textContent = 'Actual Image'; |
| container.replaceChild(actualImage, container.firstChild); |
| } else { |
| text.textContent = 'Expected Image'; |
| container.replaceChild(expectedImage, container.firstChild); |
| } |
| } |
| |
| var text = document.querySelector('.imageText'); |
| var container = document.querySelector('.imageContainer'); |
| var actualImage = preloadImage(container.getAttribute('actual')); |
| var expectedImage = preloadImage(container.getAttribute('expected')); |
| })(); |
| </script> |
| </body> |
| </html> |
| """ % (test, get_relative_path(diff), get_relative_path(actual), expected) |
| |
| diffdir = self._create_diff_for_test(outdir, test) |
| pretty_diff_name = result + '-pretty-diff.html' |
| pretty_diff = os.path.abspath(os.path.join(diffdir, pretty_diff_name)) |
| f = open(pretty_diff, 'w') |
| f.write(html) |
| f.close() |
| |
| return os.path.join(test, pretty_diff_name) |
| |
| class HTMLPrettyDiffText(HTMLPrettyDiff): |
| def write(self, test, outdir, result, actual, expected, diff): |
| import difflib |
| |
| actual_file = open(os.path.join(outdir, actual), 'r') |
| expected_file = open(expected, 'r') |
| html = difflib.HtmlDiff().make_file(actual_file.readlines(), |
| expected_file.readlines(), |
| "Actual", "Expected", context=True) |
| actual_file.close() |
| expected_file.close() |
| |
| diffdir = self._create_diff_for_test(outdir, test) |
| pretty_diff_name = result + '-pretty-diff.html' |
| pretty_diff = os.path.abspath(os.path.join(diffdir, pretty_diff_name)) |
| f = open(pretty_diff, 'w') |
| f.write(html) |
| f.close() |
| |
| return os.path.join(test, pretty_diff_name) |
| |
| def create_pretty_diff(backend): |
| if backend.get_diff_ext() == '.diff.png': |
| return HTMLPrettyDiffImage() |
| # Disable pretty diff for Text files for now, since HtmlDiff().make_file() is |
| # entering in an infinite loop with some files. We need to either fix that somehow or |
| # find a different way to generate pretty diffs of text files. |
| #if backend.get_diff_ext() == '.diff': |
| # return HTMLPrettyDiffText() |
| return None |
| |
| class BackendTestResult: |
| |
| def __init__(self, test, refsdir, outdir, backend, results): |
| self._test = test |
| self._refsdir = refsdir |
| self._outdir = outdir |
| self._backend = backend |
| self.config = Config() |
| |
| self._results = [] |
| |
| ref_path = os.path.join(self._refsdir, self._test) |
| if not backend.has_md5(ref_path): |
| return |
| ref_names = backend.get_ref_names(ref_path) |
| for result in results: |
| basename = os.path.basename(result) |
| if basename in ref_names: |
| self._results.append(basename) |
| |
| def is_failed(self): |
| return len(self._results) > 0 |
| |
| def is_crashed(self): |
| return self._backend.is_crashed(os.path.join(self._outdir, self._test)) |
| |
| def is_failed_to_run(self): |
| return self._backend.is_failed(os.path.join(self._outdir, self._test)) |
| |
| def get_stderr(self): |
| return self._backend.get_stderr(os.path.join(self._outdir, self._test)) |
| |
| def get_failed_html(self): |
| html = "" |
| for result in self._results: |
| actual = os.path.join(self._test, result) |
| actual_path = os.path.join(self._outdir, actual) |
| expected = os.path.join(self._refsdir, self._test, result) |
| if self.config.abs_paths: |
| expected = os.path.abspath(expected) |
| html += "<li><a href='../%s'>actual</a> <a href='%s'>expected</a> " % (actual, expected) |
| if self._backend.has_diff(actual_path): |
| diff = actual + self._backend.get_diff_ext() |
| html += "<a href='../%s'>diff</a> " % (diff) |
| if self.config.pretty_diff: |
| pretty_diff = create_pretty_diff(self._backend) |
| if pretty_diff: |
| html += "<a href='%s'>pretty diff</a> " % (pretty_diff.write (self._test, self._outdir, result, actual, expected, diff)) |
| html += "</li>\n" |
| |
| if html: |
| return "<ul>%s</ul>\n" % (html) |
| return "" |
| |
| |
| class TestResult: |
| |
| def __init__(self, docsdir, refsdir, outdir, resultdir, results, backends): |
| self._refsdir = refsdir |
| self._outdir = outdir |
| self.config = Config() |
| |
| self._test = resultdir[len(self._outdir):].lstrip('/') |
| self._doc = os.path.join(docsdir, self._test) |
| self._results = {} |
| for backend in backends: |
| ref_path = os.path.join(self._refsdir, self._test) |
| if not backend.has_md5(ref_path) and not backend.is_crashed(ref_path) and not backend.is_failed(ref_path): |
| continue |
| self._results[backend] = BackendTestResult(self._test, refsdir, outdir, backend, results) |
| |
| def get_test(self): |
| return self._test |
| |
| def is_failed(self): |
| for backend in self._results: |
| if self._results[backend].is_failed(): |
| return True |
| return False; |
| |
| def get_failed_html(self): |
| html = "" |
| for backend in self._results: |
| if self._results[backend].is_crashed() or self._results[backend].is_failed_to_run(): |
| continue |
| backend_html = self._results[backend].get_failed_html() |
| if not backend_html: |
| continue |
| |
| html += "<li>%s " % (backend.get_name()) |
| stderr = self._results[backend].get_stderr() |
| if os.path.exists(stderr): |
| stderr_name = stderr[len(self._outdir):].lstrip('/') |
| html += "<a href='../%s'>stderr</a>" % (stderr_name) |
| html += "</li>\n%s" % (backend_html) |
| |
| if html: |
| return "<h2><a name='%s'><a href='%s'>%s</a></a></h2>\n<ul>%s</ul><a href='#top'>Top</a>\n" % (self._test, self._doc, self._test, html) |
| return "" |
| |
| def get_crashed_html(self): |
| html = "" |
| for backend in self._results: |
| if not self._results[backend].is_crashed(): |
| continue |
| |
| html += "<li><a href='%s'>%s</a> (%s)</li>\n" % (self._doc, self._test, backend.get_name()) |
| |
| if html: |
| return "<ul>%s</ul>\n" % (html) |
| return "" |
| |
| def get_failed_to_run_html(self): |
| html = "" |
| for backend in self._results: |
| status = self._results[backend].is_failed_to_run() |
| if not status: |
| continue |
| |
| html += "<li><a href='%s'>%s</a> [Status: %d] (%s)</li>\n" % (self._doc, self._test, status, backend.get_name()) |
| |
| if html: |
| return "<ul>%s</ul>\n" % (html) |
| return "" |
| |
| class HTMLReport: |
| |
| def __init__(self, docsdir, refsdir, outdir): |
| self._docsdir = docsdir |
| self._refsdir = refsdir |
| self._outdir = outdir |
| self._htmldir = os.path.join(outdir, 'html') |
| self.config = Config() |
| |
| try: |
| os.makedirs(self._htmldir) |
| except OSError as e: |
| if e.errno != errno.EEXIST: |
| raise |
| except: |
| raise |
| |
| def create(self, launch_browser): |
| html = "<html><body><a name='top'></a>" |
| if os.path.exists(os.path.join(self._outdir, '.exited_early')): |
| html += "<p style='border: 3px solid red; font-weight: bold; display: inline-block; padding: 3px'>Testing exited early</p>" |
| if self.config.backends: |
| backends = [get_backend(name) for name in self.config.backends] |
| else: |
| backends = get_all_backends() |
| |
| results = {} |
| for root, dirs, files in os.walk(self._outdir, False): |
| if not files: |
| continue |
| if not root.lower().endswith('.pdf'): |
| continue |
| if root.startswith(self._htmldir): |
| continue |
| |
| results[root] = TestResult(self._docsdir, self._refsdir, self._outdir, root, files, backends) |
| |
| failed_anchors = [] |
| failed = "" |
| crashed = "" |
| failed_to_run = "" |
| for test_name, test in sorted(results.items()): |
| if test.is_failed(): |
| failed_anchors.append(test.get_test()) |
| failed += test.get_failed_html() |
| crashed += test.get_crashed_html() |
| failed_to_run += test.get_failed_to_run_html() |
| |
| if failed: |
| failed = "<h1><a name='failed'>Tests Failed (differences were found)</name></h1>\n%s" % (failed) |
| if crashed: |
| crashed = "<h1><a name='crashed'>Tests Crashed</a></h1>\n%s" % (crashed) |
| if failed_to_run: |
| failed_to_run = "<h1><a name='failed_to_run'>Tests that failed to run (command returned an error status)</a></h1>\n%s" % (failed_to_run) |
| |
| if failed or crashed or failed_to_run: |
| html += "<ul>\n" |
| if failed: |
| html += "<li><a href='#failed'>Tests Failed (differences were found)</a></li>\n<ul>" |
| for anchor in failed_anchors: |
| html += "<li><a href='#%s'>%s</a></li>" % (anchor, anchor) |
| html += "</ul>\n" |
| if crashed: |
| html += "<li><a href='#crashed'>Tests Crashed</a></li>\n" |
| if failed_to_run: |
| html += "<li><a href='#failed_to_run'>Tests that failed to run (command returned an error status)</a></li>\n" |
| html += "</ul>\n" |
| |
| html += failed + crashed + failed_to_run + "</body></html>" |
| |
| report_index = os.path.join(self._htmldir, 'index.html') |
| with open(report_index, 'w') as f: |
| f.write(html) |
| |
| if launch_browser: |
| subprocess.Popen(['xdg-open', report_index]) |