Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/search/ast2ast.py b/search/ast2ast.py
index bf4de4f..0e1e4cf 100644
--- a/search/ast2ast.py
+++ b/search/ast2ast.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.
 
 """Convert a user's issue search AST into a simplified AST.
 
@@ -36,8 +35,8 @@
 import re
 
 from framework import exceptions
-from proto import ast_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import tracker_pb2
 # TODO(jrobbins): if BUILTIN_ISSUE_FIELDS was passed through, I could
 # remove this dep.
 from search import query2ast
diff --git a/search/ast2select.py b/search/ast2select.py
index a6e5f17..cbcb0b0 100644
--- a/search/ast2select.py
+++ b/search/ast2select.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.
 
 """Convert a user's issue search AST into SQL clauses.
 
@@ -26,8 +25,8 @@
 import logging
 
 from framework import sql
-from proto import ast_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import tracker_pb2
 from search import query2ast
 from services import tracker_fulltext
 
diff --git a/search/ast2sort.py b/search/ast2sort.py
index 08ed346..8abb798 100644
--- a/search/ast2sort.py
+++ b/search/ast2sort.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.
 
 """Convert a user's issue sorting directives into SQL clauses.
 
@@ -28,7 +27,7 @@
 import logging
 
 from framework import sql
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from tracker import tracker_constants
 
 
diff --git a/search/backendnonviewable.py b/search/backendnonviewable.py
index ab751a4..21f1189 100644
--- a/search/backendnonviewable.py
+++ b/search/backendnonviewable.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.
 
 """Servlet that searches for issues that the specified user cannot view.
 
@@ -37,7 +36,7 @@
 NONVIEWABLE_MEMCACHE_EXPIRATION = 15 * framework_constants.SECS_PER_MINUTE
 
 
-class BackendNonviewable(jsonfeed.FlaskInternalTask):
+class BackendNonviewable(jsonfeed.InternalTask):
   """JSON servlet for getting issue IDs that the specified user cannot view."""
 
   CHECK_SAME_APP = True
@@ -114,7 +113,7 @@
 
   def GetAtRiskIIDs(
     self, cnxn, user, effective_ids, project, perms, shard_id):
-    # type: (MonorailConnection, proto.user_pb2.User, Sequence[int], Project,
+    # type: (MonorailConnection, mrproto.user_pb2.User, Sequence[int], Project,
     #     permission_objects_pb2.PermissionSet, int) -> Sequence[int]
     """Return IIDs of restricted issues that user might not be able to view."""
     at_risk_label_ids = search_helpers.GetPersonalAtRiskLabelIDs(
diff --git a/search/backendsearch.py b/search/backendsearch.py
index 91a00dc..f0fbbbe 100644
--- a/search/backendsearch.py
+++ b/search/backendsearch.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.
 
 """A servlet that implements the backend of issues search.
 
@@ -31,7 +30,7 @@
 from tracker import tracker_constants
 
 
-class BackendSearch(jsonfeed.FlaskInternalTask):
+class BackendSearch(jsonfeed.InternalTask):
   """JSON servlet for issue search in a GAE backend."""
 
   CHECK_SAME_APP = True
@@ -65,7 +64,7 @@
                  int(1000 * (time.time() - start)))
 
     if pipeline.error:
-      error_message = pipeline.error.message
+      error_message = str(pipeline.error)
     else:
       error_message = None
 
diff --git a/search/backendsearchpipeline.py b/search/backendsearchpipeline.py
index 69fdc6b..56fb6af 100644
--- a/search/backendsearchpipeline.py
+++ b/search/backendsearchpipeline.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.
 
 """Backend issue issue search and sorting.
 
@@ -27,8 +26,8 @@
 from framework import framework_helpers
 from framework import sorting
 from framework import sql
-from proto import ast_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import tracker_pb2
 from search import ast2ast
 from search import ast2select
 from search import ast2sort
@@ -156,11 +155,11 @@
     where.extend(query_where)
   except ast2ast.MalformedQuery as e:
     # TODO(jrobbins): inform the user that their query had invalid tokens.
-    logging.info('Invalid query tokens %s.\n %r\n\n', e.message, query_ast)
+    logging.info('Invalid query tokens %s.\n %r\n\n', str(e), query_ast)
     return [], False, e
   except ast2select.NoPossibleResults as e:
     # TODO(jrobbins): inform the user that their query was impossible.
-    logging.info('Impossible query %s.\n %r\n\n', e.message, query_ast)
+    logging.info('Impossible query %s.\n %r\n\n', str(e), query_ast)
     return [], False, e
   logging.info('translated to left_joins %r', left_joins)
   logging.info('translated to where %r', where)
@@ -203,9 +202,9 @@
 
   issue_ids, db_capped = services.issue.RunIssueQuery(
       cnxn, left_joins + sort_left_joins, where, order_by, shard_id=shard_id)
-  logging.warn('executed "%s" query %r for %d issues in %dms',
-               query_desc, query_ast, len(issue_ids),
-               int((time.time() - start_time) * 1000))
+  logging.warning(
+      'executed "%s" query %r for %d issues in %dms', query_desc, query_ast,
+      len(issue_ids), int((time.time() - start_time) * 1000))
   capped = fts_capped or db_capped
   return issue_ids, capped, None
 
@@ -280,7 +279,7 @@
       query_desc='getting query issue IDs')
   logging.info('Found %d result_iids', len(result_iids))
   if error:
