Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/features/activities.py b/features/activities.py
index 35c6a64..cc8a728 100644
--- a/features/activities.py
+++ b/features/activities.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.
 
 """Code to support project and user activies pages."""
 from __future__ import print_function
@@ -20,7 +19,7 @@
 from framework import template_helpers
 from framework import timestr
 from project import project_views
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from tracker import tracker_helpers
 from tracker import tracker_views
 
@@ -93,7 +92,7 @@
       self.project = project_views.ProjectView(project)
 
     else:
-      logging.warn('unknown activity object %r', pb)
+      logging.warning('unknown activity object %r', pb)
 
     nested_page_data = {
         'activity_type': activity_type,
diff --git a/features/alert2issue.py b/features/alert2issue.py
index daf72ca..6ecf9b0 100644
--- a/features/alert2issue.py
+++ b/features/alert2issue.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.
 
 """Handlers to process alert notification messages."""
 from __future__ import print_function
@@ -238,9 +237,11 @@
   if not cc_emails:
     return []
   emails = [addr for _, addr in email.utils.getaddresses([cc_emails])]
-  return [userID for _, userID
-          in user_svc.LookupExistingUserIDs(cnxn, emails).iteritems()
-          if userID is not None]
+  return [
+      userID
+      for _, userID in user_svc.LookupExistingUserIDs(cnxn, emails).items()
+      if userID is not None
+  ]
 
 
 def _GetPriority(known_labels, priority):
diff --git a/features/autolink.py b/features/autolink.py
index 67c898a..9d9611f 100644
--- a/features/autolink.py
+++ b/features/autolink.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.
 
 """Autolink helps auto-link references to artifacts in text.
 
@@ -40,7 +39,7 @@
 from features import autolink_constants
 from framework import template_helpers
 from framework import validate
-from proto import project_pb2
+from mrproto import project_pb2
 from tracker import tracker_helpers
 
 
diff --git a/features/autolink_constants.py b/features/autolink_constants.py
index ddb9bb3..d6239d6 100644
--- a/features/autolink_constants.py
+++ b/features/autolink_constants.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.
 
 """Some constants of regexes used in Monorail to validate urls and emails."""
 from __future__ import print_function
diff --git a/features/banspammer.py b/features/banspammer.py
index fd28045..e0c00c8 100644
--- a/features/banspammer.py
+++ b/features/banspammer.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 for banning spammer users"""
 from __future__ import print_function
@@ -12,14 +11,14 @@
 import time
 
 from framework import cloud_tasks_helpers
-from framework import flaskservlet
 from framework import framework_helpers
 from framework import permissions
 from framework import jsonfeed
+from framework import servlet
 from framework import urls
 
 
-class BanSpammer(flaskservlet.FlaskServlet):
+class BanSpammer(servlet.Servlet):
   """Ban a user and mark their content as spam"""
 
   def AssertBasePermission(self, mr):
@@ -60,7 +59,7 @@
     return self.handler(**kwargs)
 
 
-class BanSpammerTask(jsonfeed.FlaskInternalTask):
+class BanSpammerTask(jsonfeed.InternalTask):
   """This task will update all of the comments and issues created by the
      target user with is_spam=True, and also add a manual verdict attached
      to the user who originated the ban request. This is a potentially long
diff --git a/features/commands.py b/features/commands.py
index 3ba376e..880d126 100644
--- a/features/commands.py
+++ b/features/commands.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
diff --git a/features/commitlogcommands.py b/features/commitlogcommands.py
index f570ae3..a48784c 100644
--- a/features/commitlogcommands.py
+++ b/features/commitlogcommands.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.
 
 """Implements processing of issue update command lines.
 
@@ -27,7 +26,7 @@
 from framework import framework_bizobj
 from framework import framework_helpers
 from framework import permissions
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 
 
 # Actions have separate 'Parse' and 'Run' implementations to allow better
diff --git a/features/dateaction.py b/features/dateaction.py
index 0cb3987..bd64d02 100644
--- a/features/dateaction.py
+++ b/features/dateaction.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.
 
 """Cron and task handlers for email notifications of issue date value arrival.
 
@@ -31,16 +30,15 @@
 from framework import permissions
 from framework import timestr
 from framework import urls
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from tracker import tracker_bizobj
-from tracker import tracker_helpers
 from tracker import tracker_views
 
 
 TEMPLATE_PATH = framework_constants.TEMPLATE_PATH
 
 
-class DateActionCron(jsonfeed.FlaskInternalTask):
+class DateActionCron(jsonfeed.InternalTask):
   """Find and process issues with date-type values that arrived today."""
 
   def HandleRequest(self, mr):
@@ -213,14 +211,6 @@
       if (field.field_id in arrived_dates_by_field_id and
           field.date_action in (tracker_pb2.DateAction.PING_OWNER_ONLY,
                                 tracker_pb2.DateAction.PING_PARTICIPANTS))]
-
-    # TODO(jrobbins): For now, assume all pings apply only to open issues.
-    # Later, allow each date action to specify whether it applies to open
-    # issues or all issues.
-    means_open = tracker_helpers.MeansOpenInProject(
-        tracker_bizobj.GetStatus(issue), config)
-    pings = [ping for ping in pings if means_open]
-
     pings = sorted(pings, key=lambda ping: ping[0].field_name)
     return pings
 
diff --git a/features/features_bizobj.py b/features/features_bizobj.py
index 804e6a4..3cba32e 100644
--- a/features/features_bizobj.py
+++ b/features/features_bizobj.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.
 
 """Business objects for the Monorail features.
 
@@ -16,7 +15,7 @@
 
 from framework import framework_bizobj
 from framework import urls
-from proto import features_pb2
+from mrproto import features_pb2
 
 
 def GetOwnerIds(hotlist):
diff --git a/features/features_constants.py b/features/features_constants.py
index b21a7f5..8427d26 100644
--- a/features/features_constants.py
+++ b/features/features_constants.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.
 
 """Some constants used in Monorail hotlist pages."""
 from __future__ import print_function
@@ -16,7 +15,7 @@
 OTHER_BUILT_IN_COLS = (
     tracker_constants.OTHER_BUILT_IN_COLS + ['Adder', 'Added', 'Note'])
 # pylint: disable=line-too-long
-ISSUE_INPUT_REGEX = '%s:\d+(([,]|\s)+%s:\d+)*' % (
+ISSUE_INPUT_REGEX = r'%s:\d+(([,]|\s)+%s:\d+)*' % (
     project_constants.PROJECT_NAME_PATTERN,
     project_constants.PROJECT_NAME_PATTERN)
 FIELD_DEF_NAME_PATTERN = '[a-zA-Z]([_-]?[a-zA-Z0-9])*'
diff --git a/features/federated.py b/features/federated.py
index 2e4486a..f595469 100644
--- a/features/federated.py
+++ b/features/federated.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.
 
 """Logic for storing and representing issues from external trackers."""
 
diff --git a/features/filterrules.py b/features/filterrules.py
index 119c1b3..1f4945c 100644
--- a/features/filterrules.py
+++ b/features/filterrules.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.
 
 """Implementation of the filter rules feature."""
 from __future__ import print_function
@@ -15,7 +14,7 @@
 from tracker import tracker_constants
 
 
-class RecomputeDerivedFieldsTask(jsonfeed.FlaskInternalTask):
+class RecomputeDerivedFieldsTask(jsonfeed.InternalTask):
   """JSON servlet that recomputes derived fields on a batch of issues."""
 
   def HandleRequest(self, mr):
@@ -39,7 +38,7 @@
     return self.handler(**kwargs)
 
 
-class ReindexQueueCron(jsonfeed.FlaskInternalTask):
+class ReindexQueueCron(jsonfeed.InternalTask):
   """JSON servlet that reindexes some issues each minute, as needed."""
 
   def HandleRequest(self, mr):
diff --git a/features/filterrules_helpers.py b/features/filterrules_helpers.py
index 22acc7d..9d38b67 100644
--- a/features/filterrules_helpers.py
+++ b/features/filterrules_helpers.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.
 
 """Implementation of the filter rules helper functions."""
 from __future__ import print_function
@@ -22,8 +21,8 @@
 from framework import framework_constants
 from framework import urls
 from framework import validate
-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 search import searchpipeline
 from tracker import component_helpers
@@ -35,7 +34,7 @@
 # Maximum number of filer rules that can be specified in a given
 # project.  This helps us bound the amount of time needed to
 # (re)compute derived fields.
-MAX_RULES = 200
+MAX_RULES = 250
 
 BLOCK = tracker_constants.RECOMPUTE_DERIVED_FIELDS_BLOCK_SIZE
 
@@ -730,7 +729,7 @@
 
   elif action_type == 'add_ccs':
     cc_ids = []
-    for email in re.split('[,;\s]+', action_value):
+    for email in re.split(r'[,;\s]+', action_value):
       if not email.strip():
         continue
       try:
@@ -749,7 +748,7 @@
 
   elif action_type == 'also_notify':
     add_notify = []
-    for addr in re.split('[,;\s]+', action_value):
+    for addr in re.split(r'[,;\s]+', action_value):
       if validate.IsValidEmail(addr.strip()):
         add_notify.append(addr.strip())
       else:
diff --git a/features/filterrules_views.py b/features/filterrules_views.py
index 75fb425..5b268fb 100644
--- a/features/filterrules_views.py
+++ b/features/filterrules_views.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 to display filter rules in templates."""
 from __future__ import print_function
diff --git a/features/generate_dataset.py b/features/generate_dataset.py
index b13ae88..b897444 100644
--- a/features/generate_dataset.py
+++ b/features/generate_dataset.py
@@ -1,3 +1,6 @@
+# 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.
 """This module is used to go from raw data to a csv dataset to build models for
    component prediction.
 """
@@ -16,7 +19,6 @@
 import os
 import settings
 from framework import sql
-from framework import servlet
 
 if not settings.unit_test_mode:
   import MySQLdb as mdb
@@ -117,7 +119,7 @@
   pretty_issue = text.lower().strip()
 
   quoteless_issue = re.sub('\'', '', pretty_issue)
-  no_punctuation_issue = re.sub('[^\w\s]|_+', ' ', quoteless_issue)
+  no_punctuation_issue = re.sub(r'[^\w\s]|_+', ' ', quoteless_issue)
   one_space_issue = ' '.join(no_punctuation_issue.split())
 
   return one_space_issue
diff --git a/features/hotlist_helpers.py b/features/hotlist_helpers.py
index f23f72e..1c4dcbf 100644
--- a/features/hotlist_helpers.py
+++ b/features/hotlist_helpers.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 by the hotlist pages."""
 from __future__ import print_function
@@ -333,36 +332,35 @@
 
 
 def GetURLOfHotlist(cnxn, hotlist, user_service, url_for_token=False):
-    """Determines the url to be used to access the given hotlist.
+  """Determines the url to be used to access the given hotlist.
 
-    Args:
-      cnxn: connection to SQL database
-      hotlist: the hotlist_pb
-      user_service: interface to user data storage
-      url_for_token: if true, url returned will use user's id
-        regardless of their user settings, for tokenization.
+  Args:
+    cnxn: connection to SQL database
+    hotlist: the hotlist_pb
+    user_service: interface to user data storage
+    url_for_token: if true, url returned will use user's id
+      regardless of their user settings, for tokenization.
 
-    Returns:
-      The string url to be used when accessing this hotlist.
-    """
-    if not hotlist.owner_ids:  # Should never happen.
-      logging.error('Unowned Hotlist: id:%r, name:%r', hotlist.hotlist_id,
-                                                       hotlist.name)
-      return ''
-    owner_id = hotlist.owner_ids[0]  # only one owner allowed
-    owner = user_service.GetUser(cnxn, owner_id)
-    if owner.obscure_email or url_for_token:
-      return '/u/%d/hotlists/%s' % (owner_id, hotlist.name)
-    return (
-        '/u/%s/hotlists/%s' % (
-            owner.email, hotlist.name))
+  Returns:
+    The string url to be used when accessing this hotlist.
+  """
+  if not hotlist.owner_ids:  # Should never happen.
+    logging.error(
+        'Unowned Hotlist: id:%r, name:%r', hotlist.hotlist_id, hotlist.name)
+    return ''
+  owner_id = hotlist.owner_ids[0]  # only one owner allowed
+  owner = user_service.GetUser(cnxn, owner_id)
+  if owner.obscure_email or url_for_token:
+    return '/u/%d/hotlists/%s' % (owner_id, hotlist.name)
+  return ('/u/%s/hotlists/%s' % (owner.email, hotlist.name))
 
 
 def RemoveHotlist(cnxn, hotlist_id, services):
   """Removes the given hotlist from the database.
-    Args:
-      hotlist_id: the id of the hotlist to be removed.
-      services: interfaces to data storage.
+
+  Args:
+    hotlist_id: the id of the hotlist to be removed.
+    services: interfaces to data storage.
   """
   services.hotlist_star.ExpungeStars(cnxn, hotlist_id)
   services.user.ExpungeHotlistsFromHistory(cnxn, [hotlist_id])
diff --git a/features/hotlist_views.py b/features/hotlist_views.py
index 8b17bbb..0c8eb0b 100644
--- a/features/hotlist_views.py
+++ b/features/hotlist_views.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 to display hotlists in templates."""
 from __future__ import print_function
diff --git a/features/hotlistcreate.py b/features/hotlistcreate.py
index 6913c19..4b28dd6 100644
--- a/features/hotlistcreate.py
+++ b/features/hotlistcreate.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 for creating new hotlists."""
 from __future__ import print_function
@@ -14,7 +13,6 @@
 
 from features import hotlist_helpers
 from framework import exceptions
-from framework import flaskservlet
 from framework import framework_bizobj
 from framework import framework_helpers
 from framework import permissions
@@ -30,7 +28,7 @@
 _MSG_INVALID_MEMBERS_INPUT = 'One or more editor emails is not valid.'
 
 
-class HotlistCreate(flaskservlet.FlaskServlet):
+class HotlistCreate(servlet.Servlet):
   """HotlistCreate shows a simple page with a form to create a hotlist."""
 
   _PAGE_TEMPLATE = 'features/hotlist-create-page.ezt'
diff --git a/features/hotlistdetails.py b/features/hotlistdetails.py
index f9c0435..5548362 100644
--- a/features/hotlistdetails.py
+++ b/features/hotlistdetails.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.
 
 """Servlets for hotlist details main subtab."""
 from __future__ import print_function
@@ -14,10 +13,9 @@
 
 from features import hotlist_helpers
 from framework import framework_bizobj
-from framework import flaskservlet
 from framework import framework_helpers
-from framework import servlet
 from framework import permissions
+from framework import servlet
 from framework import urls
 
 _MSG_DESCRIPTION_MISSING = 'Description is missing.'
@@ -33,7 +31,7 @@
   """A page with hotlist details and editing options."""
 
   _PAGE_TEMPLATE = 'features/hotlist-details-page.ezt'
-  _MAIN_TAB_MODE = flaskservlet.FlaskServlet.HOTLIST_TAB_DETAILS
+  _MAIN_TAB_MODE = servlet.Servlet.HOTLIST_TAB_DETAILS
 
   def AssertBasePermission(self, mr):
     super(HotlistDetails, self).AssertBasePermission(mr)
@@ -123,8 +121,8 @@
       default_col_spec = post_data['default_col_spec']
     return summary, description, name, default_col_spec
 
-  # def GetHotlistDetailsPage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def GetHotlistDetailsPage(self, **kwargs):
+    return self.handler(**kwargs)
 
-  # def PostHotlistDetailsPage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def PostHotlistDetailsPage(self, **kwargs):
+    return self.handler(**kwargs)
diff --git a/features/hotlistissues.py b/features/hotlistissues.py
index 78ba007..e5b491c 100644
--- a/features/hotlistissues.py
+++ b/features/hotlistissues.py
@@ -1,14 +1,12 @@
-# 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 that implement the hotlistissues page and related forms."""
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
 
-import logging
 import ezt
 
 import settings
@@ -20,15 +18,13 @@
 from features import features_constants
 from features import hotlist_helpers
 from framework import exceptions
-from framework import flaskservlet
-from framework import servlet
-from framework import sorting
-from framework import permissions
 from framework import framework_helpers
-from framework import paginate
-from framework import framework_constants
 from framework import framework_views
 from framework import grid_view_helpers
+from framework import paginate
+from framework import permissions
+from framework import servlet
+from framework import sorting
 from framework import template_helpers
 from framework import timestr
 from framework import urls
@@ -47,7 +43,7 @@
   """HotlistIssues is a page that shows the issues of one hotlist."""
 
   _PAGE_TEMPLATE = 'features/hotlist-issues-page.ezt'
-  _MAIN_TAB_MODE = flaskservlet.FlaskServlet.HOTLIST_TAB_ISSUES
+  _MAIN_TAB_MODE = servlet.Servlet.HOTLIST_TAB_ISSUES
 
   def AssertBasePermission(self, mr):
     """Check that the user has permission to even visit this page."""
@@ -349,8 +345,8 @@
 
     return grid_view_data
 
-  # def GetHotlistIssuesPage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def GetHotlistIssuesPage(self, **kwargs):
+    return self.handler(**kwargs)
 
-  # def PostHotlistIssuesPage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def PostHotlistIssuesPage(self, **kwargs):
+    return self.handler(**kwargs)
diff --git a/features/hotlistissuescsv.py b/features/hotlistissuescsv.py
index 2b35dad..04fdb52 100644
--- a/features/hotlistissuescsv.py
+++ b/features/hotlistissuescsv.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.
 
 """Implemention of the hotlist issues list output as a CSV file."""
 from __future__ import print_function
@@ -61,5 +60,5 @@
     return csv_helpers.ReformatRowsForCSV(
         mr, page_data, '%d/csv' % mr.hotlist_id)
 
-  # def GetHotlistIssuesCsvPage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def GetHotlistIssuesCsvPage(self, **kwargs):
+    return self.handler(**kwargs)
diff --git a/features/hotlistpeople.py b/features/hotlistpeople.py
index c574469..7bac917 100644
--- a/features/hotlistpeople.py
+++ b/features/hotlistpeople.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 to implement the hotlistpeople page and related forms."""
 from __future__ import print_function
@@ -16,7 +15,6 @@
 from features import hotlist_helpers
 from features import hotlist_views
 from framework import framework_helpers
-from framework import flaskservlet
 from framework import framework_views
 from framework import paginate
 from framework import permissions
@@ -31,7 +29,7 @@
   _PAGE_TEMPLATE = 'project/people-list-page.ezt'
   # Note: using the project's peoplelist page template. minor edits were
   # to make it compatible with HotlistPeopleList
-  _MAIN_TAB_MODE = flaskservlet.FlaskServlet.HOTLIST_TAB_PEOPLE
+  _MAIN_TAB_MODE = servlet.Servlet.HOTLIST_TAB_PEOPLE
 
   def AssertBasePermission(self, mr):
     super(HotlistPeopleList, self).AssertBasePermission(mr)
@@ -219,9 +217,7 @@
 
   def ProcessRemoveMembers(self, mr, post_data, hotlist_url):
     """Process the user's request to remove members."""
-    #TODO: convert for flask
-    #remove_strs = post_data.getlist('remove')
-    remove_strs = post_data.getall('remove')
+    remove_strs = post_data.getlist('remove')
     logging.info('remove_strs = %r', remove_strs)
     remove_ids = set(
         self.services.user.LookupUserIDs(mr.cnxn, remove_strs).values())
@@ -254,8 +250,8 @@
               hotlist_url, urls.HOTLIST_PEOPLE),
           saved=1, ts=int(time.time()), include_project=False)
 
