[moblab] Sort the build numbers correctly when generating build list.

Build numbers were being sorted by standard cmp - change to a custom
comparison that gets the order correct.

BUG=chromium:681915
TEST=unit tests and local moblab testing

Change-Id: I3d1ae480420aa8ff9b1c96bec161106f0b0aca4b
Reviewed-on: https://chromium-review.googlesource.com/502048
Commit-Ready: Keith Haddow <haddowk@chromium.org>
Tested-by: Keith Haddow <haddowk@chromium.org>
Reviewed-by: Keith Haddow <haddowk@chromium.org>
Reviewed-by: Michael Tang <ntang@chromium.org>
diff --git a/frontend/afe/moblab_rpc_interface.py b/frontend/afe/moblab_rpc_interface.py
index ee7a60b..f0dd4c7 100644
--- a/frontend/afe/moblab_rpc_interface.py
+++ b/frontend/afe/moblab_rpc_interface.py
@@ -614,6 +614,30 @@
     return _get_builds_for_in_directory(board_name + '-firmware')
 
 
+def _get_sortable_build_number(sort_key):
+    """ Converts a build number line cyan-release/R59-9460.27.0 into an integer.
+
+        To be able to sort a list of builds you need to convert the build number
+        into an integer so it can be compared correctly to other build.
+
+        cyan-release/R59-9460.27.0 =>  5909460027000
+
+        If the sort key is not recognised as a build number 1 will be returned.
+
+    @param sort_key: A string that represents a build number like
+                     cyan-release/R59-9460.27.0
+    @return: An integer that represents that build number or 1 if not recognised
+             as a build.
+    """
+    build_number = re.search('.*/R([0-9]*)-([0-9]*)\.([0-9]*)\.([0-9]*)',
+                             sort_key)
+    if not build_number or not len(build_number.groups()) == 4:
+      return 1
+    return int("%d%05d%03d%03d" % (int(build_number.group(1)),
+                                   int(build_number.group(2)),
+                                   int(build_number.group(3)),
+                                   int(build_number.group(4))))
+
 def _get_builds_for_in_directory(directory_name, milestone_limit=3,
                                  build_limit=20):
     """ Fetch the most recent builds for the last three milestones from gcs.
@@ -650,6 +674,7 @@
     build_list = []
     for milestone in milestones[:milestone_limit]:
          builds = build_map[milestone]
+         builds.sort(key=_get_sortable_build_number)
          builds.reverse()
          build_list.extend(builds[:build_limit])
     return build_list
diff --git a/frontend/afe/moblab_rpc_interface_unittest.py b/frontend/afe/moblab_rpc_interface_unittest.py
index d7eb30a..062b061 100644
--- a/frontend/afe/moblab_rpc_interface_unittest.py
+++ b/frontend/afe/moblab_rpc_interface_unittest.py
@@ -369,6 +369,55 @@
         self.assertEquals('value6', shadow_config.get('section2', 'opt6'))
         self.mox.VerifyAll()
 
+    def testGetBuildsForInDirectory(self):
+        config_mock = self.mox.CreateMockAnything()
+        moblab_rpc_interface._CONFIG = config_mock
+        config_mock.get_config_value(
+            'CROS', 'image_storage_server').AndReturn('gs://bucket1/')
+        self.mox.StubOutWithMock(common_lib_utils, 'run')
+        output = self.mox.CreateMockAnything()
+        self.mox.StubOutWithMock(StringIO, 'StringIO', use_mock_anything=True)
+        StringIO.StringIO().AndReturn(output)
+        output.getvalue().AndReturn(
+        """gs://bucket1/dummy/R53-8480.0.0/\ngs://bucket1/dummy/R53-8530.72.0/\n
+        gs://bucket1/dummy/R54-8712.0.0/\ngs://bucket1/dummy/R54-8717.0.0/\n
+        gs://bucket1/dummy/R55-8759.0.0/\n
+        gs://bucket1/dummy/R55-8760.0.0-b5849/\n
+        gs://bucket1/dummy/R56-8995.0.0/\ngs://bucket1/dummy/R56-9001.0.0/\n
+        gs://bucket1/dummy/R57-9202.66.0/\ngs://bucket1/dummy/R58-9331.0.0/\n
+        gs://bucket1/dummy/R58-9334.15.0/\ngs://bucket1/dummy/R58-9334.17.0/\n
+        gs://bucket1/dummy/R58-9334.18.0/\ngs://bucket1/dummy/R58-9334.19.0/\n
+        gs://bucket1/dummy/R58-9334.22.0/\ngs://bucket1/dummy/R58-9334.28.0/\n
+        gs://bucket1/dummy/R58-9334.3.0/\ngs://bucket1/dummy/R58-9334.30.0/\n
+        gs://bucket1/dummy/R58-9334.36.0/\ngs://bucket1/dummy/R58-9334.55.0/\n
+        gs://bucket1/dummy/R58-9334.6.0/\ngs://bucket1/dummy/R58-9334.7.0/\n
+        gs://bucket1/dummy/R58-9334.9.0/\ngs://bucket1/dummy/R59-9346.0.0/\n
+        gs://bucket1/dummy/R59-9372.0.0/\ngs://bucket1/dummy/R59-9387.0.0/\n
+        gs://bucket1/dummy/R59-9436.0.0/\ngs://bucket1/dummy/R59-9452.0.0/\n
+        gs://bucket1/dummy/R59-9453.0.0/\ngs://bucket1/dummy/R59-9455.0.0/\n
+        gs://bucket1/dummy/R59-9460.0.0/\ngs://bucket1/dummy/R59-9460.11.0/\n
+        gs://bucket1/dummy/R59-9460.16.0/\ngs://bucket1/dummy/R59-9460.25.0/\n
+        gs://bucket1/dummy/R59-9460.8.0/\ngs://bucket1/dummy/R59-9460.9.0/\n
+        gs://bucket1/dummy/R60-9472.0.0/\ngs://bucket1/dummy/R60-9491.0.0/\n
+        gs://bucket1/dummy/R60-9492.0.0/\ngs://bucket1/dummy/R60-9497.0.0/\n
+        gs://bucket1/dummy/R60-9500.0.0/""")
+
+        output.close()
+
+        common_lib_utils.run(moblab_rpc_interface._GSUTIL_CMD,
+                             args=('ls', 'gs://bucket1/dummy'),
+                             stdout_tee=mox.IgnoreArg()).AndReturn(output)
+        self.mox.ReplayAll()
+        expected_results = ['dummy/R60-9500.0.0', 'dummy/R60-9497.0.0',
+            'dummy/R60-9492.0.0', 'dummy/R60-9491.0.0', 'dummy/R60-9472.0.0',
+            'dummy/R59-9460.25.0', 'dummy/R59-9460.16.0', 'dummy/R59-9460.11.0',
+            'dummy/R59-9460.9.0', 'dummy/R59-9460.8.0', 'dummy/R58-9334.55.0',
+            'dummy/R58-9334.36.0', 'dummy/R58-9334.30.0', 'dummy/R58-9334.28.0',
+            'dummy/R58-9334.22.0']
+        actual_results = moblab_rpc_interface._get_builds_for_in_directory(
+            "dummy",3, 5)
+        self.assertEquals(expected_results, actual_results)
+        self.mox.VerifyAll()
 
     def testRunBucketPerformanceTestFail(self):
         self.mox.StubOutWithMock(common_lib_utils, 'run')