[PATCH 1/6] New TKO: Changes to html and css code needed for new results comparison

Subsystems: the rest

STALE6314d

6 messages, 1 author, 2009-06-29 · open the first message on its own page

[PATCH 1/6] New TKO: Changes to html and css code needed for new results comparison

From: Lucas Meneghel Rodrigues <hidden>
Date: 2009-06-29 04:03:01

These are the HTML and css changes needed for the new results
comparison feature.

Signed-off-by: Dror Russo <redacted>
---
 frontend/client/src/autotest/public/TkoClient.html |    6 ++++++
 frontend/client/src/autotest/public/tkoclient.css  |    9 ++++++++-
 2 files changed, 14 insertions(+), 1 deletions(-)
diff --git a/frontend/client/src/autotest/public/TkoClient.html b/frontend/client/src/autotest/public/TkoClient.html
index ce8a643..745c544 100644
--- a/frontend/client/src/autotest/public/TkoClient.html
+++ b/frontend/client/src/autotest/public/TkoClient.html
@@ -84,6 +84,11 @@
       <div id="test_detail_view" title="Test details">
         <span id="td_fetch" class="box-full">Fetch test by ID:
           <span id="td_fetch_controls"></span>
+        </span><br>
+        <span id="td_comp_attr" class="box-full">
+          Compare this execution using attributes <span id="td_comp_attr_control"></span>
+          with previous executions of <span id="td_comp_name_control"></span>
+          <span id="td_comp_save_control"></span>
         </span><br><br>
         <div id="td_title" class="title"></div><br>
         <div id="td_data">
@@ -110,6 +115,7 @@
           <span class="field-name">Test labels:</span>
           <span id="td_test_labels"></span><br>
           <span id="td_attributes"></span><br>
+          <span id="td_analysis"></span><br>
           <br>
 
           <span class="field-name">Key log files:</span>
diff --git a/frontend/client/src/autotest/public/tkoclient.css b/frontend/client/src/autotest/public/tkoclient.css
index 9726d8c..2a86ace 100644
--- a/frontend/client/src/autotest/public/tkoclient.css
+++ b/frontend/client/src/autotest/public/tkoclient.css
@@ -60,4 +60,11 @@ div.spreadsheet-cell-nonclickable {
 
 .test-attributes .header table {
   font-weight: bold;
-}
\ No newline at end of file
+}
+.test-analysis .content {
+  white-space: pre;
+  font-family: monospace;
+}
+.test-analysis .header table {
+  font-weight: bold;
+}
-- 
1.6.2.5

[PATCH 2/6] New TKO: Implement the Result Analysis UI class

From: Lucas Meneghel Rodrigues <hidden>
Date: 2009-06-29 04:03:03

This patch implements the result analysis UI class on the new tko
code, as well as register it on the result analysis comparison
window.

Signed-off-by: Dror Russo <redacted>
---
 .../client/src/autotest/tko/TestDetailView.java    |  194 +++++++++++++++++---
 1 files changed, 168 insertions(+), 26 deletions(-)
diff --git a/frontend/client/src/autotest/tko/TestDetailView.java b/frontend/client/src/autotest/tko/TestDetailView.java
index 30337e8..9f32a0a 100644
--- a/frontend/client/src/autotest/tko/TestDetailView.java
+++ b/frontend/client/src/autotest/tko/TestDetailView.java
@@ -7,11 +7,13 @@ import autotest.common.Utils;
 import autotest.common.ui.DetailView;
 import autotest.common.ui.NotifyManager;
 import autotest.common.ui.RealHyperlink;
+import autotest.common.StaticDataRepository;
 
 import com.google.gwt.json.client.JSONNumber;
 import com.google.gwt.json.client.JSONObject;
 import com.google.gwt.json.client.JSONString;
 import com.google.gwt.json.client.JSONValue;
+import com.google.gwt.json.client.JSONArray;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.WindowResizeListener;
 import com.google.gwt.user.client.ui.Composite;
@@ -24,6 +26,11 @@ import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.Panel;
 import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Widget;