-    logging.warn('Got error %r', error)
+    logging.warning('Got error %r', error)
 
   projects_str = ','.join(str(pid) for pid in sorted(query_project_ids))
   projects_str = projects_str or 'all'
diff --git a/search/frontendsearchpipeline.py b/search/frontendsearchpipeline.py
index ec0a28e..b606672 100644
--- a/search/frontendsearchpipeline.py
+++ b/search/frontendsearchpipeline.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.
 
 """The FrontendSearchPipeline class manages issue search and sorting.
 
@@ -24,6 +23,7 @@
 import logging
 import math
 import random
+import six
 import time
 
 from google.appengine.api import apiproxy_stub_map
@@ -524,9 +524,9 @@
         cnxn, query_ast, project_ids, services, harmonized_config,
         is_member=member_of_all_projects)
   except query2ast.InvalidQueryError as e:
-    return e.message
+    return str(e)
   except ast2ast.MalformedQuery as e:
-    return e.message
+    return str(e)
 
   return None
 
@@ -551,7 +551,7 @@
     error_responses, services, me_user_ids, logged_in_user_id, new_url_num,
     subqueries, can, group_by_spec, sort_spec, warnings, use_cached_searches):
   # type: (MonorailConnection, Sequence[str], Sequence[int],
-  #     proto.tracker_pb2.ProjectIssueConfig,
+  #     mrproto.tracker_pb2.ProjectIssueConfig,
   #     Mapping[Tuple(int, str), Sequence[int]],
   #     Mapping[Tuple(int, str), Sequence[bool]],
   #     Mapping[Tuple(int, str), Collection[int]], Sequence[Tuple(int, str)],
@@ -690,7 +690,7 @@
 
   Instead, we do the same check, without blocking on any individual RPC.
   """
-  if settings.local_mode:
+  if six.PY3 or settings.local_mode:
     # The development server has very different code for RPCs than the
     # code used in the hosted environment.
     return apiproxy_stub_map.UserRPC.wait_any(active_rpcs)
@@ -777,7 +777,6 @@
       nonviewable_iids[sid] = set(issue_ids)
 
   if sid not in nonviewable_iids:
-    logging.info('nonviewable for %r not found', key)
     logging.info('starting backend call for nonviewable iids %r', key)
     rpc = _StartBackendNonviewableCall(
       pid, logged_in_user_id, sid, invalidation_timestep)
@@ -1048,7 +1047,7 @@
     logging.info('got json text: %r length %r',
                  json_content[:framework_constants.LOGGING_MAX_LENGTH],
                  len(json_content))
