[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()