+
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -33,17 +40,22 @@ import java.util.List;
 class TestDetailView extends DetailView {
     private static final int NO_TEST_ID = -1;
 
-    private static final JsonRpcProxy logLoadingProxy = 
+    private static final JsonRpcProxy logLoadingProxy =
         new PaddedJsonRpcProxy(Utils.RETRIEVE_LOGS_URL);
 
+    private static JsonRpcProxy rpcProxy = JsonRpcProxy.getProxy();
     private int testId = NO_TEST_ID;
+    private String testName = null;
     private String jobTag;
     private List<LogFileViewer> logFileViewers = new ArrayList<LogFileViewer>();
-    private RealHyperlink logLink = new RealHyperlink("(view all logs)");
 
+    private RealHyperlink logLink = new RealHyperlink("(view all logs)");
+    protected static TextBox attrInput = new TextBox();
+    protected static TextBox testSetInput = new TextBox();
+    protected Button attrUpdateButton = new Button("Save");
     private Panel logPanel;
-    
-    private class LogFileViewer extends Composite 
+
+    private class LogFileViewer extends Composite
                                 implements DisclosureHandler, WindowResizeListener {
         private DisclosurePanel panel;
         private ScrollPanel scroller; // ScrollPanel wrapping log contents
@@ -65,17 +77,17 @@ class TestDetailView extends DetailView {
                 handle(result);
             }
         };
-        
+
         public LogFileViewer(String logFilePath, String logFileName) {
             this.logFilePath = logFilePath;
             panel = new DisclosurePanel(logFileName);
             panel.addEventHandler(this);
             panel.addStyleName("log-file-panel");
             initWidget(panel);
-            
+
             Window.addWindowResizeListener(this);
         }
-        
+
         public void onOpen(DisclosureEvent event) {
             JSONObject params = new JSONObject();
             params.put("path", new JSONString(getLogUrl()));
@@ -87,7 +99,7 @@ class TestDetailView extends DetailView {
         private String getLogUrl() {
             return Utils.getLogsUrl(jobTag + "/" + logFilePath);
         }
-        
+
         public void handle(JSONValue value) {
             String logContents = value.isString().stringValue();
             if (logContents.equals("")) {
@@ -106,8 +118,8 @@ class TestDetailView extends DetailView {
         }
 
         /**
-         * Firefox fails to set relative widths correctly for elements with overflow: scroll (or 
-         * auto, or hidden).  Instead, it just expands the element to fit the contents.  So we use 
+         * Firefox fails to set relative widths correctly for elements with overflow: scroll (or
+         * auto, or hidden).  Instead, it just expands the element to fit the contents.  So we use
          * this trick to dynamically implement width: 100%.
          */
         private void setScrollerWidth() {
@@ -131,11 +143,119 @@ class TestDetailView extends DetailView {
 
         public void onClose(DisclosureEvent event) {}
     }
-    
+
+
+    /* This class handles the test comparison analysis.*/
+    private static class AnalysisTable extends Composite implements DisclosureHandler {
+        private ScrollPanel scroller;
+        private int testID;
+        private DisclosurePanel panel = new DisclosurePanel("Test case comparison");
+        private JsonRpcCallback rpcCallback = new JsonRpcCallback() {
+            @Override
+            public void onError(JSONObject errorObject) {
+                super.onError(errorObject);
+                String errorString = getErrorString(errorObject);
+                if (errorString.equals("")) {
+                    errorString = "No comparison analysis data available for this configuration.";
+                }
+                setStatusText(errorString);
+            }
+
+            @Override
+            public void onSuccess(JSONValue result) {
+                handle(result);
+            }
+
+        };
+
+        public AnalysisTable(int tid) {
+            this.testID = tid;
+            panel.addEventHandler(this);
+            panel.addStyleName("test-analysis");
+            initWidget(panel);
+        }
+
+        public void onOpen(DisclosureEvent event) {
+            JSONObject params = new JSONObject();
+            params.put("test", new JSONString(Integer.toString(testID)));
+            params.put("attr", new JSONString(attrInput.getText()));
+            params.put("testset", new JSONString(testSetInput.getText()));
+            rpcProxy.rpcCall("get_testcase_comparison_data", params, rpcCallback);
+            setStatusText("Loading (may take few seconds)...");
+        }
+
+        public void handle(JSONValue value) {
+            String contents = value.isString().stringValue();
+            if (contents.equals("")) {
+                setText("No analysis data for this test case.");
+            } else {
+                setText(contents);
+            }
+        }
+
+        private void setText(String text) {
+            panel.clear();
+            scroller = new ScrollPanel();
+            scroller.getElement().setInnerText(text);
+            panel.add(scroller);
+        }
+
+        private void setStatusText(String status) {
+            panel.clear();
+            panel.add(new HTML("<i>" + status + "</i>"));
+        }
+
+        public void onClose(DisclosureEvent event) {}
+
+    }
+
+
+    private void retrieveComparisonAttributes(String testName) {
+        attrInput.setText("");
+        testSetInput.setText("");
+        StaticDataRepository staticData = StaticDataRepository.getRepository();
+        JSONObject args = new JSONObject();
+        args.put("owner", new JSONString(staticData.getCurrentUserLogin()));
+        args.put("testname", new JSONString(testName));
+
+        rpcProxy.rpcCall("get_test_comparison_attr", args, new JsonRpcCallback() {
+            @Override
+            public void onSuccess(JSONValue result) {
+                JSONArray queries = result.isArray();
+                if (queries.size() == 0) {
+                    return;
+                }
+                assert queries.size() == 1;
+                JSONObject query = queries.get(0).isObject();
+                attrInput.setText(query.get("attributes").isString().stringValue());
+                testSetInput.setText(query.get("testset").isString().stringValue());
+                return;
+            }
+        });
+
+    }
+
+    public void saveComparisonAttribute() {
+        StaticDataRepository staticData = StaticDataRepository.getRepository();
+        JSONObject args = new JSONObject();
+        args.put("name", new JSONString(testName));
+        args.put("owner", new JSONString(staticData.getCurrentUserLogin()));
+        args.put("attr_token", new JSONString(attrInput.getText()));
+        args.put("testset_token", new JSONString(testSetInput.getText()));
+        rpcProxy.rpcCall("add_test_comparison_attr", args, new JsonRpcCallback() {
+           @Override
+           public void onSuccess(JSONValue result) {
+              NotifyManager.getInstance().showMessage("Test comparison attributes saved");
+           }
+        });
+   }
+
+
+
     private static class AttributeTable extends Composite {
         private DisclosurePanel container = new DisclosurePanel("Test attributes");
         private FlexTable table = new FlexTable();
-        
+
         public AttributeTable(JSONObject attributes) {
             processAttributes(attributes);
             setupTableStyle();
@@ -148,7 +268,7 @@ class TestDetailView extends DetailView {
                 table.setText(0, 0, "No test attributes");
                 return;
             }
-            
+
             List<String> sortedKeys = new ArrayList<String>(attributes.keySet());
             Collections.sort(sortedKeys);
             for (String key : sortedKeys) {
@@ -158,7 +278,7 @@ class TestDetailView extends DetailView {
                 table.setText(row, 1, value);
             }
         }
-        
+
         private void setupTableStyle() {
             container.addStyleName("test-attributes");
         }
@@ -167,12 +287,21 @@ class TestDetailView extends DetailView {
     @Override
     public void initialize() {
         super.initialize();
-        
+        RootPanel.get(getTdCompAttrControlId()).add(attrInput);
+        RootPanel.get(getTdCompSetNameControlId()).add(testSetInput);
+        RootPanel.get(getTdCompSaveControlId()).add(attrUpdateButton);
+
         logPanel = new FlowPanel();
         RootPanel.get("td_log_files").add(logPanel);
-        
+
         logLink.setOpensNewWindow(true);
         RootPanel.get("td_view_logs_link").add(logLink);
+
+        attrUpdateButton.addClickListener(new ClickListener() {
+            public void onClick(Widget sender) {
+                saveComparisonAttribute();
+            }
+        });
     }
 
     private void addLogViewers(String testName) {
@@ -206,10 +335,10 @@ class TestDetailView extends DetailView {
                     resetPage();
                     return;
                 }
-                
+                testName = new String(test.get("test_name").isString().stringValue());
                 showTest(test);
             }
-            
+
             @Override
             public void onError(JSONObject errorObject) {
                 super.onError(errorObject);
@@ -217,7 +346,7 @@ class TestDetailView extends DetailView {
             }
         });
     }
-    
+
     @Override
     protected void setObjectId(String id) {
         try {
@@ -227,7 +356,7 @@ class TestDetailView extends DetailView {
             throw new IllegalArgumentException();
         }
     }
-    
+
     @Override
     protected String getObjectId() {
         if (testId == NO_TEST_ID) {
@@ -260,7 +389,19 @@ class TestDetailView extends DetailView {
     public String getElementId() {
         return "test_detail_view";
     }
-    
+
+    protected String getTdCompAttrControlId() {
+        return "td_comp_attr_control";
+    }
+
+    protected String getTdCompSetNameControlId() {
+        return "td_comp_name_control";
+    }
+
+    protected String getTdCompSaveControlId() {
+        return "td_comp_save_control";
+    }
+
     @Override
     public void display() {
         super.display();
@@ -270,7 +411,8 @@ class TestDetailView extends DetailView {
     protected void showTest(JSONObject test) {
         String testName = test.get("test_name").isString().stringValue();
         jobTag = test.get("job_tag").isString().stringValue();
-        
+
+        retrieveComparisonAttributes(testName);
         showText(testName, "td_test");
         showText(jobTag, "td_job_tag");
         showField(test, "job_name", "td_job_name");
@@ -281,22 +423,22 @@ class TestDetailView extends DetailView {
         showField(test, "hostname", "td_hostname");
         showField(test, "platform", "td_platform");
         showField(test, "kernel", "td_kernel");
-        
+
         String[] labels = Utils.JSONtoStrings(test.get("labels").isArray());
         String labelList = Utils.joinStrings(", ", Arrays.asList(labels));
         if (labelList.equals("")) {
             labelList = "none";
         }
         showText(labelList, "td_test_labels");
-        
         JSONObject attributes = test.get("attributes").isObject();
         RootPanel attributePanel = RootPanel.get("td_attributes");
         attributePanel.clear();
         attributePanel.add(new AttributeTable(attributes));
-        
+        RootPanel analysisPanel = RootPanel.get("td_analysis");
+        analysisPanel.clear();
+        analysisPanel.add(new AnalysisTable(testId));
         logLink.setHref(Utils.getRetrieveLogsUrl(jobTag));
         addLogViewers(testName);
-        
         displayObjectData("Test " + testName + " (job " + jobTag + ")");
     }
     @Override
-- 
1.6.2.5

[PATCH 3/6] Introduce a maximum attribute mismatch parameter on global_config

From: Lucas Meneghel Rodrigues <hidden>
Date: 2009-06-29 04:03:04

Set a limit on the maximum number of attribute mismatches considered
by the test comparison code, and make it configurable.

Signed-off-by: Dror Russo <redacted>
---
 global_config.ini |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/global_config.ini b/global_config.ini
index 6fb8f4e..752a4ce 100644
--- a/global_config.ini
+++ b/global_config.ini
@@ -11,6 +11,7 @@ query_timeout: 3600
 min_retry_delay: 20
 max_retry_delay: 60
 graph_cache_creation_timeout_minutes: 10
+test_comparison_maximum_attribute_mismatches: 4
 
 [AUTOTEST_WEB]
 host: localhost
-- 
1.6.2.5

[PATCH 4/6] Implement test comparison table on tko/models.py

From: Lucas Meneghel Rodrigues <hidden>
Date: 2009-06-29 04:03:05

This patch creates the Test Comparison Attribute
table on the TKO database.

Signed-off-by: Dror Russo <redacted>
---
 new_tko/tko/models.py |   11 +++++++++++
 1 files changed, 11 insertions(+), 0 deletions(-)
diff --git a/new_tko/tko/models.py b/new_tko/tko/models.py
index dd02fa8..6412228 100644
--- a/new_tko/tko/models.py
+++ b/new_tko/tko/models.py
@@ -211,6 +211,17 @@ class IterationAttribute(dbmodels.Model, model_logic.ModelExtensions):
         db_table = 'iteration_attributes'
 
 
+class TestComparisonAttribute(dbmodels.Model, model_logic.ModelExtensions):
+    testname = dbmodels.CharField(maxlength=90)
+    # TODO: change this to foreign key once DBs are merged
+    owner = dbmodels.CharField(maxlength=80)
+    attributes = dbmodels.CharField(maxlength=1024)
+    testset = dbmodels.CharField(maxlength=1024)
+
+    class Meta:
+        db_table = 'test_comparison_attributes'
+
+
 class IterationResult(dbmodels.Model, model_logic.ModelExtensions):
     # see comment on IterationAttribute regarding primary_key=True
     test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
-- 
1.6.2.5

[PATCH 5/6] Migration script to add results comparison attributes table

From: Lucas Meneghel Rodrigues <hidden>
Date: 2009-06-29 04:03:07

A migration script was added to create the tests comparison
attributes table

Signed-off-by: Dror Russo <redacted>
---
 .../029_add_test_comparison_attributes.py          |   20 ++++++++++++++++++++
 1 files changed, 20 insertions(+), 0 deletions(-)
 create mode 100644 tko/migrations/029_add_test_comparison_attributes.py
diff --git a/tko/migrations/029_add_test_comparison_attributes.py b/tko/migrations/029_add_test_comparison_attributes.py
new file mode 100644
index 0000000..675ade1
--- /dev/null
+++ b/tko/migrations/029_add_test_comparison_attributes.py
@@ -0,0 +1,20 @@
+UP_SQL = """
+CREATE TABLE `test_comparison_attributes` (
+    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+    `testname` varchar(255) NOT NULL,
+    `owner` varchar(80) NOT NULL,
+    `attributes` varchar(1024) NOT NULL,
+    `testset` varchar(1024) NOT NULL
+);
+"""
+
+DOWN_SQL = """
+DROP TABLE IF EXISTS `test_comparison_attributes`;
+"""
+
+def migrate_up(manager):
+    manager.execute(UP_SQL)
+
+
+def migrate_down(manager):
+    manager.execute(DOWN_SQL)
-- 
1.6.2.5

[PATCH 6/6] Changes to the tko rpc interface

From: Lucas Meneghel Rodrigues <hidden>
Date: 2009-06-29 04:03:09

Implementing the rpc interface logic for results comparison.

Signed-off-by: Dror Russo <redacted>
---
 new_tko/tko/rpc_interface.py |  375 +++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 373 insertions(+), 2 deletions(-)
diff --git a/new_tko/tko/rpc_interface.py b/new_tko/tko/rpc_interface.py
index ac7e0b7..b8f56ca 100644
--- a/new_tko/tko/rpc_interface.py
+++ b/new_tko/tko/rpc_interface.py
@@ -1,10 +1,11 @@
-import os, pickle, datetime, itertools, operator
+import os, pickle, datetime, itertools, operator, urllib2, re
 from django.db import models as dbmodels
 from autotest_lib.frontend import thread_local
 from autotest_lib.frontend.afe import rpc_utils, model_logic
 from autotest_lib.frontend.afe import readonly_connection
-from autotest_lib.new_tko.tko import models, tko_rpc_utils, graphing_utils
+from autotest_lib.new_tko.tko import models, tko_rpc_utils, graphing_utils, db
 from autotest_lib.new_tko.tko import preconfigs
+from autotest_lib.client.common_lib import global_config
 
 # table/spreadsheet view support
 
@@ -377,6 +378,52 @@ def delete_saved_queries(id_list):
     query.delete()
 
 
+def get_test_comparison_attr(**filter_data):
+    """
+    Attributes list for test comparison is store at test comparison attributes
+    db table per user per test name. This function returns the database record
+    (if exists) that matches the user name and test name provided in
+    filter_data.
+
+    @param **filter_data: Dictionary of parameters that will be delegated to
+            TestComparisonAttribute.list_objects.
+    """
+    return rpc_utils.prepare_for_serialization(
+        models.TestComparisonAttribute.list_objects(filter_data))
+
+
+def add_test_comparison_attr(name, owner, attr_token=None, testset_token=""):
+    """
+    This function adds a new record to the test comparison attributes db table.
+
+    @param name: Test name.
+    @param owner: Test owner.
+    """
+    testname = name.strip()
+    if attr_token:
+        # if testset is not defined, the default is to save the test name (e.g.
+        # make the comparison on previous executions of the given testname)
+        if not testset_token or testset_token == "":
+            testset = testname
+        else:
+            testset = testset_token.strip()
+        existing_list = models.TestComparisonAttribute.objects.filter(
+                                                      owner=owner,testname=name)
+        if existing_list:
+            query_object = existing_list[0]
+            query_object.attributes= attr_token.strip()
+            query_object.testset = testset
+            query_object.save()
+            return query_object.id
+        return models.TestComparisonAttribute.add_object(owner=owner,
+                                                         testname=name,
+                                                         attributes=attr_token,
+                                                         testset=testset).id
+    else:
+        raise model_logic.ValidationError('No attributes defined for '
+                                          'comparison operation')
+
+
 # other
 def get_motd():
     return rpc_utils.get_motd()
@@ -445,3 +492,327 @@ def get_static_data():
     result['motd'] = rpc_utils.get_motd()
 
     return result
+
+
+def _get_test_lists(testID, attr_keys, testset):
+    """
+    A test execution is a list containing header (ID, Status, Reason and
+    Attributes) values. The function searches the results database (tko)
+    and returns three test lists:
+
+    @param testID: Test ID.
+    @param attr_keys: List with test attributes.
+    @param testset: List of tests.
+    @return: Tuple with the following elements:
+            * test headers list
+            * current test execution to compare with past executions.
+            * List of previous test executions of the same testcase
+            (test name is identical)
+            * All attributes that matched the reg exp of the attribute keys
+            provided
+    """
+    tko_db = db.db()
+    [(testname, status, reason)] = tko_db.select('test,status_word,reason',
+                                                 'test_view',
+                                                 {'test_idx': long(testID)},
+                                                 distinct=True)
+
+    # From all previous executions, filter those that name matches the regular
+    # expression provided by the user
+    previous_valid_test_executions = []
+    rows = tko_db.select('test_idx, test, status_word, reason', 'test_view', {},
+                         distinct=True)
+    p = re.compile('^%s$' % testset.strip())
+    for row in rows:
+        m = p.match(row[1])
+        if m:
+            previous_valid_test_executions.append(row)
+    if len(previous_valid_test_executions) == 0:
+        raise model_logic.ValidationError('No comparison data available for '
+                                          'this configuration.')
+
+    previous_test_executions = []
+    test_headers = ['ID','NAME','STATUS', 'REASON']
+    current_test_execution = [testID, str(testname), str(status), str(reason)]
+    current_test_execution_attr=[]
+    for (tc_attr) in db.select('attribute', 'test_attributes',
+                               {'test_idx': long(testID)}, distinct=True):
+        current_test_execution_attr.append(str(tc_attr[0]))
+
+    # Find all attribute for the comparison (including all matches if
+    # regexp specified)
+    attributes = []
+    for key in attr_keys:
+        valid_key = False
+        p = re.compile('^%s$' % key.strip())
+        for tc_attr in current_test_execution_attr:
+            m = p.match(tc_attr)
+            if m:
+                test_headers.append(tc_attr)
+                attributes.append(tc_attr)
+                valid_key=True
+        if not valid_key:
+            raise model_logic.ValidationError("Attribute '%s' does "
+                                              "not exist in this test "
+                                              "execution." % key)
+    for row in previous_valid_test_executions:
+        if row[0] <= long(testID): # look at historical data only
+                                   # (previous test executions)
+            previous_test_attributes = []
+            for (attr) in db.select('attribute', 'test_attributes',
+                                    {'test_idx': row[0]}, distinct=True):
+                previous_test_attributes.append(str(attr[0]))
+            # Check that previous test contains all required attributes
+            # for comparison
+            gap = [val for val in attributes if val not in
+                   previous_test_attributes]
+            if len(gap) == 0:
+                if row[0] != long(testID):
+                    test = [int(row[0]),str(row[1]), str(row[2]), str(row[3])]
+                for key in attributes:
+                    for (attr_key,attr_val) in db.select('attribute,value',
+                                                        'test_attributes',
+                                                        {'test_idx':row[0]},
+                                                        distinct=True):
+                        if str(attr_key)==key:
+                            if row[0] == long(testID):
+                                current_test_execution.append(str(attr_val))
+                            else:
+                                test.append(str(attr_val))
+                            break
+                if row[0] != long(testID):
+                    previous_test_executions.append(test)
+
+    if len(previous_test_executions) == 0:
+        raise model_logic.ValidationError('No comparison data available '
+                                          'for this configuration.')
+
+    return (attributes, test_headers, current_test_execution,
+            previous_test_executions)
+
+
+def _find_mismatches(headers, orig, new, valid_attr):
+    """
+    Finds the attributes mismatch between two tests provided: orig and new.
+
+    @param headers: Test headers.
+    @param orig: First test to be compared.
+    @param new: Second test to be compared.
+    @param valid_attr: Test attributes to be considered valid.
+    @returns: Tuple with the number of mismaching attributes found, a list
+            of the mismaching attribute names and a list of the mismaching
+            attribute values.
+    """
+    i = 0
+    mismatched_headers = []
+    mismatched_values = []
+
+    for index, item in enumerate(orig):
+        if item != new[index] and headers[index] in valid_attr:
+            i += 1
+            mismatched_headers.append(headers[index])
+            mismatched_values.append(new[index])
+
+    return (i,','.join(mismatched_headers),','.join(mismatched_values))
+
+
+def _prepare_test_analysis_dict(test_headers, previous_test_executions,
+                                current_test_execution, passed, attributes,
+                                max_mismatches_allowed=2):
+    """
+    Prepares and returns a dictionary of comparison analysis data.
+
+    @param test_headers: Dictionary with test headers.
+    @param previous_test_executions: List of test executions.
+    @param current_test_execution: Test execution being visualized on TKO.
+    @param passed: List of passed tests.
+    @param attributes: List of test attributes.
+    @param max_mismatches_allowed: Maximum amount of mismatches to be
+            considered for analysis.
+    """
+    dict = {}
+    counters = ['total', 'passed', 'pass_rate']
+    if len(previous_test_executions) > 0: # At least one test defined
+        for item in previous_test_executions:  
+            (mismatches, attr_headers,
+             attr_values) = _find_mismatches(test_headers,
+                                             current_test_execution, item,
+                                             attributes)
+            if mismatches <= max_mismatches_allowed:
+                # Create dictionary keys if does not exist and
+                # initialize all counters  
+                if mismatches not in dict.keys():
+                    dict[mismatches] = {'total': 0, 'passed': 0, 'pass_rate': 0}
+                if attr_headers not in dict[mismatches].keys():
+                    dict[mismatches][attr_headers] = {'total': 0, 'passed': 0,
+                                                      'pass_rate': 0}
+                if attr_values not in dict[mismatches][attr_headers].keys():
+                    dict[mismatches][attr_headers][attr_values] = {'total': 0,
+                                                                   'passed': 0,
+                                                                 'pass_rate': 0}
+                if (item[test_headers.index('STATUS')] not in
+                    dict[mismatches][attr_headers][attr_values].keys()):
+                    s = item[test_headers.index('STATUS')]
+                    dict[mismatches][attr_headers][attr_values][s] = []
+
+                # Update all counters
+                testcase = [item[test_headers.index('ID')],
+                            item[test_headers.index('NAME')],
+                            item[test_headers.index('REASON')]]
+
+                s = item[test_headers.index('STATUS')]
+                dict[mismatches][attr_headers][attr_values][s].append(testcase)
+                dict[mismatches]['total'] += 1
+                dict[mismatches][attr_headers]['total'] += 1
+                dict[mismatches][attr_headers][attr_values]['total'] += 1
+
+                if item[test_headers.index('STATUS')] in passed:
+                    dict[mismatches]['passed'] += 1
+                    dict[mismatches][attr_headers]['passed'] += 1
+                    dict[mismatches][attr_headers][attr_values]['passed'] += 1
+
+                p = float(dict[mismatches]['passed'])
+                t = float(dict[mismatches]['total'])
+                dict[mismatches]['pass_rate'] = int(p/t * 100)
+
+                p = float(dict[mismatches][attr_headers]['passed'])
+                t = float(dict[mismatches][attr_headers]['total'])
+                dict[mismatches][attr_headers]['pass_rate'] = int(p/t * 100)
+
+                p = float(dict[mismatches][attr_headers][attr_values]['passed'])
+                t = float(dict[mismatches][attr_headers][attr_values]['total'])
+                dict[mismatches][attr_headers][attr_values]['pass_rate'] = (
+                                                                int(p/t * 100))
+
+    return (dict, counters)
+
+
+def _make_content(testID, current_test_execution, headers, counters, t_dict,
+                  attributes, max_mismatches_allowed):
+    """
+    Prepares the comparison analysis text to be presented in the frontend
+    GUI.
+
+    @param testID: Test ID.
+    @param current_test_execution: Current text execution.
+    @param headers: Test headers.
+    @param counters: Auxiliary counters.
+    @param t_dict: Dictionary with the result that will be returned.
+    @param attributes: Test attributes.
+    @param max_mismatches_allowed: Number of maximum mismatches allowed.
+    """
+    mismatches  = t_dict.keys()
+    content = ('Test case comparison with the following attributes: %s\n' %
+               str(attributes))
+    content += ('(maximum allowed mismatching attributes = %d)\n' %
+                int(max_mismatches_allowed))
+
+    for mismatch in mismatches:
+        if mismatch not in counters:
+            content += ('\n%d mismatching attributes (%d/%d)\n' %
+                (mismatch, int(t_dict[mismatch]['passed']),
+                int(t_dict[mismatch]['total'])))
+        attribute_headers = t_dict[mismatch].keys()
+        for attr_header in attribute_headers:
+            if attr_header not in counters:
+                if int(mismatch) > 0:
+                    p = int(t_dict[mismatch][attr_header]['passed'])
+                    t = int(t_dict[mismatch][attr_header]['total'])
+                    content += '  %s (%d/%d)\n' % (attr_header, p, t)
+            attribute_values = t_dict[mismatch][attr_header].keys()
+            for attr_value in attribute_values:
+                if attr_value not in counters:
+                    if int(mismatch) > 0:
+                        p = int(
+                            t_dict[mismatch][attr_header][attr_value]['passed'])
+                        t = int(
+                            t_dict[mismatch][attr_header][attr_value]['total'])
+                        content += ('    %s (%d/%d)\n' % (attr_value, p, t))
+                    test_sets = (
+                            t_dict[mismatch][attr_header][attr_value].keys())
+                    for test_set in test_sets:
+                        if test_set not in counters:
+                            tc = (
+                            t_dict[mismatch][attr_header][attr_value][test_set])
+                            if len(tc) > 0:
+                                content += '      %s\n' % (test_set)
+                                links =[]
+                                for t in tc:
+                                    link = '%d : %s : %s' % (t[0], t[1], t[2])
+                                    content += '       %s\n' % link
+
+    return content
+
+
+def get_testcase_comparison_data(test, attr ,testset):
+    """
+    Test case comparison compares a given test execution with other executions
+    of the same test according to a predefined list of attributes. The result
+    is a dictionary of test cases classified by status (GOOD, FAIL, etc.) and
+    attributes mismatching distance (same idea as hamming distance, just
+    implemented on test attributes).
+
+    Distance of 0 refers to tests that have identical attributes values,
+    distance of 1 to tests that have only one different attribute value, so on
+    and so forth.
+
+    The general layout of test case comparison result is:
+
+    number of mismatching attributes (nSuccess/nTotal)
+       differing attributes (nSuccess/nTotal)
+         attribute value (nSuccess/nTotal)
+           Status
+             testID   reason
+             ...
+           Status (different)
+             testID   reason
+             ...
+           ...
+
+    @param test: Job ID that we'll get comparison data for.
+    @param attr: String of attributes to use for the comparison delimited by
+            comma.
+    @return: Dictionary with test comparison data that will be serialized.
+    """
+    result = {}
+    tid = None
+    attr_keys = None
+    attributes = None
+    if test:
+        tid = int(test)
+    if attr:
+        attr_keys = attr.replace(' ', '').split(',')
+
+    # process test comparison analysis
+    try:
+        c = global_config.global_config
+        max_mismatches_allowed = int(c.get_config_value('TKO',
+                                'test_comparison_maximum_attribute_mismatches'))
+        passed= ['GOOD']
+        if not tid:
+            raise model_logic.ValidationError('Test was not specified.')
+        if not attr_keys:
+            raise model_logic.ValidationError('At least one attribute must be '
+                                              'specified.')
+        if not testset:
+            raise model_logic.ValidationError('Previous test executions scope '
+                                              'must be specified.')
+
+        (attributes, test_headers, current_test_execution,
+         previous_test_executions) = _get_test_lists(tid, attr_keys, testset)
+
+        (test_comparison_dict,
+         counters) = _prepare_test_analysis_dict(test_headers,
+                                                 previous_test_executions,
+                                                 current_test_execution,
+                                                 passed, attributes,
+                                                 max_mismatches_allowed)
+
+        result = _make_content(tid, current_test_execution, test_headers,
+                               counters, test_comparison_dict, attributes,
+                               max_mismatches_allowed)
+
+    except urllib2.HTTPError:
+        result = 'Test comparison error!'
+
+    return rpc_utils.prepare_for_serialization(result)
-- 
1.6.2.5
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help