-    if json_content == '':
+    if json_content == b'':
       raise Exception('Fast fail')
     json_data = json.loads(json_content)
     unfiltered_iids[shard_key] = json_data['unfiltered_iids']
@@ -1110,7 +1109,7 @@
     logging.info('got json text: %r length %r',
                  json_content[:framework_constants.LOGGING_MAX_LENGTH],
                  len(json_content))
-    if json_content == '':
+    if json_content == b'':
       raise Exception('Fast fail')
     json_data = json.loads(json_content)
     nonviewable_iids[shard_id] = set(json_data['nonviewable'])
@@ -1120,7 +1119,7 @@
       logging.exception(e)
 
     if not remaining_retries:
-      logging.warn('Used all retries, so give up on shard %r', shard_id)
+      logging.warning('Used all retries, so give up on shard %r', shard_id)
       return
 
     if duration_sec >= settings.backend_deadline:
diff --git a/search/query2ast.py b/search/query2ast.py
index 235f9b3..40c5cdc 100644
--- a/search/query2ast.py
+++ b/search/query2ast.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.
 
 """A set of functions that integrate the GAE search index with Monorail."""
 from __future__ import print_function
@@ -16,8 +15,8 @@
 
 from google.appengine.api import search
 
-from proto import ast_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import tracker_pb2
 
 
 # TODO(jrobbins): Consider re-implementing this whole file by using a
@@ -191,9 +190,9 @@
 def ParseUserQuery(
     query, scope, builtin_fields, harmonized_config, warnings=None,
     now=None):
-  # type: (str, str, Mapping[str, proto.tracker_pb2.FieldDef],
-  #   proto.tracker_pb2.ProjectIssueConfig, Sequence[str], int) ->
-  #     proto.ast_pb2.QueryAST
+  # type: (str, str, Mapping[str, mrproto.tracker_pb2.FieldDef],
+  #   mrproto.tracker_pb2.ProjectIssueConfig, Sequence[str], int) ->
+  #     mrproto.ast_pb2.QueryAST
   """Parse a user query and return a set of structure terms.
 
   Args:
@@ -251,8 +250,8 @@
 
 
 def _ParseConjunction(subquery, scope, fields, warnings, now=None):
-  # type: (str, str, Mapping[str, proto.tracker_pb2.FieldDef], Sequence[str],
-  #     int) -> proto.ast_pb2.Condition
+  # type: (str, str, Mapping[str, mrproto.tracker_pb2.FieldDef], Sequence[str],
+  #     int) -> mrproto.ast_pb2.Condition
   """Parse part of a user query into a Conjunction PB."""
   scoped_query = ('%s %s' % (scope, subquery)).lower()
   cond_strs = _ExtractConds(scoped_query, warnings)
@@ -263,8 +262,8 @@
 
 
 def _ParseCond(cond_str, fields, warnings, now=None):
-  # type: (str, Mapping[str, proto.tracker_pb2.FieldDef], Sequence[str],
-  #     int) -> proto.ast_pb2.Condition
+  # type: (str, Mapping[str, mrproto.tracker_pb2.FieldDef], Sequence[str],
+  #     int) -> mrproto.ast_pb2.Condition
   """Parse one user query condition string into a Condition PB."""
   op_match = OP_RE.match(cond_str)
   # Do not treat as key:value search terms if any of the special prefixes match.
@@ -308,8 +307,8 @@
 
 
 def _ParseStructuredTerm(prefix, op_str, value, fields, now=None):
-  # type: (str, str, str, Mapping[str, proto.tracker_pb2.FieldDef]) ->
-  #     proto.ast_pb2.Condition
+  # type: (str, str, str, Mapping[str, mrproto.tracker_pb2.FieldDef]) ->
+  #     mrproto.ast_pb2.Condition
   """Parse one user structured query term into an internal representation.
 
   Args:
@@ -571,7 +570,7 @@
 
 
 def _ParseQuery(token_iterator):