-  # def GetHotlistPeoplePage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def GetHotlistPeoplePage(self, **kwargs):
+    return self.handler(**kwargs)
 
-  # def PostHotlistPeoplePage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def PostHotlistPeoplePage(self, **kwargs):
+    return self.handler(**kwargs)
diff --git a/features/inboundemail.py b/features/inboundemail.py
index d9b2c37..df392a8 100644
--- a/features/inboundemail.py
+++ b/features/inboundemail.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.
 
 """Handler to process inbound email with issue comments and commands."""
 from __future__ import print_function
@@ -38,7 +37,7 @@
 from framework import permissions
 from framework import sql
 from framework import template_helpers
-from proto import project_pb2
+from mrproto import project_pb2
 
 
 TEMPLATE_PATH_BASE = framework_constants.TEMPLATE_PATH
diff --git a/features/notify.py b/features/notify.py
index 230cbf5..1217004 100644
--- a/features/notify.py
+++ b/features/notify.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.
 
 """Task handlers for email notifications of issue changes.
 
@@ -44,7 +43,7 @@
 from tracker import tracker_bizobj
 from tracker import tracker_helpers
 from tracker import tracker_views
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 
 
 class NotifyIssueChangeTask(notify_helpers.NotifyTaskBase):
@@ -983,7 +982,7 @@
     return self.handler(**kwargs)
 
 
-class OutboundEmailTask(jsonfeed.FlaskInternalTask):
+class OutboundEmailTask(jsonfeed.InternalTask):
   """JSON servlet that sends one email.
 
   Handles tasks enqueued from notify_helpers._EnqueueOutboundEmail.
