Make changes on the scheduler notification code to allow
for shorter code. Also, introduce a new common_lib.utils
function that generates a pretty print representation of
any matrix, that will adjust the length of all columns
automagically, making the resulting e-mail report much
prettier to see, even with very large test names. Example:

header = ("Word1", "Word2", "Word3")
rows = [["Heeeeeeeeeeeeeey", "Hey", "Eh"],
        ["Orange", "Nonononono", "Pineapple"],
        ["Egg", "Spam", "Bacon"]]

result = matrix_to_string(rows, header)

Gives:

Word1            Word2      Word3
Heeeeeeeeeeeeeey Hey        Eh
Orange           Nonononono Pineapple
Egg              Spam       Bacon

Signed-off-by: Lucas Meneghel Rodrigues <[email protected]>
---
 client/common_lib/utils.py    |   38 ++++++++++++++++++++++++++++
 scheduler/scheduler_models.py |   55 ++++++++++++++++++++++------------------
 2 files changed, 68 insertions(+), 25 deletions(-)

diff --git a/client/common_lib/utils.py b/client/common_lib/utils.py
index a117cec..f44dd1e 100644
--- a/client/common_lib/utils.py
+++ b/client/common_lib/utils.py
@@ -202,6 +202,44 @@ def open_write_close(filename, data):
         f.close()
 
 
+def matrix_to_string(matrix, header=None):
+    """
+    Return a pretty, aligned string representation of a nxm matrix.
+
+    This representation can be used to print any tabular data, such as
+    database results. It works by scanning the lengths of each element
+    in each column, and determining the format string dynamically.
+
+    @param matrix: Matrix representation (list with n rows of m elements).
+    @param header: Optional tuple with header elements to be displayed.
+    """
+    lengths = []
+    for row in matrix:
+        for column in row:
+            i = row.index(column)
+            cl = len(column)
+            try:
+                ml = lengths[i]
+                if cl > ml:
+                    lengths[i] = cl
+            except IndexError:
+                lengths.append(cl)
+
+    lengths = tuple(lengths)
+    format_string = ""
+    for length in lengths:
+        format_string += "%-" + str(length) + "s "
+    format_string += "\n"
+
+    matrix_str = ""
+    if header:
+        matrix_str += format_string % header
+    for row in matrix:
+        matrix_str += format_string % tuple(row)
+
+    return matrix_str
+
+
 def read_keyval(path):
     """
     Read a key-value pair format file into a dictionary, and return it.
diff --git a/scheduler/scheduler_models.py b/scheduler/scheduler_models.py
index 618add6..0a6ae7b 100644
--- a/scheduler/scheduler_models.py
+++ b/scheduler/scheduler_models.py
@@ -19,6 +19,7 @@ _drone_manager: reference to global DroneManager instance.
 import datetime, itertools, logging, os, re, sys, time, weakref
 from django.db import connection
 from autotest_lib.client.common_lib import global_config, host_protections
+from autotest_lib.client.common_lib import global_config, utils
 from autotest_lib.frontend.afe import models, model_attributes
 from autotest_lib.database import database_connection
 from autotest_lib.scheduler import drone_manager, email_manager
@@ -608,7 +609,7 @@ class HostQueueEntry(DBObject):
         job_stats = Job(id=self.job.id).get_execution_details()
 
         subject = ('Autotest | Job ID: %s "%s" | Status: %s ' %
-                    (self.job.id, self.job.name, status))
+                   (self.job.id, self.job.name, status))
 
         if hostname is not None:
             subject += '| Hostname: %s ' % hostname
@@ -631,6 +632,7 @@ class HostQueueEntry(DBObject):
             body += "User tests failed: %s\n" % job_stats['total_failed']
             body += ("User tests success rate: %.2f %%\n" %
                      job_stats['success_rate'])
+
         if job_stats['failed_rows']:
             body += "Failures:\n"
             body += job_stats['failed_rows']
@@ -865,6 +867,23 @@ class Job(DBObject):
 
         @return: Dictionary with test execution details
         """
+        def _find_test_jobs(rows):
+            """
+            Here we are looking for tests such as SERVER_JOB and CLIENT_JOB.*
+            Those are autotest 'internal job' tests, so they should not be
+            counted when evaluating the test stats.
+
+            @param rows: List of rows (matrix) with database results.
+            """
+            job_test_pattern = re.compile('SERVER|CLIENT\\_JOB\.[\d]')
+            n_test_jobs = 0
+            for r in rows:
+                test_name = r[0]
+                if job_test_pattern.match(test_name):
+                    n_test_jobs += 1
+
+            return n_test_jobs
+
         stats = {}
 
         rows = _db.execute("""
@@ -877,23 +896,8 @@ class Job(DBObject):
 
         failed_rows = [r for r in rows if not 'GOOD' in r]
 
-
-        # Here we are looking for tests such as SERVER_JOB and CLIENT_JOB.*
-        # Those are autotest 'internal job' tests, so they should not be
-        # counted when evaluating the test stats
-        job_test_pattern = re.compile('SERVER|CLIENT\\_JOB\.[\d]')
-        n_test_jobs = 0
-        for r in rows:
-            test_name = r[0]
-            if job_test_pattern.match(test_name):
-                n_test_jobs += 1
-
-        # Same for the failed jobs
-        n_test_jobs_failed = 0
-        for f in failed_rows:
-            test_name_failed = f[0]
-            if job_test_pattern.match(test_name_failed):
-                n_test_jobs_failed += 1
+        n_test_jobs = _find_test_jobs(rows)
+        n_test_jobs_failed = _find_test_jobs(failed_rows)
 
         total_executed = len(rows) - n_test_jobs
         total_failed = len(failed_rows) - n_test_jobs_failed
@@ -908,11 +912,12 @@ class Job(DBObject):
         stats['total_passed'] = total_executed - total_failed
         stats['success_rate'] = success_rate
 
-        failed_str = '%-30s %-10s %-40s\n' % ("Test Name", "Status", "Reason")
-        for row in failed_rows:
-            failed_str += '%-30s %-10s %-40s\n' % row
-
-        stats['failed_rows'] = failed_str
+        status_header = ("Test Name", "Status", "Reason")
+        if failed_rows:
+            stats['failed_rows'] = utils.matrix_to_string(failed_rows,
+                                                          status_header)
+        else:
+            stats['failed_rows'] = ''
 
         time_row = _db.execute("""
                    SELECT started_time, finished_time
@@ -925,8 +930,8 @@ class Job(DBObject):
             delta = t_end - t_begin
             minutes, seconds = divmod(delta.seconds, 60)
             hours, minutes = divmod(minutes, 60)
-            stats['execution_time'] = "%02d:%02d:%02d" % (
-                    hours, minutes, seconds)
+            stats['execution_time'] = ("%02d:%02d:%02d" %
+                                       (hours, minutes, seconds))
         else:
             stats['execution_time'] = '(none)'
 
-- 
1.7.2.3

_______________________________________________
Autotest mailing list
[email protected]
http://test.kernel.org/cgi-bin/mailman/listinfo/autotest

Reply via email to