Upstream Autotest merge.

As titled, a merge up to 93fc426ca133e775eb495f34d138fc57d92fb55e.

- Removes a bunch of deprecated code.
- Moves several private utilities into the private repo.
- Couple changes ported upstream and resynced.

BUG=None
TEST=In progress... will spin up new Autotest server and use
run_remote_tests for bvt, regression, smoke.

Change-Id: Id3e2ad529bb7b05f148e5d98aea46bb9ea828200
Reviewed-on: http://gerrit.chromium.org/gerrit/3350
Tested-by: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
diff --git a/client/tools/html_report.py b/client/tools/html_report.py
old mode 100644
new mode 100755
index d220ade..563a7a9
--- a/client/tools/html_report.py
+++ b/client/tools/html_report.py
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 """
-Script used to parse the test results and generate an HTML report.
+Module used to parse the autotest job results and generate an HTML report.
 
 @copyright: (c)2005-2007 Matt Kruse (javascripttoolbox.com)
 @copyright: Red Hat 2008-2009
@@ -27,7 +27,6 @@
     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;
@@ -37,7 +36,6 @@
         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;
@@ -1375,20 +1373,26 @@
 """
 
 
-#################################################################
-##  This script gets kvm autotest results directory path as an ##
-##  input and create a single html formatted result page.      ##
-#################################################################
-
-stimelist = []
 
 
 def make_html_file(metadata, results, tag, host, output_file_name, dirname):