-  # type (Sequence[proto.ast_pb2.QueryToken]) -> Sequence[str]
+  # type (Sequence[mrproto.ast_pb2.QueryToken]) -> Sequence[str]
   """Recursive helper to convert query tokens into a list of subqueries.
 
   Parses a Query based on the following grammar (EBNF):
@@ -633,7 +632,7 @@
 
 
 def _ParseOrGroup(token_iterator):
-  # type (Sequence[proto.ast_pb2.QueryToken]) -> Sequence[str]
+  # type (Sequence[mrproto.ast_pb2.QueryToken]) -> Sequence[str]
   """Recursive helper to convert a single "OrGroup" into subqueries.
 
   An OrGroup here is based on the following grammar:
@@ -675,7 +674,7 @@
 
 
 def _ParseAndGroup(token_iterator):
-  # type (Sequence[proto.ast_pb2.QueryToken]) -> Sequence[str]
+  # type (Sequence[mrproto.ast_pb2.QueryToken]) -> Sequence[str]
   """Recursive helper to convert a single "AndGroup" into subqueries.
 
   An OrGroup here is based on the following grammar:
@@ -724,7 +723,7 @@
 
 
 def _ValidateAndTokenizeQuery(query):
-  # type: (str) -> Sequence[proto.ast_pb2.QueryToken]
+  # type: (str) -> Sequence[mrproto.ast_pb2.QueryToken]
   """Converts the input query into a set of tokens for easier parsing.
 
   Tokenizing the query string before parsing allows us to not have to as many
@@ -777,7 +776,7 @@
 
 
 def _TokenizeSubqueryOnOr(subquery):
-  # type: (str) -> Sequence[proto.ast_pb2.QueryToken]
+  # type: (str) -> Sequence[mrproto.ast_pb2.QueryToken]
   """Helper to split a subquery by OR and convert the result into tokens.
 
   Args:
@@ -872,10 +871,24 @@
       pass
     return 'End of PeekIterator'
 
+  def __next__(self):
+    # type: () -> Any
+    """Gets the next value in the iterator and increments pointer.
+
+    Returns:
+      Next value in iterator.
+
+    Raises:
+      StopIteration if you're at the end of the iterator.
+    """
+    return self.next()
+
   def next(self):
     # type: () -> Any
     """Gets the next value in the iterator and increments pointer.
 
+    For backwards compatibility with Python 2.
+
     Returns:
       Next value in iterator.
 
diff --git a/search/search_helpers.py b/search/search_helpers.py
index 0b3beb8..2ff4c50 100644
--- a/search/search_helpers.py
+++ b/search/search_helpers.py
@@ -1,7 +1,6 @@
-# Copyright 2018 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 2018 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 from __future__ import print_function
 from __future__ import division
