Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/features/test/activities_test.py b/features/test/activities_test.py
index c17eb4b..981e894 100644
--- a/features/test/activities_test.py
+++ b/features/test/activities_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.feature.activities."""
 from __future__ import print_function
@@ -18,8 +17,8 @@
 from features import activities
 from framework import framework_views
 from framework import profiler
-from proto import tracker_pb2
-from proto import user_pb2
+from mrproto import tracker_pb2
+from mrproto import user_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
diff --git a/features/test/alert2issue_test.py b/features/test/alert2issue_test.py
index 2046b5b..ce97fbc 100644
--- a/features/test/alert2issue_test.py
+++ b/features/test/alert2issue_test.py
@@ -1,14 +1,13 @@
-# Copyright 2019 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 2019 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.feature.alert2issue."""
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
 
-import email
+import email.message
 import unittest
 from mock import patch
 try:
@@ -20,7 +19,7 @@
 from features import alert2issue
 from framework import authdata
 from framework import emailfmt
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
@@ -316,7 +315,7 @@
 
     # create a test email message, which tests can alternate the header values
     # to verify the behaviour of a given parser function.
-    self.test_msg = email.Message.Message()
+    self.test_msg = email.message.Message()
     for key, value in self.msg.items():
       self.test_msg[key] = value
 
diff --git a/features/test/autolink_test.py b/features/test/autolink_test.py
index a779014..bf02b8e 100644
--- a/features/test/autolink_test.py
+++ b/features/test/autolink_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.
 
 """Unittest for the autolink feature."""
 from __future__ import print_function
@@ -14,7 +13,7 @@
 from features import autolink
 from features import autolink_constants
 from framework import template_helpers
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from testing import fake
 from testing import testing_helpers
 
diff --git a/features/test/banspammer_test.py b/features/test/banspammer_test.py
index e12c506..f96358a 100644
--- a/features/test/banspammer_test.py
+++ b/features/test/banspammer_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 ban spammer feature."""
 from __future__ import print_function
@@ -10,6 +9,7 @@
 
 import json
 import mock
+import six
 import unittest
 from six.moves import urllib
 
@@ -18,7 +18,7 @@
 from framework import framework_views
 from framework import permissions
 from framework import urls
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
@@ -72,7 +72,7 @@
         'app_engine_http_request':
             {
                 'relative_uri': urls.BAN_SPAMMER_TASK + '.do',
-                'body': urllib.parse.urlencode(params),
+                'body': six.ensure_binary(urllib.parse.urlencode(params)),
                 'headers': {
                     'Content-type': 'application/x-www-form-urlencoded'
                 }
@@ -81,8 +81,8 @@
     get_client_mock().queue_path.assert_called_with(
         settings.app_id, settings.CLOUD_TASKS_REGION, 'default')
     get_client_mock().create_task.assert_called_once()
-    ((_parent, called_task), _kwargs) = get_client_mock().create_task.call_args
-    self.assertEqual(called_task, task)
+    _, kwargs = get_client_mock().create_task.call_args
+    self.assertEqual(kwargs['task'], task)
 
 
 class BanSpammerTaskTest(unittest.TestCase):
@@ -102,9 +102,12 @@
 
   def testProcessFormData_okSomeIssues(self):
     mr = testing_helpers.MakeMonorailRequest(
-        path=urls.BAN_SPAMMER_TASK + '.do', method='POST',
-        params={'spammer_id': 111, 'reporter_id': 222})
-
+        path=urls.BAN_SPAMMER_TASK + '.do',
+        method='POST',
+        params={
+            'spammer_id': 111,
+            'reporter_id': 222
+        })
     for i in range(0, 10):
       issue = fake.MakeTestIssue(
           1, i, 'issue_summary', 'New', 111, project_name='project-name')
diff --git a/features/test/commands_test.py b/features/test/commands_test.py
index e8bc47b..78f29e6 100644
--- a/features/test/commands_test.py
+++ b/features/test/commands_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.
 
 """Classes and functions that implement command-line-like issue updates."""
 from __future__ import print_function
@@ -13,7 +12,7 @@
 
 from features import commands
 from framework import framework_constants
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from services import service_manager
 from testing import fake
 from tracker import tracker_bizobj
diff --git a/features/test/commitlogcommands_test.py b/features/test/commitlogcommands_test.py
index 7e5d566..8131dee 100644
--- a/features/test/commitlogcommands_test.py
+++ b/features/test/commitlogcommands_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.features.commitlogcommands."""
 from __future__ import print_function
