未验证 提交 56aa6112 编写于 作者: L Lukáš Doktor

Merging pull request 1385

* https://github.com/avocado-framework/avocado:
  Makefile: use setup.py for getting the current versions
  Result: have a fallback result instance
  HTML Result: use existing result instead of custom result dict
  HTML Result: give the right name to ReportModel input
  HTML result: use pkg_resources to locate required resources
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
PYTHON=$(shell which python) PYTHON=$(shell which python)
PYTHON26=$(shell $(PYTHON) -V 2>&1 | grep 2.6 -q && echo true || echo false) PYTHON26=$(shell $(PYTHON) -V 2>&1 | grep 2.6 -q && echo true || echo false)
VERSION=$(shell $(PYTHON) $(CURDIR)/avocado/core/version.py) VERSION=$(shell $(PYTHON) setup.py --version)
DESTDIR=/ DESTDIR=/
BUILDIR=$(CURDIR)/debian/avocado BUILDIR=$(CURDIR)/debian/avocado
PROJECT=avocado PROJECT=avocado
......
...@@ -17,16 +17,14 @@ HTML output module. ...@@ -17,16 +17,14 @@ HTML output module.
import codecs import codecs
import os import os
import shutil import shutil
import sys
import time import time
import subprocess import subprocess
import urllib import urllib
import pystache import pystache
import pkg_resources
from .result import Result from .result import Result
from ..utils import path as utils_path
from ..utils import runtime
def check_resource_requirements(): def check_resource_requirements():
...@@ -35,23 +33,19 @@ def check_resource_requirements(): ...@@ -35,23 +33,19 @@ def check_resource_requirements():
Currently, only the template file is looked for Currently, only the template file is looked for
""" """
base_path = os.path.dirname(sys.modules[__name__].__file__) return pkg_resources.resource_exists(
html_resources_path = os.path.join(base_path, 'resources', 'htmlresult') 'avocado.core',
template = os.path.join(html_resources_path, 'templates', 'report.mustache') 'resources/htmlresult/templates/report.mustache')
return os.path.exists(template)
class ReportModel(object): class ReportModel(object):
""" """
Prepares JSON that can be passed up to mustache for rendering. Prepares an object that can be passed up to mustache for rendering.
""" """
def __init__(self, json_input, html_output): def __init__(self, result, html_output):
""" self.result = result
Base JSON that comes from test results.
"""
self.json = json_input
self.html_output = html_output self.html_output = html_output
self.html_output_dir = os.path.abspath(os.path.dirname(html_output)) self.html_output_dir = os.path.abspath(os.path.dirname(html_output))
...@@ -69,13 +63,14 @@ class ReportModel(object): ...@@ -69,13 +63,14 @@ class ReportModel(object):
return value return value
def job_id(self): def job_id(self):
return self.json['job_id'] return self.result.job_unique_id
def execution_time(self): def execution_time(self):
return "%.2f" % self.json['time'] return "%.2f" % self.result.tests_total_time
def results_dir(self, relative_links=True): def results_dir(self, relative_links=True):
results_dir = os.path.abspath(os.path.dirname(self.json['debuglog'])) results_dir = os.path.abspath(os.path.dirname(
self.result.logfile))
if relative_links: if relative_links:
return os.path.relpath(results_dir, self.html_output_dir) return os.path.relpath(results_dir, self.html_output_dir)
else: else:
...@@ -85,17 +80,20 @@ class ReportModel(object): ...@@ -85,17 +80,20 @@ class ReportModel(object):
return os.path.basename(self.results_dir(False)) return os.path.basename(self.results_dir(False))
def logdir(self): def logdir(self):
return os.path.relpath(self.json['logdir'], self.html_output_dir) logdir = os.path
path = os.path.relpath(self.result.logdir,
self.html_output_dir)
return urllib.quote(path)
def total(self): def total(self):
return self.json['total'] return self.result.tests_total
def passed(self): def passed(self):
return self.json['pass'] return self.result.passed
def pass_rate(self): def pass_rate(self):
total = float(self.json['total']) total = float(self.result.tests_total)
passed = float(self.json['pass']) passed = float(self.result.passed)
if total > 0: if total > 0:
pr = 100 * (passed / total) pr = 100 * (passed / total)
else: else:
...@@ -130,27 +128,34 @@ class ReportModel(object): ...@@ -130,27 +128,34 @@ class ReportModel(object):
"RUNNING": "info", "RUNNING": "info",
"NOSTATUS": "info", "NOSTATUS": "info",
"INTERRUPTED": "danger"} "INTERRUPTED": "danger"}
test_info = self.json['tests'] test_info = []
results_dir = self.results_dir(False) results_dir = self.results_dir(False)
for t in test_info: for t in self.result.tests:
formatted = {}
logdir = os.path.join(results_dir, 'test-results', t['logdir']) logdir = os.path.join(results_dir, 'test-results', t['logdir'])
t['logdir'] = os.path.relpath(logdir, self.html_output_dir) formatted['logdir'] = os.path.relpath(logdir, self.html_output_dir)
logfile = os.path.join(logdir, 'debug.log') logfile = os.path.join(logdir, 'debug.log')
t['logfile'] = os.path.relpath(logfile, self.html_output_dir) formatted['logfile'] = os.path.relpath(logfile, self.html_output_dir)
t['logfile_basename'] = os.path.basename(logfile) formatted['logfile_basename'] = os.path.basename(logfile)
t['time'] = "%.2f" % t['time'] formatted['time'] = "%.2f" % t['time_elapsed']
t['time_start'] = time.strftime("%Y-%m-%d %H:%M:%S", formatted['time_start'] = time.strftime("%Y-%m-%d %H:%M:%S",
time.localtime(t['time_start'])) time.localtime(t['time_start']))
t['row_class'] = mapping[t['status']] formatted['row_class'] = mapping[t['status']]
exhibition_limit = 40 exhibition_limit = 40
if len(t['fail_reason']) > exhibition_limit: fail_reason = t.get('fail_reason')
t['fail_reason'] = ('<a data-container="body" ' if fail_reason is None:
'data-toggle="popover" ' fail_reason = '<unknown>'
'data-placement="top" ' fail_reason = str(fail_reason)
'title="Error Details" ' if len(fail_reason) > exhibition_limit:
'data-content="%s">%s...</a>' % fail_reason = ('<a data-container="body" '
(t['fail_reason'], 'data-toggle="popover" '
t['fail_reason'][:exhibition_limit])) 'data-placement="top" '
'title="Error Details" '
'data-content="%s">%s...</a>' %
('fail_reason',
'fail_reason'[:exhibition_limit]))
formatted['fail_reason'] = fail_reason
test_info.append(formatted)
return test_info return test_info
def _sysinfo_phase(self, phase): def _sysinfo_phase(self, phase):
...@@ -209,67 +214,48 @@ class HTMLResult(Result): ...@@ -209,67 +214,48 @@ class HTMLResult(Result):
self.output = force_html_file self.output = force_html_file
else: else:
self.output = self.args.html_output self.output = self.args.html_output
self.json = None
def start_tests(self):
"""
Called once before any tests are executed.
"""
Result.start_tests(self)
self.json = {'debuglog': self.logfile,
'job_id': runtime.CURRENT_JOB.unique_id,
'tests': []}
def end_test(self, state):
"""
Called when the given test has been run.
:param state: result of :class:`avocado.core.test.Test.get_state`.
:type state: dict
"""
Result.end_test(self, state)
t = {'test': str(state.get('name', "<unknown>")),
'url': state.get('name', "<unknown>"),
'time_start': state.get('time_start', -1),
'time_end': state.get('time_end', -1),
'time': state.get('time_elapsed', -1),
'status': state.get('status', "ERROR"),
'fail_reason': str(state.get('fail_reason', "<unknown>")),
'whiteboard': state.get('whiteboard', "<unknown>"),
'logdir': urllib.quote(state.get('logdir', "<unknown>")),
'logfile': urllib.quote(state.get('logfile', "<unknown>"))
}
self.json['tests'].append(t)
def end_tests(self): def end_tests(self):
""" """
Called once after all tests are executed. Called once after all tests are executed.
""" """
Result.end_tests(self) Result.end_tests(self)
self.json.update({
'total': len(self.json['tests']),
'pass': self.passed,
'errors': self.errors,
'failures': self.failed,
'skip': self.skipped,
'time': self.tests_total_time
})
self._render_report() self._render_report()
def _copy_static_resources(self):
module = 'avocado.core'
base_path = 'resources/htmlresult/static'
for top_dir in pkg_resources.resource_listdir(module, base_path):
rsrc_dir = base_path + '/%s' % top_dir
if pkg_resources.resource_isdir(module, rsrc_dir):
rsrc_files = pkg_resources.resource_listdir(module, rsrc_dir)
for rsrc_file in rsrc_files:
source = pkg_resources.resource_filename(
module,
rsrc_dir + '/%s' % rsrc_file)
dest = os.path.join(
os.path.dirname(os.path.abspath(self.output)),
top_dir,
os.path.basename(source))
pkg_resources.ensure_directory(dest)
shutil.copy(source, dest)
def _render_report(self): def _render_report(self):
context = ReportModel(json_input=self.json, html_output=self.output) context = ReportModel(result=self,
base_path = os.path.dirname(sys.modules[__name__].__file__) html_output=self.output)
html_resources_path = os.path.join(base_path, 'resources', 'htmlresult') template = pkg_resources.resource_string(
template = os.path.join(html_resources_path, 'templates', 'report.mustache') 'avocado.core',
'resources/htmlresult/templates/report.mustache')
# pylint: disable=E0611 # pylint: disable=E0611
try: try:
if hasattr(pystache, 'Renderer'): if hasattr(pystache, 'Renderer'):
renderer = pystache.Renderer('utf-8', 'utf-8') renderer = pystache.Renderer('utf-8', 'utf-8')
report_contents = renderer.render(open(template, 'r').read(), context) report_contents = renderer.render(template, context)
else: else:
from pystache import view from pystache import view
v = view.View(open(template, 'r').read(), context) v = view.View(template, context)
report_contents = v.render('utf8') # encodes into ascii report_contents = v.render('utf8') # encodes into ascii
report_contents = codecs.decode("utf8") # decode to unicode report_contents = codecs.decode("utf8") # decode to unicode
except UnicodeDecodeError as details: except UnicodeDecodeError as details:
...@@ -278,23 +264,14 @@ class HTMLResult(Result): ...@@ -278,23 +264,14 @@ class HTMLResult(Result):
ui = logging.getLogger("avocado.app") ui = logging.getLogger("avocado.app")
ui.critical("\n" + ("-" * 80)) ui.critical("\n" + ("-" * 80))
ui.critical("HTML failed to render the template: %s\n\n", ui.critical("HTML failed to render the template: %s\n\n",
open(template, 'r').read()) template)
ui.critical("-" * 80) ui.critical("-" * 80)
ui.critical("%s:\n\n", details) ui.critical("%s:\n\n", details)
ui.critical("%r\n\n", self.json)
ui.critical("%r", getattr(details, "object", "object not found")) ui.critical("%r", getattr(details, "object", "object not found"))
ui.critical("-" * 80) ui.critical("-" * 80)
raise raise
static_basedir = os.path.join(html_resources_path, 'static') self._copy_static_resources()
output_dir = os.path.dirname(os.path.abspath(self.output))
utils_path.init_dir(output_dir)
for resource_dir in os.listdir(static_basedir):
res_dir = os.path.join(static_basedir, resource_dir)
out_dir = os.path.join(output_dir, resource_dir)
if os.path.exists(out_dir):
shutil.rmtree(out_dir)
shutil.copytree(res_dir, out_dir)
with codecs.open(self.output, 'w', 'utf-8') as report_file: with codecs.open(self.output, 'w', 'utf-8') as report_file:
report_file.write(report_contents) report_file.write(report_contents)
......
...@@ -264,6 +264,8 @@ class Job(object): ...@@ -264,6 +264,8 @@ class Job(object):
for klass in self.args.test_result_classes: for klass in self.args.test_result_classes:
test_result_instance = klass(self) test_result_instance = klass(self)
self.result_proxy.add_output_plugin(test_result_instance) self.result_proxy.add_output_plugin(test_result_instance)
else:
self.result_proxy.add_output_plugin(result.Result(self))
def _make_test_result(self): def _make_test_result(self):
""" """
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册