diff --git a/search/searchpipeline.py b/search/searchpipeline.py
index 422a619..0147e53 100644
--- a/search/searchpipeline.py
+++ b/search/searchpipeline.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 and classes used in issue search and sorting."""
 from __future__ import print_function
diff --git a/search/test/ast2ast_test.py b/search/test/ast2ast_test.py
index 9edeaf1..cd50b18 100644
--- a/search/test/ast2ast_test.py
+++ b/search/test/ast2ast_test.py
@@ -1,17 +1,17 @@
-# 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.
 
 """Tests for the ast2ast module."""
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
 
+import six
 import unittest
 
-from proto import ast_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import tracker_pb2
 from search import ast2ast
 from search import query2ast
 from services import service_manager
@@ -182,7 +182,7 @@
       self.assertEqual(
           'Searching for issues accross multiple/all projects without '
           'project prefixes is ambiguous and is currently not supported.',
-          cm.exception.message)
+          str(cm.exception))
 
   def testPreprocessBlockedOnCond_WithExternalIssues(self):
     blockedon_field = BUILTIN_ISSUE_FIELDS['blockedon']
@@ -322,7 +322,7 @@
       self.assertEqual(
           'Searching for issues accross multiple/all projects without '
           'project prefixes is ambiguous and is currently not supported.',
-          cm.exception.message)
+          str(cm.exception))
 
   def testPreprocessBlockingCond_WithExternalIssues(self):
     blocking_field = BUILTIN_ISSUE_FIELDS['blocking']
@@ -452,19 +452,19 @@
         ast_pb2.QueryOp.IS_DEFINED, [BUILTIN_ISSUE_FIELDS['label']],
         ['Priority', 'Severity'], [])
     regex = ast2ast._MakePrefixRegex(cond)
-    self.assertRegexpMatches('Priority-1', regex)
-    self.assertRegexpMatches('Severity-3', regex)
-    self.assertNotRegexpMatches('My-Priority', regex)
+    self.assertRegex('Priority-1', regex)
+    self.assertRegex('Severity-3', regex)
+    self.assertNotRegex('My-Priority', regex)
 
   def testKeyValueRegex(self):
     cond = ast_pb2.MakeCond(
         ast_pb2.QueryOp.KEY_HAS, [BUILTIN_ISSUE_FIELDS['label']],
         ['Type-Feature', 'Type-Security'], [])
     regex = ast2ast._MakeKeyValueRegex(cond)
-    self.assertRegexpMatches('Type-Feature', regex)
-    self.assertRegexpMatches('Type-Bug-Security', regex)
-    self.assertNotRegexpMatches('Type-Bug', regex)
-    self.assertNotRegexpMatches('Security-Feature', regex)
+    self.assertRegex('Type-Feature', regex)
+    self.assertRegex('Type-Bug-Security', regex)
+    self.assertNotRegex('Type-Bug', regex)
+    self.assertNotRegex('Security-Feature', regex)
 
   def testKeyValueRegex_multipleKeys(self):
     cond = ast_pb2.MakeCond(
@@ -478,8 +478,8 @@
         ast_pb2.QueryOp.TEXT_HAS, [BUILTIN_ISSUE_FIELDS['label']],
         ['Type-Bug'], [])
     regex = ast2ast._MakeKeyValueRegex(cond)
-    self.assertRegexpMatches('Type-Bug-Security', regex)
-    self.assertNotRegexpMatches('Type-BugSecurity', regex)
+    self.assertRegex('Type-Bug-Security', regex)
+    self.assertNotRegex('Type-BugSecurity', regex)
 
   def testPreprocessLabelCond(self):
     label_field = BUILTIN_ISSUE_FIELDS['label']
@@ -693,7 +693,7 @@
         self.cnxn, cond, [1], self.services, None, True)
     self.assertEqual(ast_pb2.QueryOp.EQ, actual.op)
     self.assertEqual([hotlist_id_field], actual.field_defs)
-    self.assertItemsEqual([10, 30, 40, 50, 60], actual.int_values)
+    six.assertCountEqual(self, [10, 30, 40, 50, 60], actual.int_values)
 
   def testPreprocessHotlistCond_UserNotFound(self):
     hotlist_field = BUILTIN_ISSUE_FIELDS['hotlist']
diff --git a/search/test/ast2select_test.py b/search/test/ast2select_test.py
index f20d524..903c8c8 100644
--- a/search/test/ast2select_test.py
+++ b/search/test/ast2select_test.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.
 
 """Tests for the ast2select module."""
 from __future__ import print_function
@@ -13,8 +12,8 @@
 import unittest
 
 from framework import sql
-from proto import ast_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import tracker_pb2
 from search import ast2select
 from search import query2ast
 from tracker import tracker_bizobj
diff --git a/search/test/ast2sort_test.py b/search/test/ast2sort_test.py
index 9d365e8..ac5a00b 100644
--- a/search/test/ast2sort_test.py
+++ b/search/test/ast2sort_test.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.
 
 """Tests for the ast2sort module."""
 from __future__ import print_function
@@ -10,7 +9,7 @@
 
 import unittest
 
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from search import ast2sort
 from search import query2ast
 
diff --git a/search/test/backendnonviewable_test.py b/search/test/backendnonviewable_test.py
index 5360a93..a1b16c7 100644
--- a/search/test/backendnonviewable_test.py
+++ b/search/test/backendnonviewable_test.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.
 
 """Unittests for monorail.search.backendnonviewable."""
 from __future__ import print_function
diff --git a/search/test/backendsearch_test.py b/search/test/backendsearch_test.py
index 6a9a710..f018f2f 100644
--- a/search/test/backendsearch_test.py
+++ b/search/test/backendsearch_test.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.
 
 """Unittests for monorail.search.backendsearch."""
 from __future__ import print_function
@@ -125,4 +124,4 @@
     self.mox.VerifyAll()
     self.assertEqual([], json_data['unfiltered_iids'])
     self.assertFalse(json_data['search_limit_reached'])
-    self.assertEqual(error.message, json_data['error'])
+    self.assertEqual(str(error), json_data['error'])
diff --git a/search/test/backendsearchpipeline_test.py b/search/test/backendsearchpipeline_test.py
index dab2dba..1da86e7 100644
--- a/search/test/backendsearchpipeline_test.py
+++ b/search/test/backendsearchpipeline_test.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.
 
 """Tests for the backendsearchpipeline module."""
 from __future__ import print_function
@@ -21,8 +20,8 @@
 from framework import framework_helpers
 from framework import sorting
 from framework import sql
-from proto import ast_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import tracker_pb2
 from search import backendsearchpipeline
 from search import ast2ast
 from search import query2ast
diff --git a/search/test/frontendsearchpipeline_test.py b/search/test/frontendsearchpipeline_test.py
index 9a94c3d..09883ad 100644
--- a/search/test/frontendsearchpipeline_test.py
+++ b/search/test/frontendsearchpipeline_test.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.
 
 """Tests for the frontendsearchpipeline module."""
 from __future__ import print_function
@@ -23,9 +22,9 @@
 from framework import framework_helpers
 from framework import sorting
 from framework import urls
-from proto import ast_pb2
-from proto import project_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import project_pb2
+from mrproto import tracker_pb2
 from search import frontendsearchpipeline
 from search import searchpipeline
 from search import query2ast
diff --git a/search/test/query2ast_test.py b/search/test/query2ast_test.py
index fc92e72..a122d99 100644
--- a/search/test/query2ast_test.py
+++ b/search/test/query2ast_test.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.
 
 """Tests for the query2ast module."""
 from __future__ import print_function
@@ -13,8 +12,8 @@
 import unittest
 import mock
 
-from proto import ast_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import tracker_pb2
 from search import query2ast
 from tracker import tracker_bizobj
 
@@ -751,7 +750,7 @@
         query2ast.ParseUserQuery(
             'modified>=' + val, '', BUILTIN_ISSUE_FIELDS,
             self.default_config)
-      self.assertEqual('Could not parse date: ' + val, cm.exception.message)
+      self.assertEqual('Could not parse date: ' + val, str(cm.exception))
 
   def testQueryToSubqueries_BasicQuery(self):
     self.assertEqual(['owner:me'], query2ast.QueryToSubqueries('owner:me'))
diff --git a/search/test/search_helpers_test.py b/search/test/search_helpers_test.py
index b9cfb51..e5746a6 100644
--- a/search/test/search_helpers_test.py
+++ b/search/test/search_helpers_test.py
@@ -1,7 +1,6 @@
-# Copyright 2018 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 2018 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for monorail.search.search_helpers."""
 from __future__ import print_function
@@ -19,7 +18,7 @@
 from google.appengine.ext import testbed
 from framework import permissions
 from framework import sql
-from proto import user_pb2
+from mrproto import user_pb2
 from services import chart_svc
 from services import service_manager
 from testing import fake
diff --git a/search/test/searchpipeline_test.py b/search/test/searchpipeline_test.py
index 5d23316..ce4426a 100644
--- a/search/test/searchpipeline_test.py
+++ b/search/test/searchpipeline_test.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.
 
 """Tests for the searchpipeline module."""
 from __future__ import print_function
@@ -10,8 +9,8 @@
 
 import unittest
 
-from proto import ast_pb2
-from proto import tracker_pb2
+from mrproto import ast_pb2
+from mrproto import tracker_pb2
 from search import searchpipeline
 from services import service_manager
 from testing import fake