From patchwork Wed Mar 9 09:21:04 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lucas Meneghel Rodrigues X-Patchwork-Id: 620941 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id p299LXsZ007719 for ; Wed, 9 Mar 2011 09:21:33 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756946Ab1CIJV1 (ORCPT ); Wed, 9 Mar 2011 04:21:27 -0500 Received: from mx1.redhat.com ([209.132.183.28]:47532 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756873Ab1CIJVV (ORCPT ); Wed, 9 Mar 2011 04:21:21 -0500 Received: from int-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id p299LKPL022761 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Wed, 9 Mar 2011 04:21:20 -0500 Received: from freedom.redhat.com (vpn-8-153.rdu.redhat.com [10.11.8.153]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id p299LDHs025539; Wed, 9 Mar 2011 04:21:17 -0500 From: Lucas Meneghel Rodrigues To: autotest@test.kernel.org Cc: kvm@vger.kernel.org, mgoldish@redhat.com, crosa@redhat.com, Lucas Meneghel Rodrigues Subject: [PATCH 1/7] KVM test: Move test utilities to client/tools Date: Wed, 9 Mar 2011 06:21:04 -0300 Message-Id: <1299662470-3017-2-git-send-email-lmr@redhat.com> In-Reply-To: <1299662470-3017-1-git-send-email-lmr@redhat.com> References: <1299662470-3017-1-git-send-email-lmr@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.22 Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Wed, 09 Mar 2011 09:21:34 +0000 (UTC) diff --git a/client/tools/__init__.py b/client/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/client/tools/cd_hash.py b/client/tools/cd_hash.py new file mode 100755 index 0000000..04f8cbe --- /dev/null +++ b/client/tools/cd_hash.py @@ -0,0 +1,48 @@ +#!/usr/bin/python +""" +Program that calculates several hashes for a given CD image. + +@copyright: Red Hat 2008-2009 +""" + +import os, sys, optparse, logging +import common +import kvm_utils +from autotest_lib.client.common_lib import logging_manager +from autotest_lib.client.bin import utils + + +if __name__ == "__main__": + parser = optparse.OptionParser("usage: %prog [options] [filenames]") + options, args = parser.parse_args() + + logging_manager.configure_logging(kvm_utils.KvmLoggingConfig()) + + if args: + filenames = args + else: + parser.print_help() + sys.exit(1) + + for filename in filenames: + filename = os.path.abspath(filename) + + file_exists = os.path.isfile(filename) + can_read_file = os.access(filename, os.R_OK) + if not file_exists: + logging.critical("File %s does not exist!", filename) + continue + if not can_read_file: + logging.critical("File %s does not have read permissions!", + filename) + continue + + logging.info("Hash values for file %s", os.path.basename(filename)) + logging.info("md5 (1m): %s", utils.hash_file(filename, 1024*1024, + method="md5")) + logging.info("sha1 (1m): %s", utils.hash_file(filename, 1024*1024, + method="sha1")) + logging.info("md5 (full): %s", utils.hash_file(filename, method="md5")) + logging.info("sha1 (full): %s", utils.hash_file(filename, + method="sha1")) + logging.info("") diff --git a/client/tools/html_report.py b/client/tools/html_report.py new file mode 100755 index 0000000..8b4b109 --- /dev/null +++ b/client/tools/html_report.py @@ -0,0 +1,1727 @@ +#!/usr/bin/python +""" +Script used to parse the test results and generate an HTML report. + +@copyright: (c)2005-2007 Matt Kruse (javascripttoolbox.com) +@copyright: Red Hat 2008-2009 +@author: Dror Russo (drusso@redhat.com) +""" + +import os, sys, re, getopt, time, datetime, commands +import common + + +format_css = """ +html,body { + padding:0; + color:#222; + background:#FFFFFF; +} + +body { + padding:0px; + font:76%/150% "Lucida Grande", "Lucida Sans Unicode", Lucida, Verdana, Geneva, Arial, Helvetica, sans-serif; +} + +#page_title{ + text-decoration:none; + font:bold 2em/2em Arial, Helvetica, sans-serif; + text-transform:none; + text-shadow: 2px 2px 2px #555; + text-align: left; + color:#555555; + border-bottom: 1px solid #555555; +} + +#page_sub_title{ + text-decoration:none; + font:bold 16px Arial, Helvetica, sans-serif; + text-transform:uppercase; + text-shadow: 2px 2px 2px #555; + text-align: left; + color:#555555; + margin-bottom:0; +} + +#comment{ + text-decoration:none; + font:bold 10px Arial, Helvetica, sans-serif; + text-transform:none; + text-align: left; + color:#999999; + margin-top:0; +} + + +#meta_headline{ + text-decoration:none; + font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ; + text-align: left; + color:black; + font-weight: bold; + font-size: 14px; + } + + +table.meta_table +{text-align: center; +font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ; +width: 90%; +background-color: #FFFFFF; +border: 0px; +border-top: 1px #003377 solid; +border-bottom: 1px #003377 solid; +border-right: 1px #003377 solid; +border-left: 1px #003377 solid; +border-collapse: collapse; +border-spacing: 0px;} + +table.meta_table td +{background-color: #FFFFFF; +color: #000; +padding: 4px; +border-top: 1px #BBBBBB solid; +border-bottom: 1px #BBBBBB solid; +font-weight: normal; +font-size: 13px;} + + +table.stats +{text-align: center; +font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ; +width: 100%; +background-color: #FFFFFF; +border: 0px; +border-top: 1px #003377 solid; +border-bottom: 1px #003377 solid; +border-right: 1px #003377 solid; +border-left: 1px #003377 solid; +border-collapse: collapse; +border-spacing: 0px;} + +table.stats td{ +background-color: #FFFFFF; +color: #000; +padding: 4px; +border-top: 1px #BBBBBB solid; +border-bottom: 1px #BBBBBB solid; +font-weight: normal; +font-size: 11px;} + +table.stats th{ +background: #dcdcdc; +color: #000; +padding: 6px; +font-size: 12px; +border-bottom: 1px #003377 solid; +font-weight: bold;} + +table.stats td.top{ +background-color: #dcdcdc; +color: #000; +padding: 6px; +text-align: center; +border: 0px; +border-bottom: 1px #003377 solid; +font-size: 10px; +font-weight: bold;} + +table.stats th.table-sorted-asc{ + background-image: url(ascending.gif); + background-position: top left ; + background-repeat: no-repeat; +} + +table.stats th.table-sorted-desc{ + background-image: url(descending.gif); + background-position: top left; + background-repeat: no-repeat; +} + +table.stats2 +{text-align: left; +font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ; +width: 100%; +background-color: #FFFFFF; +border: 0px; +} + +table.stats2 td{ +background-color: #FFFFFF; +color: #000; +padding: 0px; +font-weight: bold; +font-size: 13px;} + + + +/* Put this inside a @media qualifier so Netscape 4 ignores it */ +@media screen, print { + /* Turn off list bullets */ + ul.mktree li { list-style: none; } + /* Control how "spaced out" the tree is */ + ul.mktree, ul.mktree ul , ul.mktree li { margin-left:10px; padding:0px; } + /* Provide space for our own "bullet" inside the LI */ + ul.mktree li .bullet { padding-left: 15px; } + /* Show "bullets" in the links, depending on the class of the LI that the link's in */ + ul.mktree li.liOpen .bullet { cursor: pointer; } + ul.mktree li.liClosed .bullet { cursor: pointer; } + ul.mktree li.liBullet .bullet { cursor: default; } + /* Sublists are visible or not based on class of parent LI */ + ul.mktree li.liOpen ul { display: block; } + ul.mktree li.liClosed ul { display: none; } + + /* Format menu items differently depending on what level of the tree they are in */ + /* Uncomment this if you want your fonts to decrease in size the deeper they are in the tree */ +/* + ul.mktree li ul li { font-size: 90% } +*/ +} +""" + + +table_js = """ +/** + * Copyright (c)2005-2007 Matt Kruse (javascripttoolbox.com) + * + * Dual licensed under the MIT and GPL licenses. + * This basically means you can use this code however you want for + * free, but don't claim to have written it yourself! + * Donations always accepted: http://www.JavascriptToolbox.com/donate/ + * + * Please do not link to the .js files on javascripttoolbox.com from + * your site. Copy the files locally to your server instead. + * + */ +/** + * Table.js + * Functions for interactive Tables + * + * Copyright (c) 2007 Matt Kruse (javascripttoolbox.com) + * Dual licensed under the MIT and GPL licenses. + * + * @version 0.981 + * + * @history 0.981 2007-03-19 Added Sort.numeric_comma, additional date parsing formats + * @history 0.980 2007-03-18 Release new BETA release pending some testing. Todo: Additional docs, examples, plus jQuery plugin. + * @history 0.959 2007-03-05 Added more "auto" functionality, couple bug fixes + * @history 0.958 2007-02-28 Added auto functionality based on class names + * @history 0.957 2007-02-21 Speed increases, more code cleanup, added Auto Sort functionality + * @history 0.956 2007-02-16 Cleaned up the code and added Auto Filter functionality. + * @history 0.950 2006-11-15 First BETA release. + * + * @todo Add more date format parsers + * @todo Add style classes to colgroup tags after sorting/filtering in case the user wants to highlight the whole column + * @todo Correct for colspans in data rows (this may slow it down) + * @todo Fix for IE losing form control values after sort? + */ + +/** + * Sort Functions + */ +var Sort = (function(){ + var sort = {}; + // Default alpha-numeric sort + // -------------------------- + sort.alphanumeric = function(a,b) { + return (a==b)?0:(asort.numeric.convert(b)) { + return (-1); + } + if (sort.numeric.convert(a)==sort.numeric.convert(b)) { + return 0; + } + if (sort.numeric.convert(a)0) { + var rows = section.rows; + for (var j=0,L2=rows.length; j0) { + var cells = row.cells; + for (var k=0,L3=cells.length; k1 && cells[cells.length-1].cellIndex>0) { + // Define the new function, overwrite the one we're running now, and then run the new one + (this.getCellIndex = function(td) { + return td.cellIndex; + })(td); + } + // Safari will always go through this slower block every time. Oh well. + for (var i=0,L=cells.length; i=0 && node.options) { + // Sort select elements by the visible text + return node.options[node.selectedIndex].text; + } + return ""; + }, + 'IMG':function(node) { + return node.name || ""; + } + }; + + /** + * Get the text value of a cell. Only use innerText if explicitly told to, because + * otherwise we want to be able to handle sorting on inputs and other types + */ + table.getCellValue = function(td,useInnerText) { + if (useInnerText && def(td.innerText)) { + return td.innerText; + } + if (!td.childNodes) { + return ""; + } + var childNodes=td.childNodes; + var ret = ""; + for (var i=0,L=childNodes.length; i-1) { + filters={ 'filter':filters.options[filters.selectedIndex].value }; + } + // Also allow for a regular input + if (filters.nodeName=="INPUT" && filters.type=="text") { + filters={ 'filter':"/"+filters.value+"/" }; + } + // Force filters to be an array + if (typeof(filters)=="object" && !filters.length) { + filters = [filters]; + } + + // Convert regular expression strings to RegExp objects and function strings to function objects + for (var i=0,L=filters.length; ipageend) { + hideRow = true; + } + } + } + + row.style.display = hideRow?"none":""; + } + } + + if (def(page)) { + // Check to see if filtering has put us past the requested page index. If it has, + // then go back to the last page and show it. + if (pagestart>=unfilteredrowcount) { + pagestart = unfilteredrowcount-(unfilteredrowcount%pagesize); + tdata.page = page = pagestart/pagesize; + for (var i=pagestart,L=unfilteredrows.length; i0) { + if (typeof(args.insert)=="function") { + func.insert(cell,colValues); + } + else { + var sel = ''; + cell.innerHTML += "
"+sel; + } + } + } + }); + if (val = classValue(t,table.FilteredRowcountPrefix)) { + tdata.container_filtered_count = document.getElementById(val); + } + if (val = classValue(t,table.RowcountPrefix)) { + tdata.container_all_count = document.getElementById(val); + } + }; + + /** + * Attach the auto event so it happens on load. + * use jQuery's ready() function if available + */ + if (typeof(jQuery)!="undefined") { + jQuery(table.auto); + } + else if (window.addEventListener) { + window.addEventListener( "load", table.auto, false ); + } + else if (window.attachEvent) { + window.attachEvent( "onload", table.auto ); + } + + return table; +})(); +""" + + +maketree_js = """/** + * Copyright (c)2005-2007 Matt Kruse (javascripttoolbox.com) + * + * Dual licensed under the MIT and GPL licenses. + * This basically means you can use this code however you want for + * free, but don't claim to have written it yourself! + * Donations always accepted: http://www.JavascriptToolbox.com/donate/ + * + * Please do not link to the .js files on javascripttoolbox.com from + * your site. Copy the files locally to your server instead. + * + */ +/* +This code is inspired by and extended from Stuart Langridge's aqlist code: + http://www.kryogenix.org/code/browser/aqlists/ + Stuart Langridge, November 2002 + sil@kryogenix.org + Inspired by Aaron's labels.js (http://youngpup.net/demos/labels/) + and Dave Lindquist's menuDropDown.js (http://www.gazingus.org/dhtml/?id=109) +*/ + +// Automatically attach a listener to the window onload, to convert the trees +addEvent(window,"load",convertTrees); + +// Utility function to add an event listener +function addEvent(o,e,f){ + if (o.addEventListener){ o.addEventListener(e,f,false); return true; } + else if (o.attachEvent){ return o.attachEvent("on"+e,f); } + else { return false; } +} + +// utility function to set a global variable if it is not already set +function setDefault(name,val) { + if (typeof(window[name])=="undefined" || window[name]==null) { + window[name]=val; + } +} + +// Full expands a tree with a given ID +function expandTree(treeId) { + var ul = document.getElementById(treeId); + if (ul == null) { return false; } + expandCollapseList(ul,nodeOpenClass); +} + +// Fully collapses a tree with a given ID +function collapseTree(treeId) { + var ul = document.getElementById(treeId); + if (ul == null) { return false; } + expandCollapseList(ul,nodeClosedClass); +} + +// Expands enough nodes to expose an LI with a given ID +function expandToItem(treeId,itemId) { + var ul = document.getElementById(treeId); + if (ul == null) { return false; } + var ret = expandCollapseList(ul,nodeOpenClass,itemId); + if (ret) { + var o = document.getElementById(itemId); + if (o.scrollIntoView) { + o.scrollIntoView(false); + } + } +} + +// Performs 3 functions: +// a) Expand all nodes +// b) Collapse all nodes +// c) Expand all nodes to reach a certain ID +function expandCollapseList(ul,cName,itemId) { + if (!ul.childNodes || ul.childNodes.length==0) { return false; } + // Iterate LIs + for (var itemi=0;itemi + + +KVM Autotest Results + + + + +""" % (format_css, table_js, maketree_js) + + + if output_file_name: + output = open(output_file_name, "w") + else: #if no output file defined, print html file to console + output = sys.stdout + # create html page + print >> output, html_prefix + print >> output, '

KVM Autotest Execution Report

' + + # formating date and time to print + t = datetime.datetime.now() + + epoch_sec = time.mktime(t.timetuple()) + now = datetime.datetime.fromtimestamp(epoch_sec) + + # basic statistics + total_executed = 0 + total_failed = 0 + total_passed = 0 + for res in results: + total_executed += 1 + if res['status'] == 'GOOD': + total_passed += 1 + else: + total_failed += 1 + stat_str = 'No test cases executed' + if total_executed > 0: + failed_perct = int(float(total_failed)/float(total_executed)*100) + stat_str = ('From %d tests executed, %d have passed (%d%% failures)' % + (total_executed, total_passed, failed_perct)) + + kvm_ver_str = metadata['kvmver'] + + print >> output, '' + print >> output, '' % host + print >> output, '' % tag + print >> output, '' % now.ctime() + print >> output, ''% stat_str + print >> output, '' + print >> output, '' % kvm_ver_str + print >> output, '
HOST:%s
RESULTS DIR:%s
DATE:%s
STATS:%s
KVM VERSION:%s
' + + + ## print test results + print >> output, '
' + print >> output, '

Test Results

' + print >> output, '

click on table headers to asc/desc sort

' + result_table_prefix = """ + + + + + + + + + + +""" + print >> output, result_table_prefix + for res in results: + print >> output, '' + print >> output, '' % res['time'] + print >> output, '' % res['testcase'] + if res['status'] == 'GOOD': + print >> output, '' + elif res['status'] == 'FAIL': + print >> output, '' + elif res['status'] == 'ERROR': + print >> output, '' + else: + print >> output, '' % res['status'] + # print exec time (seconds) + print >> output, '' % res['exec_time_sec'] + # print log only if test failed.. + if res['log']: + #chop all '\n' from log text (to prevent html errors) + rx1 = re.compile('(\s+)') + log_text = rx1.sub(' ', res['log']) + + # allow only a-zA-Z0-9_ in html title name + # (due to bug in MS-explorer) + rx2 = re.compile('([^a-zA-Z_0-9])') + updated_tag = rx2.sub('_', res['title']) + + html_body_text = '%s%s' % (str(updated_tag), log_text) + print >> output, '' % (str(updated_tag), str(html_body_text)) + else: + print >> output, '' + # print execution time + print >> output, '' % os.path.join(dirname, res['title'], "debug") + + print >> output, '' + print >> output, "
Date/TimeTest Case
StatusTime (sec)InfoDebug
%s%sPASSFAILERROR!%s%sInfoDebug
" + + + print >> output, '

Host Info

' + print >> output, '

click on each item to expend/collapse

' + ## Meta list comes here.. + print >> output, '

' + print >> output, 'Expand All' + print >> output, '   ' + print >> output, 'Collapse All' + print >> output, '

' + + print >> output, '
    ' + counter = 0 + keys = metadata.keys() + keys.sort() + for key in keys: + val = metadata[key] + print >> output, '
  • %s' % key + print >> output, '
      %s
  • ' % val + print >> output, '
' + + print >> output, "" + if output_file_name: + output.close() + + +def parse_result(dirname, line): + parts = line.split() + if len(parts) < 4: + return None + global stimelist + if parts[0] == 'START': + pair = parts[3].split('=') + stime = int(pair[1]) + stimelist.append(stime) + + elif (parts[0] == 'END'): + result = {} + exec_time = '' + # fetch time stamp + if len(parts) > 7: + temp = parts[5].split('=') + exec_time = temp[1] + ' ' + parts[6] + ' ' + parts[7] + # assign default values + result['time'] = exec_time + result['testcase'] = 'na' + result['status'] = 'na' + result['log'] = None + result['exec_time_sec'] = 'na' + tag = parts[3] + + # assign actual values + rx = re.compile('^(\w+)\.(.*)$') + m1 = rx.findall(parts[3]) + result['testcase'] = m1[0][1] + result['title'] = str(tag) + result['status'] = parts[1] + if result['status'] != 'GOOD': + result['log'] = get_exec_log(dirname, tag) + if len(stimelist)>0: + pair = parts[4].split('=') + etime = int(pair[1]) + stime = stimelist.pop() + total_exec_time_sec = etime - stime + result['exec_time_sec'] = total_exec_time_sec + return result + return None + + +def get_exec_log(resdir, tag): + stdout_file = os.path.join(resdir, tag) + '/debug/stdout' + stderr_file = os.path.join(resdir, tag) + '/debug/stderr' + status_file = os.path.join(resdir, tag) + '/status' + dmesg_file = os.path.join(resdir, tag) + '/sysinfo/dmesg' + log = '' + log += '
STDERR:
' + log += get_info_file(stderr_file) + log += '
STDOUT:
' + log += get_info_file(stdout_file) + log += '
STATUS:
' + log += get_info_file(status_file) + log += '
DMESG:
' + log += get_info_file(dmesg_file) + return log + + +def get_info_file(filename): + data = '' + errors = re.compile(r"\b(error|fail|failed)\b", re.IGNORECASE) + if os.path.isfile(filename): + f = open('%s' % filename, "r") + lines = f.readlines() + f.close() + rx = re.compile('(\'|\")') + for line in lines: + new_line = rx.sub('', line) + errors_found = errors.findall(new_line) + if len(errors_found) > 0: + data += '%s
' % str(new_line) + else: + data += '%s
' % str(new_line) + if not data: + data = 'No Information Found.
' + else: + data = 'File not found.
' + return data + + + +def usage(): + print 'usage:', + print 'make_html_report.py -r [-f output_file] [-R]' + print '(e.g. make_html_reporter.py -r '\ + '/usr/local/autotest/client/results/default -f /tmp/myreport.html)' + print 'add "-R" for an html report with relative-paths (relative '\ + 'to results directory)' + print '' + sys.exit(1) + + +def get_keyval_value(result_dir, key): + """ + Return the value of the first appearance of key in any keyval file in + result_dir. If no appropriate line is found, return 'Unknown'. + """ + keyval_pattern = os.path.join(result_dir, "kvm.*", "keyval") + keyval_lines = commands.getoutput(r"grep -h '\b%s\b.*=' %s" + % (key, keyval_pattern)) + if not keyval_lines: + return "Unknown" + keyval_line = keyval_lines.splitlines()[0] + if key in keyval_line and "=" in keyval_line: + return keyval_line.split("=")[1].strip() + else: + return "Unknown" + + +def get_kvm_version(result_dir): + """ + Return an HTML string describing the KVM version. + + @param result_dir: An Autotest job result dir + """ + kvm_version = get_keyval_value(result_dir, "kvm_version") + kvm_userspace_version = get_keyval_value(result_dir, + "kvm_userspace_version") + return "Kernel: %s
Userspace: %s" % (kvm_version, kvm_userspace_version) + + +def main(argv): + dirname = None + output_file_name = None + relative_path = False + try: + opts, args = getopt.getopt(argv, "r:f:h:R", ['help']) + except getopt.GetoptError: + usage() + sys.exit(2) + for opt, arg in opts: + if opt in ("-h", "--help"): + usage() + sys.exit() + elif opt == '-r': + dirname = arg + elif opt == '-f': + output_file_name = arg + elif opt == '-R': + relative_path = True + else: + usage() + sys.exit(1) + + html_path = dirname + # don't use absolute path in html output if relative flag passed + if relative_path: + html_path = '' + + if dirname: + if os.path.isdir(dirname): # TBD: replace it with a validation of + # autotest result dir + res_dir = os.path.abspath(dirname) + tag = res_dir + status_file_name = dirname + '/status' + sysinfo_dir = dirname + '/sysinfo' + host = get_info_file('%s/hostname' % sysinfo_dir) + rx = re.compile('^\s+[END|START].*$') + # create the results set dict + results_data = [] + if os.path.exists(status_file_name): + f = open(status_file_name, "r") + lines = f.readlines() + f.close() + for line in lines: + if rx.match(line): + result_dict = parse_result(dirname, line) + if result_dict: + results_data.append(result_dict) + # create the meta info dict + metalist = { + 'uname': get_info_file('%s/uname' % sysinfo_dir), + 'cpuinfo':get_info_file('%s/cpuinfo' % sysinfo_dir), + 'meminfo':get_info_file('%s/meminfo' % sysinfo_dir), + 'df':get_info_file('%s/df' % sysinfo_dir), + 'modules':get_info_file('%s/modules' % sysinfo_dir), + 'gcc':get_info_file('%s/gcc_--version' % sysinfo_dir), + 'dmidecode':get_info_file('%s/dmidecode' % sysinfo_dir), + 'dmesg':get_info_file('%s/dmesg' % sysinfo_dir), + 'kvmver':get_kvm_version(dirname) + } + + make_html_file(metalist, results_data, tag, host, output_file_name, + html_path) + sys.exit(0) + else: + print 'Invalid result directory <%s>' % dirname + sys.exit(1) + else: + usage() + sys.exit(1) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/client/tools/scan_results.py b/client/tools/scan_results.py new file mode 100755 index 0000000..562a05b --- /dev/null +++ b/client/tools/scan_results.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +""" +Program that parses the autotest results and return a nicely printed final test +result. + +@copyright: Red Hat 2008-2009 +""" + +def parse_results(text): + """ + Parse text containing Autotest results. + + @return: A list of result 4-tuples. + """ + result_list = [] + start_time_list = [] + info_list = [] + + lines = text.splitlines() + for line in lines: + line = line.strip() + parts = line.split("\t") + + # Found a START line -- get start time + if (line.startswith("START") and len(parts) >= 5 and + parts[3].startswith("timestamp")): + start_time = float(parts[3].split("=")[1]) + start_time_list.append(start_time) + info_list.append("") + + # Found an END line -- get end time, name and status + elif (line.startswith("END") and len(parts) >= 5 and + parts[3].startswith("timestamp")): + end_time = float(parts[3].split("=")[1]) + start_time = start_time_list.pop() + info = info_list.pop() + test_name = parts[2] + test_status = parts[0].split()[1] + # Remove "kvm." prefix + if test_name.startswith("kvm."): + test_name = test_name[4:] + result_list.append((test_name, test_status, + int(end_time - start_time), info)) + + # Found a FAIL/ERROR/GOOD line -- get failure/success info + elif (len(parts) >= 6 and parts[3].startswith("timestamp") and + parts[4].startswith("localtime")): + info_list[-1] = parts[5] + + return result_list + + +def print_result(result, name_width): + """ + Nicely print a single Autotest result. + + @param result: a 4-tuple + @param name_width: test name maximum width + """ + if result: + format = "%%-%ds %%-10s %%-8s %%s" % name_width + print format % result + + +def main(resfiles): + result_lists = [] + name_width = 40 + + for resfile in resfiles: + try: + text = open(resfile).read() + except IOError: + print "Bad result file: %s" % resfile + continue + results = parse_results(text) + result_lists.append((resfile, results)) + name_width = max([name_width] + [len(r[0]) for r in results]) + + print_result(("Test", "Status", "Seconds", "Info"), name_width) + print_result(("----", "------", "-------", "----"), name_width) + + for resfile, results in result_lists: + print " (Result file: %s)" % resfile + for r in results: + print_result(r, name_width) + + +if __name__ == "__main__": + import sys, glob + + resfiles = glob.glob("../results/default/status*") + if len(sys.argv) > 1: + if sys.argv[1] == "-h" or sys.argv[1] == "--help": + print "Usage: %s [result files]" % sys.argv[0] + sys.exit(0) + resfiles = sys.argv[1:] + main(resfiles)