#! /usr/bin/env python # GLib Testing Framework Utility -*- Mode: python; -*- # Copyright (C) 2007 Imendio AB # Authors: Tim Janik # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. import sys, re, xml.dom.minidom pkginstall_configvars = { #@PKGINSTALL_CONFIGVARS_IN24LINES@ # configvars are substituted upon script installation } # xml utilities def find_child (node, child_name): for child in node.childNodes: if child.nodeName == child_name: return child return None def list_children (node, child_name): rlist = [] for child in node.childNodes: if child.nodeName == child_name: rlist += [ child ] return rlist def find_node (node, name = None): if not node or node.nodeName == name or not name: return node for child in node.childNodes: c = find_node (child, name) if c: return c return None def node_as_text (node, name = None): if name: node = find_node (node, name) txt = '' if node: if node.nodeValue: txt += node.nodeValue for child in node.childNodes: txt += node_as_text (child) return txt def attribute_as_text (node, aname, node_name = None): node = find_node (node, node_name) if not node: return '' attr = node.attributes.get (aname, '') if hasattr (attr, 'value'): return attr.value return '' # HTML utilities def html_indent_string (n): uncollapsible_space = '  ' # HTML won't compress alternating sequences of ' ' and ' ' string = '' for i in range (0, (n + 1) / 2): string += uncollapsible_space return string # TestBinary object, instantiated per test binary in the log file class TestBinary: def __init__ (self, name): self.name = name self.testcases = [] self.duration = 0 self.success_cases = 0 self.skipped_cases = 0 self.file = '???' self.random_seed = '' # base class to handle processing/traversion of XML nodes class TreeProcess: def __init__ (self): self.nest_level = 0 def trampoline (self, node): name = node.nodeName if name == '#text': self.handle_text (node) else: try: method = getattr (self, 'handle_' + re.sub ('[^a-zA-Z0-9]', '_', name)) except: method = None if method: return method (node) else: return self.process_recursive (name, node) def process_recursive (self, node_name, node): self.process_children (node) def process_children (self, node): self.nest_level += 1 for child in node.childNodes: self.trampoline (child) self.nest_level += 1 # test report reader, this class collects some statistics and merges duplicate test binary runs class ReportReader (TreeProcess): def __init__ (self): TreeProcess.__init__ (self) self.binary_names = [] self.binaries = {} self.last_binary = None def binary_list (self): lst = [] for name in self.binary_names: lst += [ self.binaries[name] ] return lst def handle_testcase (self, node): self.last_binary.testcases += [ node ] result = attribute_as_text (node, 'result', 'status') if result == 'success': self.last_binary.success_cases += 1 if bool (int (attribute_as_text (node, 'skipped') + '0')): self.last_binary.skipped_cases += 1 def handle_text (self, node): pass def handle_testbinary (self, node): path = node.attributes.get ('path', None).value if self.binaries.get (path, -1) == -1: self.binaries[path] = TestBinary (path) self.binary_names += [ path ] self.last_binary = self.binaries[path] dn = find_child (node, 'duration') dur = node_as_text (dn) try: dur = float (dur) except: dur = 0 if dur: self.last_binary.duration += dur bin = find_child (node, 'binary') if bin: self.last_binary.file = attribute_as_text (bin, 'file') rseed = find_child (node, 'random-seed') if rseed: self.last_binary.random_seed = node_as_text (rseed) self.process_children (node) # HTML report generation class class ReportWriter (TreeProcess): # Javascript/CSS snippet to toggle element visibility cssjs = r''' ''' def __init__ (self, binary_list): TreeProcess.__init__ (self) self.binaries = binary_list self.bcounter = 0 self.tcounter = 0 self.total_tcounter = 0 self.total_fcounter = 0 self.total_duration = 0 self.indent_depth = 0 self.lastchar = '' def oprint (self, message): sys.stdout.write (message) if message: self.lastchar = message[-1] def handle_text (self, node): self.oprint (node.nodeValue) def handle_testcase (self, node, binary): skipped = bool (int (attribute_as_text (node, 'skipped') + '0')) if skipped: return # skipped tests are uninteresting for HTML reports path = attribute_as_text (node, 'path') duration = node_as_text (node, 'duration') result = attribute_as_text (node, 'result', 'status') rcolor = { 'success': 'bgcolor="lightgreen"', 'failed': 'bgcolor="red"', }.get (result, '') if result != 'success': duration = '-' # ignore bogus durations self.oprint ('\n' % (self.bcounter, self.tcounter, result)) self.oprint ('%s %s %s \n' % (html_indent_string (4), path, duration)) perflist = list_children (node, 'performance') if result != 'success': rlist = list_children (node, 'error') txt = '' for enode in rlist: txt += node_as_text (enode) if txt and txt[-1] != '\n': txt += '\n' txt = re.sub (r'"', r'\\"', txt) txt = re.sub (r'\n', r'\\n', txt) txt = re.sub (r'&', r'&', txt) txt = re.sub (r'<', r'<', txt) self.oprint ('\n' % (self.bcounter, self.tcounter, txt)) self.oprint ('Details\n' % ('TestResultWindow', binary.file, binary.random_seed, path, self.bcounter, self.tcounter)) elif perflist: presults = [] for perf in perflist: pmin = bool (int (attribute_as_text (perf, 'minimize'))) pmax = bool (int (attribute_as_text (perf, 'maximize'))) pval = float (attribute_as_text (perf, 'value')) txt = node_as_text (perf) txt = re.sub (r'&', r'&', txt) txt = re.sub (r'<', r'>', txt) txt = 'Performance(' + (pmin and 'minimized' or 'maximized') + '): ' + txt.strip() + '
\n' txt = re.sub (r'"', r'\\"', txt) txt = re.sub (r'\n', r'\\n', txt) presults += [ (pval, txt) ] presults.sort() ptxt = ''.join ([e[1] for e in presults]) self.oprint ('\n' % (self.bcounter, self.tcounter, ptxt)) self.oprint ('Details\n' % ('TestResultWindow', binary.file, binary.random_seed, path, self.bcounter, self.tcounter)) else: self.oprint ('-\n') self.oprint ('%s\n' % (rcolor, result)) self.oprint ('\n') self.tcounter += 1 self.total_tcounter += 1 self.total_fcounter += result != 'success' def handle_binary (self, binary): self.tcounter = 1 self.bcounter += 1 self.total_duration += binary.duration self.oprint ('%s%f \n' % (binary.name, binary.duration)) erlink, oklink = ('', '') real_cases = len (binary.testcases) - binary.skipped_cases if binary.success_cases < real_cases: erlink = 'href="javascript:toggle_display (\'ResultTable\', \'tr\', \'b%u_\', \'failed\')"' % self.bcounter if binary.success_cases: oklink = 'href="javascript:toggle_display (\'ResultTable\', \'tr\', \'b%u_\', \'success\')"' % self.bcounter self.oprint ('ER\n' % erlink) self.oprint ('OK\n' % oklink) self.oprint ('\n') perc = binary.success_cases * 100.0 / real_cases pcolor = { 100 : 'bgcolor="lightgreen"', 0 : 'bgcolor="red"', }.get (int (perc), 'bgcolor="yellow"') self.oprint ('%.2f%%\n' % (pcolor, perc)) self.oprint ('\n') for tc in binary.testcases: self.handle_testcase (tc, binary) def handle_totals (self): self.oprint ('') self.oprint ('Totals: %u Binaries, %u Tests, %u Failed, %u Succeeded' % (self.bcounter, self.total_tcounter, self.total_fcounter, self.total_tcounter - self.total_fcounter)) self.oprint ('%f\n' % self.total_duration) self.oprint ('-\n') perc = (self.total_tcounter - self.total_fcounter) * 100.0 / self.total_tcounter pcolor = { 100 : 'bgcolor="lightgreen"', 0 : 'bgcolor="red"', }.get (int (perc), 'bgcolor="yellow"') self.oprint ('%.2f%%\n' % (pcolor, perc)) self.oprint ('\n') def printout (self): self.oprint ('\n') self.oprint ('GTester Unit Test Report\n') self.oprint (self.cssjs) self.oprint ('\n') self.oprint ('\n') self.oprint ('

