autotest: Add test counts to test_push

BUG=chromium:940564
TEST=unit tests, also will be tested in staging after submit

Change-Id: If53c858a3c3ca8a405de8cbf8a6156a34aba02e9
Reviewed-on: https://chromium-review.googlesource.com/1554985
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Commit-Ready: Alex Zamorzaev <zamorzaev@chromium.org>
Tested-by: Alex Zamorzaev <zamorzaev@chromium.org>
Reviewed-by: Prathmesh Prabhu <pprabhu@chromium.org>
diff --git a/server/site_utils.py b/server/site_utils.py
index bc0c22a..f03dd5b 100644
--- a/server/site_utils.py
+++ b/server/site_utils.py
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 
+import collections
 import contextlib
 import grp
 import httplib
@@ -341,6 +342,7 @@
 
 
 def host_in_lab(hostname):
+    """Check if the execution is against a host in the lab"""
     return (not utils.in_moblab_ssp()
             and not lsbrelease_utils.is_moblab()
             and utils.host_is_in_lab_zone(hostname))
@@ -388,8 +390,12 @@
 
     @param suite_job_id: ID of suite job.
     @param tko: an instance of TKO as defined in server/frontend.py.
-    @return: A dictionary of test status keyed by test name, e.g.,
-             {'dummy_Fail.Error': 'ERROR', 'dummy_Fail.NAError': 'TEST_NA'}
+    @return: A defaultdict where keys are test names and values are
+             lists of test statuses, e.g.,
+             {'dummy_Fail.Error': ['ERROR'. 'ERROR'],
+              'dummy_Fail.NAError': ['TEST_NA'],
+              'dummy_Fail.RetrySuccess': ['ERROR', 'GOOD'],
+              }
     @raise: Exception when there is no test view found.
 
     """
@@ -398,10 +404,9 @@
     if not relevant_views:
         raise Exception('Failed to retrieve job results.')
 
-    test_views = {}
+    test_views = collections.defaultdict(list)
     for view in relevant_views:
-        test_views[view['test_name']] = view['status']
-
+        test_views[view['test_name']].append(view['test_status'])
     return test_views
 
 
diff --git a/server/site_utils_unittest.py b/server/site_utils_unittest.py
index 8c7c31d..294dc8f 100644
--- a/server/site_utils_unittest.py
+++ b/server/site_utils_unittest.py
@@ -2,16 +2,18 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import mox
 import unittest
 
 import common
 from autotest_lib.frontend import setup_django_lite_environment
+from autotest_lib.server import frontend
 from autotest_lib.server import site_utils
 from autotest_lib.server.cros.dynamic_suite import tools
 from autotest_lib.server.cros.dynamic_suite import suite_common
 
 
-class SiteUtilsUnittests(unittest.TestCase):
+class SiteUtilsUnittests(mox.MoxTestBase):
     """Test functions in site_utils.py"""
 
     def testParseJobName(self):
@@ -51,5 +53,47 @@
                              '%s' % (test_job_name, expected_info))
 
 
+    def testGetViewsFromTko(self):
+        """Test method get_test_views_from_tko
+        """
+        test_results = [
+            ('dummy_Pass', 'GOOD'),
+            ('dummy_Fail.RetrySuccess', 'GOOD'),
+            ('dummy_Fail.RetrySuccess', 'FAIL'),
+            ('dummy_Fail.Fail', 'FAIL'),
+            ('dummy_Fail.Fail', 'FAIL'),
+        ]
+
+        expected_test_views = {
+            'dummy_Pass': ['GOOD'],
+            'dummy_Fail.RetrySuccess': ['FAIL', 'GOOD'],
+            'dummy_Fail.Fail': ['FAIL', 'FAIL'],
+        }
+
+        self.mox.UnsetStubs()
+        tko = self.mox.CreateMock(frontend.TKO)
+        tko.run('get_detailed_test_views', afe_job_id=0).AndReturn(
+            [{'test_name':r[0], 'test_status':r[1]} for r in test_results])
+
+        self.mox.ReplayAll()
+        test_views = site_utils.get_test_views_from_tko(0, tko)
+        self.mox.VerifyAll()
+
+        self.assertEqual(sorted(test_views.keys()),
+                         sorted(expected_test_views.keys()),
+                         'Test list %s does not match expected test list %s.' %
+                         (sorted(test_views.keys()),
+                          sorted(expected_test_views.keys())))
+
+        for test_name, test_status_list in test_views.iteritems():
+            self.assertEqual(sorted(test_status_list),
+                             sorted(expected_test_views[test_name]),
+                             'For test %s the status list %s does not match '
+                             'expected status list %s.' %
+                             (test_name,
+                              sorted(test_status_list),
+                              sorted(expected_test_views[test_name])))
+
+
 if __name__ == '__main__':
     unittest.main()