+    """
+    Create HTML file contents for the job report, to stdout or filesystem.
+
+    @param metadata: Dictionary with Job metadata (tests, exec time, etc).
+    @param results: List with testcase results.
+    @param tag: Job tag.
+    @param host: Client hostname.
+    @param output_file_name: Output file name. If empty string, prints to
+            stdout.
+    @param dirname: Prefix for HTML links. If empty string, the HTML links
+            will be relative to the results dir.
+    """
     html_prefix = """
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
 <html>
 <head>
-<title>KVM Autotest Results</title>
+<title>Autotest job execution results</title>
 <style type="text/css">
 %s
 </style>
@@ -1407,14 +1411,13 @@
 <body>
 """ % (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, '<h2 id=\"page_title\">KVM Autotest Execution Report</h2>'
+    print >> output, '<h2 id=\"page_title\">Autotest job execution report</h2>'
 
     # formating date and time to print
     t = datetime.datetime.now()
@@ -1427,18 +1430,19 @@
     total_failed = 0
     total_passed = 0
     for res in results:
-        total_executed += 1
-        if res['status'] == 'GOOD':
-            total_passed += 1
-        else:
-            total_failed += 1
+        if results[res][2] != None:
+            total_executed += 1
+            if results[res][2]['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']
+    kvm_ver_str = metadata.get('kvmver', None)
 
     print >> output, '<table class="stats2">'
     print >> output, '<tr><td>HOST</td><td>:</td><td>%s</td></tr>' % host
@@ -1446,10 +1450,10 @@
     print >> output, '<tr><td>DATE</td><td>:</td><td>%s</td></tr>' % now.ctime()
     print >> output, '<tr><td>STATS</td><td>:</td><td>%s</td></tr>'% stat_str
     print >> output, '<tr><td></td><td></td><td></td></tr>'
-    print >> output, '<tr><td>KVM VERSION</td><td>:</td><td>%s</td></tr>' % kvm_ver_str
+    if kvm_ver_str is not None:
+        print >> output, '<tr><td>KVM VERSION</td><td>:</td><td>%s</td></tr>' % kvm_ver_str
     print >> output, '</table>'
 
-
     ## print test results
     print >> output, '<br>'
     print >> output, '<h2 id=\"page_sub_title\">Test Results</h2>'
@@ -1468,39 +1472,46 @@
 <tbody>
 """
     print >> output, result_table_prefix
-    for res in results:
-        print >> output, '<tr>'
-        print >> output, '<td align="left">%s</td>' % res['time']
-        print >> output, '<td align="left">%s</td>' % res['testcase']
-        if res['status'] == 'GOOD':
-            print >> output, '<td align=\"left\"><b><font color="#00CC00">PASS</font></b></td>'
-        elif res['status'] == 'FAIL':
-            print >> output, '<td align=\"left\"><b><font color="red">FAIL</font></b></td>'
-        elif res['status'] == 'ERROR':
-            print >> output, '<td align=\"left\"><b><font color="red">ERROR!</font></b></td>'
-        else:
-            print >> output, '<td align=\"left\">%s</td>' % res['status']
-        # print exec time (seconds)
-        print >> output, '<td align="left">%s</td>' % 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'])
+    def print_result(result, indent):
+        while result != []:
+            r = result.pop(0)
+            print r
+            res = results[r][2]
+            print >> output, '<tr>'
+            print >> output, '<td align="left">%s</td>' % res['time']
+            print >> output, '<td align="left" style="padding-left:%dpx">%s</td>' % (indent * 20, res['title'])
+            if res['status'] == 'GOOD':
+                print >> output, '<td align=\"left\"><b><font color="#00CC00">PASS</font></b></td>'
+            elif res['status'] == 'FAIL':
+                print >> output, '<td align=\"left\"><b><font color="red">FAIL</font></b></td>'
+            elif res['status'] == 'ERROR':
+                print >> output, '<td align=\"left\"><b><font color="red">ERROR!</font></b></td>'
+            else:
+                print >> output, '<td align=\"left\">%s</td>' % res['status']
+            # print exec time (seconds)
+            print >> output, '<td align="left">%s</td>' % 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'])
+                # 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 = '<html><head><title>%s</title></head><body>%s</body></html>' % (str(updated_tag), log_text)
-            print >> output, '<td align=\"left\"><A HREF=\"#\" onClick=\"popup(\'%s\',\'%s\')\">Info</A></td>' % (str(updated_tag), str(html_body_text))
-        else:
-            print >> output, '<td align=\"left\"></td>'
-        # print execution time
-        print >> output, '<td align="left"><A HREF=\"%s\">Debug</A></td>' % os.path.join(dirname, res['title'], "debug")
+                html_body_text = '<html><head><title>%s</title></head><body>%s</body></html>' % (str(updated_tag), log_text)
+                print >> output, '<td align=\"left\"><A HREF=\"#\" onClick=\"popup(\'%s\',\'%s\')\">Info</A></td>' % (str(updated_tag), str(html_body_text))
+            else:
+                print >> output, '<td align=\"left\"></td>'
+            # print execution time
+            print >> output, '<td align="left"><A HREF=\"%s\">Debug</A></td>' % os.path.join(dirname, res['subdir'], "debug")
 
-        print >> output, '</tr>'
+            print >> output, '</tr>'
+            print_result(results[r][1], indent + 1)
+
+    print_result(results[""][1], 0)
     print >> output, "</tbody></table>"
 
 
@@ -1528,15 +1539,27 @@
         output.close()
 
 
-def parse_result(dirname, line):
+def parse_result(dirname, line, results_data):
+    """
+    Parse job status log line.
+
+    @param dirname: Job results dir
+    @param line: Status log line.
+    @param results_data: Dictionary with for results.
+    """
     parts = line.split()
     if len(parts) < 4:
         return None
-    global stimelist
+    global tests
     if parts[0] == 'START':
         pair = parts[3].split('=')
         stime = int(pair[1])
-        stimelist.append(stime)
+        results_data[parts[1]] = [stime, [], None]
+        try:
+            parent_test = re.findall(r".*/", parts[1])[0][:-1]
+            results_data[parent_test][1].append(parts[1])
+        except IndexError:
+            results_data[""][1].append(parts[1])
 
     elif (parts[0] == 'END'):
         result = {}
@@ -1553,32 +1576,39 @@
         result['exec_time_sec'] = 'na'
         tag = parts[3]
 
+        result['subdir'] = parts[2]
         # assign actual values
         rx = re.compile('^(\w+)\.(.*)$')
         m1 = rx.findall(parts[3])
-        result['testcase'] = m1[0][1]
+        if len(m1):
+            result['testcase'] = m1[0][1]
+        else:
+            result['testcase'] = parts[3]
         result['title'] = str(tag)
         result['status'] = parts[1]
         if result['status'] != 'GOOD':
             result['log'] = get_exec_log(dirname, tag)
-        if len(stimelist)>0:
+        if len(results_data)>0:
             pair = parts[4].split('=')
-            try:
-                etime = int(pair[1])
-                stime = stimelist.pop()
-                total_exec_time_sec = etime - stime
-                result['exec_time_sec'] = total_exec_time_sec
-            except ValueError:
-                result['exec_time_sec'] = "Unknown"
-        return result
+            etime = int(pair[1])
+            stime = results_data[parts[2]][0]
+            total_exec_time_sec = etime - stime
+            result['exec_time_sec'] = total_exec_time_sec
+        results_data[parts[2]][2] = 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'
+    """
+    Get job execution summary.
+
+    @param resdir: Job results dir.
+    @param tag: Job 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 += '<br><b>STDERR:</b><br>'
     log += get_info_file(stderr_file)
@@ -1592,6 +1622,13 @@
 
 
 def get_info_file(filename):
+    """
+    Gets the contents of an autotest info file.
+
+    It also and highlights the file contents with possible problems.
+
+    @param filename: Info file path.
+    """
     data = ''
     errors = re.compile(r"\b(error|fail|failed)\b", re.IGNORECASE)
     if os.path.isfile(filename):
@@ -1613,8 +1650,10 @@
     return data
 
 
-
 def usage():
+    """
+    Print stand alone program usage.
+    """
     print 'usage:',
     print 'make_html_report.py -r <result_directory> [-f output_file] [-R]'
     print '(e.g. make_html_reporter.py -r '\
@@ -1629,6 +1668,9 @@
     """
     Return the value of the first appearance of key in any keyval file in
     result_dir. If no appropriate line is found, return 'Unknown'.
+
+    @param result_dir: Path that holds the keyval files.
+    @param key: Specific key we're retrieving.
     """
     keyval_pattern = os.path.join(result_dir, "kvm.*", "keyval")
     keyval_lines = commands.getoutput(r"grep -h '\b%s\b.*=' %s"
@@ -1646,15 +1688,68 @@
     """
     Return an HTML string describing the KVM version.
 
-        @param result_dir: An Autotest job result dir
+    @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")
+    if kvm_version == "Unknown" or kvm_userspace_version == "Unknown":
+        return None
     return "Kernel: %s<br>Userspace: %s" % (kvm_version, kvm_userspace_version)
 
 
+def create_report(dirname, html_path='', output_file_name=None):
+    """
+    Create an HTML report with info about an autotest client job.
+
+    If no relative path (html_path) or output file name provided, an HTML
+    file in the toplevel job results dir called 'job_report.html' will be
+    created, with relative links.
+
+    @param html_path: Prefix for the HTML links. Useful to specify absolute
+            in the report (not wanted most of the time).
+    @param output_file_name: Path to the report file.
+    """
+    res_dir = os.path.abspath(dirname)
+    tag = res_dir
+    status_file_name = os.path.join(dirname, 'status')
+    sysinfo_dir = os.path.join(dirname, 'sysinfo')
+    host = get_info_file(os.path.join(sysinfo_dir, 'hostname'))
+    rx = re.compile('^\s+[END|START].*$')
+    # create the results set dict
+    results_data = {}
+    results_data[""] = [0, [], None]
+    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):
+                parse_result(dirname, line, results_data)
+    # create the meta info dict
+    metalist = {
+                'uname': get_info_file(os.path.join(sysinfo_dir, 'uname')),
+                'cpuinfo':get_info_file(os.path.join(sysinfo_dir, 'cpuinfo')),
+                'meminfo':get_info_file(os.path.join(sysinfo_dir, 'meminfo')),
+                'df':get_info_file(os.path.join(sysinfo_dir, 'df')),
+                'modules':get_info_file(os.path.join(sysinfo_dir, 'modules')),
+                'gcc':get_info_file(os.path.join(sysinfo_dir, 'gcc_--version')),
+                'dmidecode':get_info_file(os.path.join(sysinfo_dir, 'dmidecode')),
+                'dmesg':get_info_file(os.path.join(sysinfo_dir, 'dmesg')),
+    }
+    if get_kvm_version(dirname) is not None:
+        metalist['kvm_ver'] = get_kvm_version(dirname)
+
+    if output_file_name is None:
+        output_file_name = os.path.join(dirname, 'job_report.html')
+    make_html_file(metalist, results_data, tag, host, output_file_name,
+                   html_path)
+
+
 def main(argv):
+    """
+    Parses the arguments and executes the stand alone program.
+    """
     dirname = None
     output_file_name = None
     relative_path = False
@@ -1685,38 +1780,7 @@
     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)
+            create_report(dirname, html_path, output_file_name)
             sys.exit(0)
         else:
             print 'Invalid result directory <%s>' % dirname