Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/framework/sorting.py b/framework/sorting.py
index 558044c..2ad4c1c 100644
--- a/framework/sorting.py
+++ b/framework/sorting.py
@@ -1,7 +1,6 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file or at
-# https://developers.google.com/open-source/licenses/bsd
+# Copyright 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Helper functions for sorting lists of project artifacts.
 
@@ -23,16 +22,16 @@
 from __future__ import division
 from __future__ import absolute_import
 
-from functools import total_ordering
+import functools
 
 import settings
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from services import caches
 from tracker import tracker_bizobj
 from tracker import tracker_constants
 
 
-@total_ordering
+@functools.total_ordering
 class DescendingValue(object):
   """A wrapper which reverses the sort order of values."""
 
@@ -149,7 +148,7 @@
     art_values_cache.CacheItem(art.issue_id, art_values)
     return sort_key
 
-  return sorted(artifacts, key=SortKey)
+  return sorted(artifacts, key=lambda x: Python2Key(SortKey(x)))
 
 
 def ComputeSortDirectives(config, group_by_spec, sort_spec, tie_breakers=None):
@@ -249,7 +248,7 @@
         key_part.extend(value)
       else:
         key_part.append(value)
-    return sorted(key_part)
+    return sorted(key_part, key=Python2Key)
 
   return _MaybeMakeDescending(CombinedAccessor, descending)
 
@@ -341,7 +340,7 @@
     else:
       values = [postprocessor(users_by_id[id_or_id_list])]
 
-    return sorted(values) or MAX_STRING
+    return sorted(values) or [MAX_STRING]
 
   return Accessor
 
@@ -427,10 +426,10 @@
     values = base_accessor(art)
     if not values:
       # Undefined values sort last.
-      return MAX_STRING
+      return [MAX_STRING]
 
     indexes = [well_known_value_indexes.get(val, MAX_STRING) for val in values]
-    return sorted(indexes)
+    return sorted(indexes, key=Python2Key)
 
   return Accessor
 
@@ -465,8 +464,8 @@
           _SortableApprovalApproverValues(art, approval_fds, users_by_id) +
           _SortableLabelValues(art, col_name, well_known_value_indexes))
       if not idx_or_lex_list:
-        return MAX_STRING  # issues with no value sort to the end of the list.
-      return sorted(idx_or_lex_list)
+        return [MAX_STRING]  # issues with no value sort to the end of the list.
+      return sorted(idx_or_lex_list, key=Python2Key)
 
     return ApproverAccessor
 
@@ -494,8 +493,8 @@
         _SortableFieldValues(art, fd_list, users_by_id, phase_name) +
         _SortableLabelValues(art, col_name, well_known_value_indexes))
     if not idx_or_lex_list:
-      return MAX_STRING  # issues with no value sort to the end of the list.
-    return sorted(idx_or_lex_list)
+      return [MAX_STRING]  # issues with no value sort to the end of the list.
+    return sorted(idx_or_lex_list, key=Python2Key)
 
   return Accessor
 
@@ -573,3 +572,37 @@
     sortable_value_list.append(idx_or_lex)
 
   return sortable_value_list
+
+
+def _Python2Cmp(a, b):
+  """Compares two objects in the Python 2 way.
+
+  In Python 3, comparing two objects of different types raises a TypeError.
+  In Python 2, when you compare two objects of different types, they are
+  generally ordered by their type names, with a few special cases carved
+  out for int/float and str/unicode.
+
+  This comparison function also looks through lists and compares them pairwise.
+  It doesn't do the same for other iterables.
+  """
+  try:
+    # First try comparing the objects directly.
+    # https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
+    return (a > b) - (a < b)
+  except TypeError:
+    s1, s2 = type(a).__name__, type(b).__name__
+    if not (s1 == 'list' and s2 == 'list'):
+      # If they are different types, compare their type names.
+      return (s1 > s2) - (s1 < s2)
+
+    # If they are both lists, compare their elements pairwise.
+    for x, y in zip(a, b):
+      element_cmp = _Python2Cmp(x, y)
+      if element_cmp != 0:
+        return element_cmp
+
+    # If the lists start with the same elements, compare their lengths.
+    return (len(a) > len(b)) - (len(a) < len(b))
+
+
+Python2Key = functools.cmp_to_key(_Python2Cmp)