@@ -14,7 +13,7 @@
 from features import commitlogcommands
 from features import send_notifications
 from framework import monorailcontext
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
diff --git a/features/test/dateaction_test.py b/features/test/dateaction_test.py
index 8ca5bc3..2b443c6 100644
--- a/features/test/dateaction_test.py
+++ b/features/test/dateaction_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.
 
 """Unittest for the dateaction module."""
 
@@ -20,7 +19,7 @@
 from framework import framework_views
 from framework import timestr
 from framework import urls
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
@@ -86,15 +85,15 @@
         'app_engine_http_request':
             {
                 'relative_uri': urls.ISSUE_DATE_ACTION_TASK + '.do',
-                'body': 'issue_id=78901',
+                'body': b'issue_id=78901',
                 'headers': {
                     'Content-type': 'application/x-www-form-urlencoded'
                 }
             }
     }
     get_client_mock().create_task.assert_any_call(
-        get_client_mock().queue_path(),
-        expected_task,
+        parent=get_client_mock().queue_path(),
+        task=expected_task,
         retry=cloud_tasks_helpers._DEFAULT_RETRY)
 
   @mock.patch('framework.cloud_tasks_helpers._get_client')
@@ -104,15 +103,15 @@
         'app_engine_http_request':
             {
                 'relative_uri': urls.ISSUE_DATE_ACTION_TASK + '.do',
-                'body': 'issue_id=78901',
+                'body': b'issue_id=78901',
                 'headers': {
                     'Content-type': 'application/x-www-form-urlencoded'
                 }
             }
     }
     get_client_mock().create_task.assert_any_call(
-        get_client_mock().queue_path(),
-        expected_task,
+        parent=get_client_mock().queue_path(),
+        task=expected_task,
         retry=cloud_tasks_helpers._DEFAULT_RETRY)
 
 
@@ -290,32 +289,3 @@
     self.assertEqual(1, len(tasks))
     notify_owner_task = tasks[0]
     self.assertEqual('starrer333@example.com', notify_owner_task['to'])
-
-  def testCalculateIssuePings_Normal(self):
-    """Return a ping for an issue that has a date that happened today."""
-    issue = fake.MakeTestIssue(
-        789, 1, 'summary', 'New', 0, issue_id=78901)
-    self.services.issue.TestAddIssue(issue)
-    now = int(time.time())
-    self.SetUpFieldValues(issue, now)
-    issue.project_name = 'proj'
-
-    pings = self.servlet._CalculateIssuePings(issue, self.config)
-
-    self.assertEqual(
-        [(self.config.field_defs[1], now),
-         (self.config.field_defs[0], now)],
-        pings)
-
-  def testCalculateIssuePings_Closed(self):
-    """Don't ping for a closed issue."""
-    issue = fake.MakeTestIssue(
-        789, 1, 'summary', 'Fixed', 0, issue_id=78901)
-    self.services.issue.TestAddIssue(issue)
-    now = int(time.time())
-    self.SetUpFieldValues(issue, now)
-    issue.project_name = 'proj'
-
-    pings = self.servlet._CalculateIssuePings(issue, self.config)
-
-    self.assertEqual([], pings)
diff --git a/features/test/features_bizobj_test.py b/features/test/features_bizobj_test.py
index 1814ae2..309a4b8 100644
--- a/features/test/features_bizobj_test.py
+++ b/features/test/features_bizobj_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 features bizobj functions."""
 from __future__ import print_function
@@ -10,7 +9,7 @@
 
 import unittest
 
-from proto import features_pb2
+from mrproto import features_pb2
 from features import features_bizobj
 from testing import fake
 
diff --git a/features/test/federated_test.py b/features/test/federated_test.py
index 1ba088a..f2a1178 100644
--- a/features/test/federated_test.py
+++ b/features/test/federated_test.py
@@ -1,7 +1,6 @@
-# Copyright 2019 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 2019 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.feature.federated."""
 
diff --git a/features/test/filterrules_helpers_test.py b/features/test/filterrules_helpers_test.py
index a68c279..c220196 100644
--- a/features/test/filterrules_helpers_test.py
+++ b/features/test/filterrules_helpers_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.
 
 """Unit tests for filterrules_helpers feature."""
 from __future__ import print_function
@@ -9,9 +8,9 @@
 from __future__ import absolute_import
 
 import mock
+import six
 import unittest
 from six.moves import urllib
-from six.moves.urllib.parse import parse_qs
 
 import settings
 from features import filterrules_helpers
@@ -19,8 +18,8 @@
 from framework import framework_constants
 from framework import template_helpers
 from framework import urls
-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 service_manager
 from testing import fake