diff --git a/features/notify_helpers.py b/features/notify_helpers.py
index f22ed0e..101ba2b 100644
--- a/features/notify_helpers.py
+++ b/features/notify_helpers.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 email notifications of issue changes."""
 from __future__ import print_function
@@ -31,7 +30,7 @@
 from framework import permissions
 from framework import template_helpers
 from framework import urls
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from search import query2ast
 from search import searchpipeline
 from tracker import tracker_bizobj
@@ -123,7 +122,7 @@
   return notified
 
 
-class NotifyTaskBase(jsonfeed.FlaskInternalTask):
+class NotifyTaskBase(jsonfeed.InternalTask):
   """Abstract base class for notification task handler."""
 
   _EMAIL_TEMPLATE = None  # Subclasses must override this.
@@ -305,8 +304,7 @@
     subject += ': ' + issue.summary
 
   footer = _MakeNotificationFooter(reasons, addr_perm.reply_perm, hostport)
-  if isinstance(footer, six.text_type):
-    footer = footer.encode('utf-8')
+  footer = six.ensure_str(footer)
   if should_use_link_only:
     body = _TruncateBody(body_link_only) + footer
   elif addr_perm.is_member:
@@ -337,10 +335,10 @@
   template = HTML_BODY_WITH_GMAIL_ACTION_TEMPLATE
   if addr_perm.user and not addr_perm.user.email_view_widget:
     template = HTML_BODY_WITHOUT_GMAIL_ACTION_TEMPLATE
-  body_with_tags = _AddHTMLTags(body.decode('utf-8'))
+  body_with_tags = _AddHTMLTags(six.ensure_text(body))
   # Escape single quotes which are occasionally used to contain HTML
   # attributes and event handler definitions.
-  body_with_tags = body_with_tags.replace("'", ''')
+  body_with_tags = body_with_tags.replace("'", ''')
   html_body = template % {
       'url': detail_url,
       'body': body_with_tags,
diff --git a/features/notify_reasons.py b/features/notify_reasons.py
index 436f975..1a73918 100644
--- a/features/notify_reasons.py
+++ b/features/notify_reasons.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.
 
 """Helper functions for deciding who to notify and why.."""
 from __future__ import print_function
@@ -20,7 +19,7 @@
 from framework import framework_helpers
 from framework import framework_views
 from framework import permissions
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from search import query2ast
 from search import searchpipeline
 from tracker import component_helpers
@@ -372,9 +371,9 @@
   old_owner_addr_perm_list = ComputeIssueChangeAddressPermList(
       cnxn, old_direct_owners + old_transitive_owners, project, issue,
       services, omit_addrs, users_by_id)
-  owner_addr_perm_set = set(owner_addr_perm_list)
-  old_owner_addr_perm_list = [ap for ap in old_owner_addr_perm_list
-                              if ap not in owner_addr_perm_set]
+  old_owner_addr_perm_list = [
+      ap for ap in old_owner_addr_perm_list if ap not in owner_addr_perm_list
+  ]
   der_owner_addr_perm_list = ComputeIssueChangeAddressPermList(
       cnxn, der_direct_owners + der_transitive_owners, project, issue,
       services, omit_addrs, users_by_id)
diff --git a/features/prettify.py b/features/prettify.py
index bc64282..f29ece7 100644
--- a/features/prettify.py
+++ b/features/prettify.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 source code syntax highlighting."""
 from __future__ import print_function