GTester Unit Test Report

\n') self.oprint ('\n\n') self.oprint ('\n') self.oprint ('\n') self.oprint ('\n') self.oprint ('\n') self.oprint ('\n') for tb in self.binaries: self.handle_binary (tb) self.handle_totals() self.oprint ('
Program / Testcase Duration (sec)ViewResult
\n') self.oprint ('\n') self.oprint ('\n') # main program handling def parse_files_and_args (): from sys import argv, stdin files = [] arg_iter = sys.argv[1:].__iter__() rest = len (sys.argv) - 1 for arg in arg_iter: rest -= 1 if arg == '--help' or arg == '-h': print_help () sys.exit (0) elif arg == '--version' or arg == '-v': print_help (False) sys.exit (0) else: files = files + [ arg ] return files def print_help (with_help = True): import os print "gtester-report (GLib utils) version", pkginstall_configvars.get ('glib-version', '0.0-uninstalled') if not with_help: return print "Usage: %s [OPTIONS] " % os.path.basename (sys.argv[0]) print "Generate HTML reports from the XML log files generated by gtester." print "Options:" print " --help, -h print this help message" print " --version, -v print version info" def main(): from sys import argv, stdin files = parse_files_and_args() if len (files) != 1: print_help (True) sys.exit (1) xd = xml.dom.minidom.parse (files[0]) rr = ReportReader() rr.trampoline (xd) rw = ReportWriter (rr.binary_list()) rw.printout() if __name__ == '__main__': main()