[go: up one dir, main page]

[M96] Add a presubmit to prevent prod changes during freeze.

(cherry picked from commit ac48e8c95e3b5af4579e0de2664c8cc286e368a2)

Bug: 1279609
Change-Id: I9e1bb0df2e36dcd6eebd633ae9d8b7b76f4ece8e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3336096
Reviewed-by: Erik Staab <estaab@chromium.org>
Commit-Queue: Garrett Beaty <gbeaty@google.com>
Cr-Original-Commit-Position: refs/heads/main@{#951302}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339352
Auto-Submit: Garrett Beaty <gbeaty@google.com>
Cr-Commit-Position: refs/branch-heads/4664@{#1308}
Cr-Branched-From: 24dc4ee75e01a29d390d43c9c264372a169273a7-refs/heads/main@{#929512}
diff --git a/infra/config/PRESUBMIT.py b/infra/config/PRESUBMIT.py
index e698865..6aab7f3e 100644
--- a/infra/config/PRESUBMIT.py
+++ b/infra/config/PRESUBMIT.py
@@ -11,6 +11,44 @@
 PRESUBMIT_VERSION = '2.0.0'
 USE_PYTHON3 = True
 
+_IGNORE_FREEZE_FOOTER = 'Ignore-Freeze'
+
+# The time module's handling of timezones is abysmal, so the boundaries are
+# precomputed in UNIX time
+_FREEZE_START = 1639641600  # 2021/12/16 00:00 -0800
+_FREEZE_END = 1641196800  # 2022/01/03 00:00 -0800
+
+
+def CheckFreeze(input_api, output_api):
+  if _FREEZE_START <= input_api.time.time() < _FREEZE_END:
+    footers = input_api.change.GitFootersFromDescription()
+    if _IGNORE_FREEZE_FOOTER not in footers:
+
+      def convert(t):
+        ts = input_api.time.localtime(t)
+        return input_api.time.strftime('%Y/%m/%d %H:%M %z', ts)
+
+      return [
+          output_api.PresubmitError(
+              'There is a prod freeze in effect from {} until {},'
+              ' files in //infra/config cannot be modified'.format(
+                  convert(_FREEZE_START), convert(_FREEZE_END)))
+      ]
+
+  return []
+
+
+def CheckTests(input_api, output_api):
+  glob = input_api.os_path.join(input_api.PresubmitLocalPath(), '*_test.py')
+  tests = input_api.canned_checks.GetUnitTests(input_api,
+                                               output_api,
+                                               input_api.glob(glob),
+                                               run_on_python2=False,
+                                               run_on_python3=True,
+                                               skip_shebang_check=True)
+  return input_api.RunTests(tests)
+
+
 def CheckLintLuciMilo(input_api, output_api):
   if ('infra/config/generated/luci/luci-milo.cfg' in input_api.LocalPaths()
       or 'infra/config/lint-luci-milo.py' in input_api.LocalPaths()):
diff --git a/infra/config/PRESUBMIT_test.py b/infra/config/PRESUBMIT_test.py
new file mode 100755
index 0000000..0ef25a4
--- /dev/null
+++ b/infra/config/PRESUBMIT_test.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Copyright (c) 2021 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.
+
+import time
+import unittest
+
+import PRESUBMIT
+
+
+class PresubmitError:
+  def __init__(self, message):
+    self.message = message
+
+  def __eq__(self, other):
+    return isinstance(other, PresubmitError) and self.message == other.message
+
+  def __repr__(self):
+    return 'PresubmitError({!r})'.format(self.message)
+
+
+class TestCheckFreeze(unittest.TestCase):
+  def get_input_api(self, current_time, footers=None):
+    """Get an input API to use for tests.
+
+    Args:
+      current_time - Current time expressed as seconds since the epoch.
+    """
+
+    class FakeTime:
+
+      localtime = time.localtime
+      strftime = time.strftime
+
+      def time(self):
+        return float(current_time)
+
+    class FakeChange:
+      def GitFootersFromDescription(self):
+        return footers or []
+
+    class FakeInputApi:
+
+      time = FakeTime()
+      change = FakeChange()
+
+    return FakeInputApi()
+
+  def get_output_api(self):
+    class FakeOutputApi:
+
+      PresubmitError = PresubmitError
+
+    return FakeOutputApi
+
+  def test_before_freeze(self):
+    input_api = self.get_input_api(1639641599)  # 2021/12/15 23:59:59 -0800
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(errors, [])
+
+  def test_start_of_freeze(self):
+    input_api = self.get_input_api(1639641600)  # 2021/12/16 00:00:00 -0800
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(len(errors), 1)
+    self.assertTrue(
+        errors[0].message.startswith('There is a prod freeze in effect'))
+
+  def test_end_of_freeze(self):
+    input_api = self.get_input_api(1641196799)  # 2022/01/02 23:59:59 -0800
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(len(errors), 1)
+    self.assertTrue(
+        errors[0].message.startswith('There is a prod freeze in effect'))
+
+  def test_after_freeze(self):
+    input_api = self.get_input_api(1641196800)  # 2022/01/03 00:00:00 -0800')
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(errors, [])
+
+  def test_ignore_freeze(self):
+    input_api = self.get_input_api(
+        1639641600,  # 2021/12/16 00:00:00 -0800
+        footers={'Ignore-Freeze': 'testing'})
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(errors, [])
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/testing/buildbot/PRESUBMIT.py b/testing/buildbot/PRESUBMIT.py
index 24598aa..2ec6ce1 100644
--- a/testing/buildbot/PRESUBMIT.py
+++ b/testing/buildbot/PRESUBMIT.py
@@ -11,6 +11,32 @@
 PRESUBMIT_VERSION = '2.0.0'
 USE_PYTHON3 = True
 
+_IGNORE_FREEZE_FOOTER = 'Ignore-Freeze'
+
+# The time module's handling of timezones is abysmal, so the boundaries are
+# precomputed in UNIX time
+_FREEZE_START = 1639641600  # 2021/12/16 00:00 -0800
+_FREEZE_END = 1641196800  # 2022/01/03 00:00 -0800
+
+
+def CheckFreeze(input_api, output_api):
+  if _FREEZE_START <= input_api.time.time() < _FREEZE_END:
+    footers = input_api.change.GitFootersFromDescription()
+    if _IGNORE_FREEZE_FOOTER not in footers:
+
+      def convert(t):
+        ts = input_api.time.localtime(t)
+        return input_api.time.strftime('%Y/%m/%d %H:%M %z', ts)
+
+      return [
+          output_api.PresubmitError(
+              'There is a prod freeze in effect from {} until {},'
+              ' files in //testing/buildbot cannot be modified'.format(
+                  convert(_FREEZE_START), convert(_FREEZE_END)))
+      ]
+
+  return []
+
 
 def CheckSourceSideSpecs(input_api, output_api):
   return input_api.RunTests([
diff --git a/testing/buildbot/PRESUBMIT_test.py b/testing/buildbot/PRESUBMIT_test.py
new file mode 100755
index 0000000..0ef25a4
--- /dev/null
+++ b/testing/buildbot/PRESUBMIT_test.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Copyright (c) 2021 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.
+
+import time
+import unittest
+
+import PRESUBMIT
+
+
+class PresubmitError:
+  def __init__(self, message):
+    self.message = message
+
+  def __eq__(self, other):
+    return isinstance(other, PresubmitError) and self.message == other.message
+
+  def __repr__(self):
+    return 'PresubmitError({!r})'.format(self.message)
+
+
+class TestCheckFreeze(unittest.TestCase):
+  def get_input_api(self, current_time, footers=None):
+    """Get an input API to use for tests.
+
+    Args:
+      current_time - Current time expressed as seconds since the epoch.
+    """
+
+    class FakeTime:
+
+      localtime = time.localtime
+      strftime = time.strftime
+
+      def time(self):
+        return float(current_time)
+
+    class FakeChange:
+      def GitFootersFromDescription(self):
+        return footers or []
+
+    class FakeInputApi:
+
+      time = FakeTime()
+      change = FakeChange()
+
+    return FakeInputApi()
+
+  def get_output_api(self):
+    class FakeOutputApi:
+
+      PresubmitError = PresubmitError
+
+    return FakeOutputApi
+
+  def test_before_freeze(self):
+    input_api = self.get_input_api(1639641599)  # 2021/12/15 23:59:59 -0800
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(errors, [])
+
+  def test_start_of_freeze(self):
+    input_api = self.get_input_api(1639641600)  # 2021/12/16 00:00:00 -0800
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(len(errors), 1)
+    self.assertTrue(
+        errors[0].message.startswith('There is a prod freeze in effect'))
+
+  def test_end_of_freeze(self):
+    input_api = self.get_input_api(1641196799)  # 2022/01/02 23:59:59 -0800
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(len(errors), 1)
+    self.assertTrue(
+        errors[0].message.startswith('There is a prod freeze in effect'))
+
+  def test_after_freeze(self):
+    input_api = self.get_input_api(1641196800)  # 2022/01/03 00:00:00 -0800')
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(errors, [])
+
+  def test_ignore_freeze(self):
+    input_api = self.get_input_api(
+        1639641600,  # 2021/12/16 00:00:00 -0800
+        footers={'Ignore-Freeze': 'testing'})
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(errors, [])
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tools/mb/PRESUBMIT.py b/tools/mb/PRESUBMIT.py
index aa560e72..c8568486 100644
--- a/tools/mb/PRESUBMIT.py
+++ b/tools/mb/PRESUBMIT.py
@@ -5,11 +5,55 @@
 USE_PYTHON3 = True
 
 
+_IGNORE_FREEZE_FOOTER = 'Ignore-Freeze'
+
+# The time module's handling of timezones is abysmal, so the boundaries are
+# precomputed in UNIX time
+_FREEZE_START = 1639641600  # 2021/12/16 00:00 -0800
+_FREEZE_END = 1641196800  # 2022/01/03 00:00 -0800
+
+
+def CheckFreeze(input_api, output_api):
+  if _FREEZE_START <= input_api.time.time() < _FREEZE_END:
+    footers = input_api.change.GitFootersFromDescription()
+    if _IGNORE_FREEZE_FOOTER not in footers:
+
+      def convert(t):
+        ts = input_api.time.localtime(t)
+        return input_api.time.strftime('%Y/%m/%d %H:%M %z', ts)
+
+      return [
+          output_api.PresubmitError(
+              'There is a prod freeze in effect from {} until {},'
+              ' files in //tools/mb cannot be modified'.format(
+                  convert(_FREEZE_START), convert(_FREEZE_END)))
+      ]
+
+  return []
+
+
+def CheckTests(input_api, output_api):
+  glob = input_api.os_path.join(input_api.PresubmitLocalPath(), '*_test.py')
+  tests = input_api.canned_checks.GetUnitTests(input_api,
+                                               output_api,
+                                               input_api.glob(glob),
+                                               run_on_python2=False,
+                                               run_on_python3=True,
+                                               skip_shebang_check=True)
+  return input_api.RunTests(tests)
+
+
 def _CommonChecks(input_api, output_api):
   results = []
 
   # Run Pylint over the files in the directory.
-  pylint_checks = input_api.canned_checks.GetPylint(input_api, output_api)
+  pylint_checks = input_api.canned_checks.GetPylint(
+      input_api,
+      output_api,
+      # pylint complains about Checkfreeze not being defined, its probably
+      # finding a different PRESUBMIT.py
+      files_to_skip=['PRESUBMIT_test.py'],
+  )
   results.extend(input_api.RunTests(pylint_checks))
 
   # Run the MB unittests.
@@ -24,6 +68,9 @@
                         cmd=cmd, kwargs=kwargs,
                         message=output_api.PresubmitError)]))
 
+  results.extend(CheckFreeze(input_api, output_api))
+  results.extend(CheckTests(input_api, output_api))
+
   return results
 
 
diff --git a/tools/mb/PRESUBMIT_test.py b/tools/mb/PRESUBMIT_test.py
new file mode 100755
index 0000000..0ef25a4
--- /dev/null
+++ b/tools/mb/PRESUBMIT_test.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Copyright (c) 2021 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.
+
+import time
+import unittest
+
+import PRESUBMIT
+
+
+class PresubmitError:
+  def __init__(self, message):
+    self.message = message
+
+  def __eq__(self, other):
+    return isinstance(other, PresubmitError) and self.message == other.message
+
+  def __repr__(self):
+    return 'PresubmitError({!r})'.format(self.message)
+
+
+class TestCheckFreeze(unittest.TestCase):
+  def get_input_api(self, current_time, footers=None):
+    """Get an input API to use for tests.
+
+    Args:
+      current_time - Current time expressed as seconds since the epoch.
+    """
+
+    class FakeTime:
+
+      localtime = time.localtime
+      strftime = time.strftime
+
+      def time(self):
+        return float(current_time)
+
+    class FakeChange:
+      def GitFootersFromDescription(self):
+        return footers or []
+
+    class FakeInputApi:
+
+      time = FakeTime()
+      change = FakeChange()
+
+    return FakeInputApi()
+
+  def get_output_api(self):
+    class FakeOutputApi:
+
+      PresubmitError = PresubmitError
+
+    return FakeOutputApi
+
+  def test_before_freeze(self):
+    input_api = self.get_input_api(1639641599)  # 2021/12/15 23:59:59 -0800
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(errors, [])
+
+  def test_start_of_freeze(self):
+    input_api = self.get_input_api(1639641600)  # 2021/12/16 00:00:00 -0800
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(len(errors), 1)
+    self.assertTrue(
+        errors[0].message.startswith('There is a prod freeze in effect'))
+
+  def test_end_of_freeze(self):
+    input_api = self.get_input_api(1641196799)  # 2022/01/02 23:59:59 -0800
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(len(errors), 1)
+    self.assertTrue(
+        errors[0].message.startswith('There is a prod freeze in effect'))
+
+  def test_after_freeze(self):
+    input_api = self.get_input_api(1641196800)  # 2022/01/03 00:00:00 -0800')
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(errors, [])
+
+  def test_ignore_freeze(self):
+    input_api = self.get_input_api(
+        1639641600,  # 2021/12/16 00:00:00 -0800
+        footers={'Ignore-Freeze': 'testing'})
+    output_api = self.get_output_api()
+
+    errors = PRESUBMIT.CheckFreeze(input_api, output_api)
+
+    self.assertEqual(errors, [])
+
+
+if __name__ == '__main__':
+  unittest.main()