diff --git a/features/pubsub.py b/features/pubsub.py
index c7c28d9..64f4305 100644
--- a/features/pubsub.py
+++ b/features/pubsub.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.
 
 """Task handlers for publishing issue updates onto a pub/sub topic.
 
@@ -11,7 +10,6 @@
 from __future__ import division
 from __future__ import absolute_import
 
-import httplib2
 import logging
 import sys
 
@@ -26,7 +24,7 @@
 from framework import jsonfeed
 
 
-class PublishPubsubIssueChangeTask(jsonfeed.FlaskInternalTask):
+class PublishPubsubIssueChangeTask(jsonfeed.InternalTask):
   """JSON servlet that pushes issue update messages onto a pub/sub topic."""
 
   def HandleRequest(self, mr):
@@ -77,8 +75,8 @@
 def set_up_pubsub_api():
   """Attempts to build and return a pub/sub API client."""
   try:
-    return build('pubsub', 'v1', http=httplib2.Http(),
-        credentials=GoogleCredentials.get_application_default())
+    return build(
+        'pubsub', 'v1', credentials=GoogleCredentials.get_application_default())
   except (Oauth2ClientError, ApiClientError):
     logging.error("Error setting up Pub/Sub API: %s" % sys.exc_info()[0])
     return None
diff --git a/features/rerankhotlist.py b/features/rerankhotlist.py
index 74365f6..ca491c8 100644
--- a/features/rerankhotlist.py
+++ b/features/rerankhotlist.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.
 
 """Class that implements the reranking on the hotlistissues table page."""
 from __future__ import print_function
@@ -19,7 +18,6 @@
 from tracker import rerank_helpers
 
 
-# TODO: convert to FLaskJsonFeed while conver to flask
 class RerankHotlistIssue(jsonfeed.JsonFeed):
   """Rerank an issue in a hotlist."""
 
@@ -136,8 +134,8 @@
         mr.target_id, mr.split_above, untouched_items)
     return rerank_helpers.GetInsertRankings(lower, higher, mr.moved_ids)
 
-  # def GetRerankHotlistIssuePage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def GetRerankHotlistIssuePage(self, **kwargs):
+    return self.handler(**kwargs)
 
-  # def PostRerankHotlistIssuePage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def PostRerankHotlistIssuePage(self, **kwargs):
+    return self.handler(**kwargs)
diff --git a/features/savedqueries.py b/features/savedqueries.py
index fb99fcf..427faa3 100644
--- a/features/savedqueries.py
+++ b/features/savedqueries.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.
 
 """Page for showing a user's saved queries and subscription options."""
 from __future__ import print_function