@@ -139,7 +138,7 @@
           'app_engine_http_request':
               {
                   'relative_uri': urls.RECOMPUTE_DERIVED_FIELDS_TASK + '.do',
-                  'body': urllib.parse.urlencode(params),
+                  'body': six.ensure_binary(urllib.parse.urlencode(params)),
                   'headers':
                       {
                           'Content-type': 'application/x-www-form-urlencoded'
@@ -147,7 +146,7 @@
               }
       }
       get_client_mock().create_task.assert_any_call(
-          parent, task, retry=cloud_tasks_helpers._DEFAULT_RETRY)
+          parent=parent, task=task, retry=cloud_tasks_helpers._DEFAULT_RETRY)
       shard_id = (shard_id + 1) % settings.num_logical_shards
 
     settings.recompute_derived_fields_in_worker = saved_flag
@@ -171,27 +170,31 @@
     self.assertEqual(get_client_mock().queue_path.call_count, num_calls)
     self.assertEqual(get_client_mock().create_task.call_count, num_calls)
 
-    ((_parent, called_task),
-     _kwargs) = get_client_mock().create_task.call_args_list[0]
-    relative_uri = called_task.get('app_engine_http_request').get(
+    _, kwargs = get_client_mock().create_task.call_args_list[0]
+    relative_uri = kwargs['task'].get('app_engine_http_request').get(
         'relative_uri')
     self.assertEqual(relative_uri, urls.RECOMPUTE_DERIVED_FIELDS_TASK + '.do')
-    encoded_params = called_task.get('app_engine_http_request').get('body')
-    params = {k: v[0] for k, v in parse_qs(encoded_params).items()}
-    self.assertEqual(params['project_id'], str(self.project.project_id))
+    encoded_params = kwargs['task'].get('app_engine_http_request').get('body')
+    params = {k: v[0] for k, v in urllib.parse.parse_qs(encoded_params).items()}
     self.assertEqual(
-        params['lower_bound'], str(12345 // self.BLOCK * self.BLOCK + 1))
-    self.assertEqual(params['upper_bound'], str(12345))
+        params[b'project_id'],
+        str(self.project.project_id).encode())
+    self.assertEqual(
+        params[b'lower_bound'],
+        str(12345 // self.BLOCK * self.BLOCK + 1).encode())
+    self.assertEqual(params[b'upper_bound'], b'12345')
 
-    ((_parent, called_task), _kwargs) = get_client_mock().create_task.call_args
-    relative_uri = called_task.get('app_engine_http_request').get(
+    _, kwargs = get_client_mock().create_task.call_args
+    relative_uri = kwargs['task'].get('app_engine_http_request').get(
         'relative_uri')
     self.assertEqual(relative_uri, urls.RECOMPUTE_DERIVED_FIELDS_TASK + '.do')
-    encoded_params = called_task.get('app_engine_http_request').get('body')
-    params = {k: v[0] for k, v in parse_qs(encoded_params).items()}
-    self.assertEqual(params['project_id'], str(self.project.project_id))
-    self.assertEqual(params['lower_bound'], str(1))
-    self.assertEqual(params['upper_bound'], str(self.BLOCK + 1))
+    encoded_params = kwargs['task'].get('app_engine_http_request').get('body')
+    params = {k: v[0] for k, v in urllib.parse.parse_qs(encoded_params).items()}
+    self.assertEqual(
+        params[b'project_id'],
+        str(self.project.project_id).encode())
+    self.assertEqual(params[b'lower_bound'], b'1')
+    self.assertEqual(params[b'upper_bound'], str(self.BLOCK + 1).encode())
 
     settings.recompute_derived_fields_in_worker = saved_flag
 
@@ -857,7 +860,7 @@
         tracker_pb2.FilterRule(),
         ]
     actual_user_ids = filterrules_helpers.OwnerCcsInvolvedInFilterRules(rules)
-    self.assertItemsEqual([111, 333, 777, 888, 999], actual_user_ids)
+    six.assertCountEqual(self, [111, 333, 777, 888, 999], actual_user_ids)
 
   def testBuildFilterRuleStrings(self):
     rules = [
@@ -876,8 +879,8 @@
         111: 'cow@test.com', 222: 'fox@test.com', 333: 'llama@test.com'}
     rule_strs = filterrules_helpers.BuildFilterRuleStrings(rules, emails_by_id)
 
-    self.assertItemsEqual(
-        rule_strs, [
+    six.assertCountEqual(
+        self, rule_strs, [
             'if label:machu '
             'then add cc(s): cow@test.com, llama@test.com, user not found',
             'if label:pichu then set default owner: fox@test.com',
@@ -909,19 +912,21 @@
     actual = filterrules_helpers.BuildRedactedFilterRuleStrings(
         self.cnxn, rules_by_project, self.services.user, deleted_emails)
 
-    self.assertItemsEqual(
-        actual,
-        {16: [
-            'if label:machu '
-            'then add cc(s): cow@test.com, llama@test.com, user not found',
-            'if label:pichu '
-            'then set default owner: %s' %
-            framework_constants.DELETED_USER_NAME],
-         19: [
-             'if owner:%s '
-             'then add label(s): cows-farting, chicken, machu-pichu' %
-             framework_constants.DELETED_USER_NAME,
-             'if label:rainforest '
-             'then notify: cake@test.com, %s' %
-             framework_constants.DELETED_USER_NAME],
+    six.assertCountEqual(
+        self, actual, {
+            16: [
+                'if label:machu '
+                'then add cc(s): cow@test.com, llama@test.com, user not found',
+                'if label:pichu '
+                'then set default owner: %s' %
+                framework_constants.DELETED_USER_NAME
+            ],
+            19: [
+                'if owner:%s '
+                'then add label(s): cows-farting, chicken, machu-pichu' %
+                framework_constants.DELETED_USER_NAME,
+                'if label:rainforest '
+                'then notify: cake@test.com, %s' %
+                framework_constants.DELETED_USER_NAME
+            ],
         })
diff --git a/features/test/filterrules_views_test.py b/features/test/filterrules_views_test.py
index 323b6c2..25d068b 100644
--- a/features/test/filterrules_views_test.py
+++ b/features/test/filterrules_views_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.
 
 """Unittest for issue tracker views."""
 from __future__ import print_function
@@ -11,7 +10,7 @@
 import unittest
 
 from features import filterrules_views
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from testing import testing_helpers
 
 
diff --git a/features/test/generate_features_test.py b/features/test/generate_features_test.py
index 8b1664e..b613a81 100644
--- a/features/test/generate_features_test.py
+++ b/features/test/generate_features_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 test for generate_features."""
 from __future__ import print_function
diff --git a/features/test/hotlist_helpers_test.py b/features/test/hotlist_helpers_test.py
index 800a913..c63ad50 100644
--- a/features/test/hotlist_helpers_test.py
+++ b/features/test/hotlist_helpers_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.
 
 """Unit tests for helpers module."""
 from __future__ import print_function
@@ -20,8 +19,8 @@
 from testing import fake
 from tracker import tablecell
 from tracker import tracker_bizobj
-from proto import features_pb2
-from proto import tracker_pb2
+from mrproto import features_pb2
+from mrproto import tracker_pb2
 
 
 class HotlistTableDataTest(unittest.TestCase):
diff --git a/features/test/hotlist_views_test.py b/features/test/hotlist_views_test.py
index 92369ba..91eae87 100644
--- a/features/test/hotlist_views_test.py
+++ b/features/test/hotlist_views_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.
 
 """Unit tests for hotlist_views classes."""
 from __future__ import print_function
@@ -16,7 +15,7 @@
 from framework import permissions
 from services import service_manager
 from testing import fake
-from proto import user_pb2
+from mrproto import user_pb2
 
 
 class MemberViewTest(unittest.TestCase):
diff --git a/features/test/hotlistcreate_test.py b/features/test/hotlistcreate_test.py
index e6cda4b..3229382 100644
--- a/features/test/hotlistcreate_test.py
+++ b/features/test/hotlistcreate_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.
 
 """Unit test for Hotlist creation servlet."""
 from __future__ import print_function
@@ -17,7 +16,7 @@
 import settings
 from framework import permissions
 from features import hotlistcreate
-from proto import site_pb2
+from mrproto import site_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
diff --git a/features/test/hotlistdetails_test.py b/features/test/hotlistdetails_test.py
index 561199c..f4a2f54 100644
--- a/features/test/hotlistdetails_test.py
+++ b/features/test/hotlistdetails_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.
 
 """Unit tests for hotlistdetails page."""
 from __future__ import print_function
@@ -22,7 +21,7 @@
 from features import features_constants
 from services import service_manager
 from features import hotlistdetails
-from proto import features_pb2
+from mrproto import features_pb2
 from testing import fake
 from testing import testing_helpers
 
@@ -35,8 +34,7 @@
     self.user_2 = self.user_service.TestAddUser('user2@test.com', 222)
     services = service_manager.Services(
         features=fake.FeaturesService(), user=self.user_service)
-    self.servlet = hotlistdetails.HotlistDetails(
-        'req', 'res', services=services)
+    self.servlet = hotlistdetails.HotlistDetails(services=services)
     self.hotlist = self.servlet.services.features.TestAddHotlist(
         'hotlist', summary='hotlist summary', description='hotlist description',
         owner_ids=[111], editor_ids=[222])
diff --git a/features/test/hotlistissues_test.py b/features/test/hotlistissues_test.py
index 265c9d1..a645edc 100644
--- a/features/test/hotlistissues_test.py
+++ b/features/test/hotlistissues_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.
 
 """Unit tests for issuelist module."""
 from __future__ import print_function
@@ -47,8 +46,7 @@
         features=fake.FeaturesService(),
         cache_manager=fake.CacheManager(),
         hotlist_star=fake.HotlistStarService())
-    self.servlet = hotlistissues.HotlistIssues(
-        'req', 'res', services=self.services)
+    self.servlet = hotlistissues.HotlistIssues(services=self.services)
     self.user1 = self.services.user.TestAddUser('testuser@gmail.com', 111)
     self.user2 = self.services.user.TestAddUser('testuser2@gmail.com', 222, )
     self.services.project.TestAddProject('project-name', project_id=1)
diff --git a/features/test/hotlistissuescsv_test.py b/features/test/hotlistissuescsv_test.py
index afa53d5..f899ce8 100644
--- a/features/test/hotlistissuescsv_test.py
+++ b/features/test/hotlistissuescsv_test.py
@@ -1,18 +1,18 @@
-# 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.
 
 """Unit tests for issuelistcsv module."""
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
 
+import six
 import unittest
 
 from google.appengine.ext import testbed
 
-import webapp2
+import flask
 
 from framework import permissions
 from framework import sorting
@@ -38,8 +38,8 @@
         project=fake.ProjectService(),
         cache_manager=fake.CacheManager(),
         features=fake.FeaturesService())
-    self.servlet = hotlistissuescsv.HotlistIssuesCsv(
-        'req', webapp2.Response(), services=self.services)
+    self.servlet = hotlistissuescsv.HotlistIssuesCsv(services=self.services)
+    self.servlet.response = flask.Response()
     self.user1 = self.services.user.TestAddUser('testuser@gmail.com', 111)
     self.user2 = self.services.user.TestAddUser('testuser2@gmail.com', 222)
     self.services.project.TestAddProject('project-name', project_id=1)
@@ -80,7 +80,7 @@
     for path in ('/u/222/hotlists/MyHotlist',
                  '/u/testuser2@gmail.com/hotlists/MyHotlist'):
       token = 'bad'
-      self._MakeMR(path + '?token=%s' % token)
+      self._MakeMR(path + '?token=%s' % six.ensure_str(token))
       self.mr.auth.user_id = self.user2.user_id
       self.assertRaises(xsrf.TokenIncorrect,
                         self.servlet.GatherPageData, self.mr)
@@ -91,7 +91,7 @@
                  '/u/testuser2@gmail.com/hotlists/MyHotlist'):
       form_token_path = self.servlet._FormHandlerURL(path)
       token = xsrf.GenerateToken(self.user1.user_id, form_token_path)
-      self._MakeMR(path + '?token=%s' % token)
+      self._MakeMR(path + '?token=%s' % six.ensure_str(token))
       self.mr.auth.email = self.user1.email
       self.mr.auth.user_id = self.user1.user_id
       self.servlet.GatherPageData(self.mr)
diff --git a/features/test/hotlistpeople_test.py b/features/test/hotlistpeople_test.py
index 3ee7925..643b1a6 100644
--- a/features/test/hotlistpeople_test.py
+++ b/features/test/hotlistpeople_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.
 
 """Unittest for Hotlist People servlet."""
 from __future__ import print_function
@@ -39,8 +38,7 @@
         'PrivateHotlist', 'owner only', [111], [222], is_private=True)
     self.public_hotlist = self.services.features.TestAddHotlist(
         'PublicHotlist', 'everyone', [111], [222], is_private=False)
-    self.servlet = hotlistpeople.HotlistPeopleList(
-        'req', 'res', services=self.services)
+    self.servlet = hotlistpeople.HotlistPeopleList(services=self.services)
     self.mox = mox.Mox()
 
   def tearDown(self):
diff --git a/features/test/inboundemail_test.py b/features/test/inboundemail_test.py
index de05749..cab9d0a 100644
--- a/features/test/inboundemail_test.py
+++ b/features/test/inboundemail_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.feature.inboundemail."""
 from __future__ import print_function
@@ -27,9 +26,9 @@
 from framework import emailfmt
 from framework import monorailcontext
 from framework import permissions
-from proto import project_pb2
-from proto import tracker_pb2
-from proto import user_pb2
+from mrproto import project_pb2
+from mrproto import tracker_pb2
+from mrproto import user_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
diff --git a/features/test/notify_helpers_test.py b/features/test/notify_helpers_test.py
index 615da38..c1ebe83 100644
--- a/features/test/notify_helpers_test.py
+++ b/features/test/notify_helpers_test.py
@@ -1,8 +1,7 @@
 # -*- coding: utf-8 -*-
-# 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 notify_helpers.py."""
 from __future__ import print_function
@@ -11,6 +10,7 @@
 
 import json
 import mock
+import six
 import unittest
 import os
 
@@ -20,7 +20,7 @@
 from framework import emailfmt
 from framework import framework_views
 from framework import urls
-from proto import user_pb2
+from mrproto import user_pb2
 from services import service_manager
 from testing import fake
 
@@ -46,7 +46,7 @@
     self.assertEqual(queue, features_constants.QUEUE_OUTBOUND_EMAIL)
 
     task_call_args = get_client_mock().create_task.call_args_list
-    ((_parent, task), _kwargs) = task_call_args[0]
+    _, kwargs = task_call_args[0]
     expected_task = {
         'app_engine_http_request':
             {
@@ -59,8 +59,8 @@
                 }
             }
     }
-    self.assertEqual(task, expected_task)
-    ((_parent, task), _kwargs) = task_call_args[1]
+    self.assertEqual(kwargs['task'], expected_task)
+    _, kwargs = task_call_args[1]
     expected_task = {
         'app_engine_http_request':
             {
@@ -73,7 +73,7 @@
                 }
             }
     }
-    self.assertEqual(task, expected_task)
+    self.assertEqual(kwargs['task'], expected_task)
 
 
 class MergeLinkedAccountReasonsTest(unittest.TestCase):
@@ -366,9 +366,12 @@
 
     expected_html_body = (
         notify_helpers.HTML_BODY_WITH_GMAIL_ACTION_TEMPLATE % {
-            'url': self.detail_url,
-            'body': '%s-- <br/>%s' % (unicode_content.decode('utf-8'),
-                                      self.expected_html_footer)})
+            'url':
+                self.detail_url,
+            'body':
+                '%s-- <br/>%s' %
+                (six.ensure_text(unicode_content), self.expected_html_footer)
+        })
     self.assertEqual(expected_html_body, email_task['html_body'])
 
   def testHtmlBody_WithLinks(self):
@@ -442,7 +445,7 @@
 
     escaped_body_with_html_content = (
         '&lt;a href=&quot;http://www.google.com&quot;&gt;test&lt;/a&gt; '
-        '&#39;something&#39;')
+        '&#x27;something&#x27;')
     notify_helpers._MakeNotificationFooter(
         ['reason'], REPLY_NOT_ALLOWED, 'example.com')
     expected_html_body = (
diff --git a/features/test/notify_reasons_test.py b/features/test/notify_reasons_test.py
index 559e322..b946bc9 100644
--- a/features/test/notify_reasons_test.py
+++ b/features/test/notify_reasons_test.py
@@ -1,7 +1,6 @@
-# Copyright 2017 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 2017 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 notify_reasons.py."""
 from __future__ import print_function
@@ -15,8 +14,8 @@
 from framework import emailfmt
 from framework import framework_views
 from framework import urls
-from proto import user_pb2
-from proto import usergroup_pb2
+from mrproto import user_pb2
+from mrproto import usergroup_pb2
 from services import service_manager
 from testing import fake
 from tracker import tracker_bizobj
diff --git a/features/test/notify_test.py b/features/test/notify_test.py
index e73488d..e4f00a3 100644
--- a/features/test/notify_test.py
+++ b/features/test/notify_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 notify.py."""
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
 
+import flask
 import json
 import mock
+import six
 import unittest
-import flask
 
 from google.appengine.ext import testbed
 
@@ -19,7 +19,7 @@
 from features import notify_reasons
 from framework import emailfmt
 from framework import urls
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
@@ -131,7 +131,7 @@
     task = notify.NotifyBlockingChangeTask(services=self.services)
     params = {
         'send_email': 1, 'issue_id': issue2.issue_id, 'seq': 0,
-        'delta_blocker_iids': self.issue1.issue_id, 'commenter_id': 1,
+        'delta_blocker_iids': str(self.issue1.issue_id), 'commenter_id': 1,
         'hostport': 'bugs.chromium.org'}
     mr = testing_helpers.MakeMonorailRequest(
         user_info={'user_id': 1},
@@ -149,7 +149,7 @@
     task = notify.NotifyBlockingChangeTask(services=self.services)
     params = {
         'send_email': 1, 'issue_id': issue2.issue_id, 'seq': 0,
-        'delta_blocker_iids': self.issue1.issue_id, 'commenter_id': 1}
+        'delta_blocker_iids': str(self.issue1.issue_id), 'commenter_id': 1}
     mr = testing_helpers.MakeMonorailRequest(
         user_info={'user_id': 1},
         params=params,
@@ -213,7 +213,8 @@
         create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do')
     self.assertEqual(3, len(call_args_list))
 
-    self.assertItemsEqual(
+    six.assertCountEqual(
+        self,
         ['user@example.com', 'mailing-list@example.com', 'member@example.com'],
         result['notified'])
     for (args, _kwargs) in call_args_list:
@@ -247,8 +248,8 @@
         create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do')
     self.assertEqual(2, len(call_args_list))
 
-    self.assertItemsEqual(
-        ['user@example.com', 'mailing-list@example.com'],
+    six.assertCountEqual(
+        self, ['user@example.com', 'mailing-list@example.com'],
         result['notified'])
 
     for (args, _kwargs) in call_args_list:
@@ -532,12 +533,13 @@
     self.assertTrue('sploot.jpg' in result['tasks'][0]['body'])
     self.assertTrue(
         '/issues/attachment?aid=4567' in result['tasks'][0]['body'])
-    self.assertItemsEqual(
-        ['user@example.com', 'approver_old@example.com',
-         'approver_new@example.com', 'TL@example.com',
-         'approvalTL@example.com', 'group_mem1@example.com',
-         'group_mem2@example.com', 'group_mem3@example.com'],
-        result['notified'])
+    six.assertCountEqual(
+        self, [
+            'user@example.com', 'approver_old@example.com',
+            'approver_new@example.com', 'TL@example.com',
+            'approvalTL@example.com', 'group_mem1@example.com',
+            'group_mem2@example.com', 'group_mem3@example.com'
+        ], result['notified'])
 
     # Test no approvers/groups notified
     # Status change to NEED_INFO does not email approvers.
@@ -564,8 +566,8 @@
     self.assertIsNotNone(result['tasks'][0].get('references'))
     self.assertEqual(result['tasks'][0]['reply_to'], emailfmt.NoReplyAddress())
     self.assertTrue('Status: need_info' in result['tasks'][0]['body'])
-    self.assertItemsEqual(
-        ['user@example.com', 'TL@example.com', 'approvalTL@example.com'],
+    six.assertCountEqual(
+        self, ['user@example.com', 'TL@example.com', 'approvalTL@example.com'],
         result['notified'])
 
   def testNotifyApprovalChangeTask_GetApprovalEmailRecipients(self):
@@ -580,7 +582,7 @@
     # Comment with not amendments notifies everyone.
     rids = task._GetApprovalEmailRecipients(
         approval_value, comment, issue, [777, 888])
-    self.assertItemsEqual(rids, [111, 222, 333, 777, 888])
+    six.assertCountEqual(self, rids, [111, 222, 333, 777, 888])
 
     # New APPROVED status notifies owners and any_comment users.
     amendment = tracker_bizobj.MakeApprovalStatusAmendment(
@@ -588,7 +590,7 @@
     comment.amendments = [amendment]
     rids = task._GetApprovalEmailRecipients(
         approval_value, comment, issue, [777, 888])
-    self.assertItemsEqual(rids, [111, 777, 888])
+    six.assertCountEqual(self, rids, [111, 777, 888])
 
     # New REVIEW_REQUESTED status notifies approvers.
     approval_value.status = tracker_pb2.ApprovalStatus.REVIEW_REQUESTED
@@ -597,7 +599,7 @@
     comment.amendments = [amendment]
     rids = task._GetApprovalEmailRecipients(
         approval_value, comment, issue, [777, 888])
-    self.assertItemsEqual(rids, [222, 333])
+    six.assertCountEqual(self, rids, [222, 333])
 
     # Approvers change notifies everyone.
     amendment = tracker_bizobj.MakeApprovalApproversAmendment(
@@ -606,7 +608,7 @@
     approval_value.approver_ids = [222]
     rids = task._GetApprovalEmailRecipients(
         approval_value, comment, issue, [777], omit_ids=[444, 333])
-    self.assertItemsEqual(rids, [111, 222, 555, 777])
+    six.assertCountEqual(self, rids, [111, 222, 555, 777])
 
   @mock.patch('framework.cloud_tasks_helpers.create_task')
   def testNotifyRulesDeletedTask(self, _create_task_mock):
@@ -627,8 +629,8 @@
     self.assertTrue('if green make yellow' in body)
     self.assertTrue('if green make yellow' in body)
     self.assertTrue('/p/proj/adminRules' in body)
-    self.assertItemsEqual(
-        ['cow@test.com', 'owner1@test.com'], result['notified'])
+    six.assertCountEqual(
+        self, ['cow@test.com', 'owner1@test.com'], result['notified'])
 
   def testOutboundEmailTask_Normal(self):
     """We can send an email."""
@@ -656,7 +658,7 @@
     res_json = json.loads(res_string)
     self.assertEqual(
         'Skipping because no "to" address found.', res_json['note'])
-    self.assertNotIn('from_addr', res_string)
+    self.assertNotIn(b'from_addr', res_string)
 
   def testOutboundEmailTask_BannedUser(self):
     """We don't send emails to banned users.."""
@@ -672,4 +674,4 @@
     res_string = res.get_data()[5:]
     res_json = json.loads(res_string)
     self.assertEqual('Skipping because user is banned.', res_json['note'])
-    self.assertNotIn('from_addr', res_string)
+    self.assertNotIn(b'from_addr', res_string)
diff --git a/features/test/prettify_test.py b/features/test/prettify_test.py
index 07fce43..b01e7d1 100644
--- a/features/test/prettify_test.py
+++ b/features/test/prettify_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.
 
 """Unittest for the prettify module."""
 from __future__ import print_function
diff --git a/features/test/pubsub_test.py b/features/test/pubsub_test.py
index e86230c..a84cfb4 100644
--- a/features/test/pubsub_test.py
+++ b/features/test/pubsub_test.py
@@ -1,7 +1,6 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is govered by a BSD-style
-# license that can be found in the LICENSE file or at
-# https://developers.google.com/open-source/licenses/bsd
+# Copyright 2019 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 features.pubsub."""
 
diff --git a/features/test/savedqueries_helpers_test.py b/features/test/savedqueries_helpers_test.py
index 7f5ad47..ccda35d 100644
--- a/features/test/savedqueries_helpers_test.py
+++ b/features/test/savedqueries_helpers_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.
 
 """Unit tests for savedqueries_helpers feature."""
 from __future__ import print_function
diff --git a/features/test/savedqueries_test.py b/features/test/savedqueries_test.py
index 08624a2..5bde7c6 100644
--- a/features/test/savedqueries_test.py
+++ b/features/test/savedqueries_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.
 
 """Unit tests for savedqueries feature."""
 from __future__ import print_function
@@ -22,8 +21,7 @@
   def setUp(self):
     self.services = service_manager.Services(
         user=fake.UserService())
-    self.servlet = savedqueries.SavedQueries(
-        'req', 'res', services=self.services)
+    self.servlet = savedqueries.SavedQueries(services=self.services)
     self.services.user.TestAddUser('a@example.com', 111)
 
   def testAssertBasePermission(self):
diff --git a/features/test/send_notifications_test.py b/features/test/send_notifications_test.py
index b15fb23..b60130f 100644
--- a/features/test/send_notifications_test.py
+++ b/features/test/send_notifications_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 prepareandsend.py"""
 from __future__ import print_function
@@ -96,8 +95,8 @@
         create_task_mock, urls.NOTIFY_BULK_CHANGE_TASK + '.do')
     self.assertEqual(1, len(call_args_list))
     _path, params = self._get_create_task_path_and_params(call_args_list[0])
-    self.assertEqual(params['comment_text'], 'comment')
-    self.assertEqual(params['amendments'], '')
+    self.assertEqual(params[b'comment_text'], b'comment')
+    self.assertEqual(params[b'amendments'], b'')
 
   @mock.patch('framework.cloud_tasks_helpers.create_task')
   def testSendIssueBulkChangeNotification_Normal(self, create_task_mock):
@@ -119,10 +118,10 @@
         create_task_mock, urls.NOTIFY_BULK_CHANGE_TASK + '.do')
     self.assertEqual(1, len(call_args_list))
     _path, params = self._get_create_task_path_and_params(call_args_list[0])
-    self.assertEqual(params['comment_text'], 'comment')
+    self.assertEqual(params[b'comment_text'], b'comment')
     self.assertEqual(
-        params['amendments'].split('\n'),
-        ['    Status: New', '    Labels: -Removed Added'])
+        params[b'amendments'].split(b'\n'),
+        [b'    Status: New', b'    Labels: -Removed Added'])
 
   @mock.patch('framework.cloud_tasks_helpers.create_task')
   def testPrepareAndSendDeletedFilterRulesNotifications(self, create_task_mock):
@@ -134,6 +133,6 @@
         create_task_mock, urls.NOTIFY_RULES_DELETED_TASK + '.do')
     self.assertEqual(1, len(call_args_list))
     _path, params = self._get_create_task_path_and_params(call_args_list[0])
-    self.assertEqual(params['project_id'], '789')
+    self.assertEqual(params[b'project_id'], b'789')
     self.assertEqual(
-        params['filter_rules'], 'if yellow make orange,if orange make blue')
+        params[b'filter_rules'], b'if yellow make orange,if orange make blue')