@@ -15,7 +14,6 @@
 
 from features import savedqueries_helpers
 from framework import framework_helpers
-from framework import flaskservlet
 from framework import permissions
 from framework import servlet
 from framework import urls
@@ -76,8 +74,8 @@
         mr, '/u/%s%s' % (mr.viewed_username, urls.SAVED_QUERIES),
         include_project=False, saved=1, ts=int(time.time()))
 
-  # def GetSavedQueriesPage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def GetSavedQueriesPage(self, **kwargs):
+    return self.handler(**kwargs)
 
-  # def PostSavedQueriesPage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def PostSavedQueriesPage(self, **kwargs):
+    return self.handler(**kwargs)
diff --git a/features/savedqueries_helpers.py b/features/savedqueries_helpers.py
index a6cb46f..b74cf73 100644
--- a/features/savedqueries_helpers.py
+++ b/features/savedqueries_helpers.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.
 
 """Utility functions and classes for dealing with saved queries.
 
@@ -43,9 +42,11 @@
 
     project_names_str = post_data.get(
         '%ssavedquery_projects_%s' % (prefix, i), '')
-    project_names = [pn.strip().lower()
-                     for pn in re.split('[],;\s]+', project_names_str)
-                     if pn.strip()]
+    project_names = [
+        pn.strip().lower()
+        for pn in re.split(r'[],;\s]+', project_names_str)
+        if pn.strip()
+    ]
     project_ids = list(project_service.LookupProjectIDs(
         cnxn, project_names).values())
 
diff --git a/features/send_notifications.py b/features/send_notifications.py
index e7ee4d4..c10083b 100644
--- a/features/send_notifications.py
+++ b/features/send_notifications.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.
 
 """Functions that prepare and send email notifications of issue changes."""
 from __future__ import print_function
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')
diff --git a/features/userhotlists.py b/features/userhotlists.py
index 65e2d9d..333d843 100644
--- a/features/userhotlists.py
+++ b/features/userhotlists.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.
 
 """Page for showing a user's hotlists."""
 from __future__ import print_function
@@ -14,7 +13,6 @@
 from features import hotlist_views
 from framework import framework_views
 from framework import servlet
-from framework import flaskservlet
 
 
 class UserHotlists(servlet.Servlet):
@@ -31,9 +29,10 @@
     viewed_users_starred_hotlists, _ = self.services.features.GetHotlistsByID(
         mr.cnxn, viewed_starred_hids)
 
-    viewed_users_relevant_hotlists = viewed_users_hotlists + list(
-        set(viewed_users_starred_hotlists.values()) -
-        set(viewed_users_hotlists))
+    viewed_users_relevant_hotlists = viewed_users_hotlists + [
+        hotlist for hotlist in viewed_users_starred_hotlists.values()
+        if hotlist not in viewed_users_hotlists
+    ]
 
     users_by_id = framework_views.MakeAllUserViews(
         mr.cnxn, self.services.user,
@@ -83,8 +82,8 @@
     help_data['cue'] = 'explain_hotlist_starring'
     return help_data
 
-  # def GetUserHotlistsPage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def GetUserHotlistsPage(self, **kwargs):
+    return self.handler(**kwargs)
 
-  # def PostUserHotlistsPage(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def PostUserHotlistsPage(self, **kwargs):
+    return self.handler(**kwargs)