blob: 0b71a8ebb3d14b9ee9dcf6c1f1f42273a880b48c [file]
#!/usr/bin/env python3
#
# Copyright 2026, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import io
import unittest
from unittest import mock
import validate
class ValidateTests(unittest.TestCase):
@mock.patch('sys.argv', ['validate.py', '--commit', 'Valid commit.'])
def test_valid_commit(self):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 0)
@mock.patch(
'sys.argv',
['validate.py', '--commit', 'Outside of relnote CaseCase valid.']
)
def test_valid_casecase_in_backticks(self):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 0)
@mock.patch(
'sys.argv',
['validate.py', '--commit', 'Relnote: This has an InvalidCase word.']
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_invalid_casecase(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
output = mock_stdout.getvalue()
self.assertIn(
'Error: The following words use CamelCase in the `Relnote:` '
'tag in the commit message, but are not surrounded by '
'backticks (`):\nInvalidCase',
output
)
self.assertIn(
'Suggested replacement:\nRelnote: This has an `InvalidCase` word.',
output
)
@mock.patch(
'sys.argv',
['validate.py', '--commit', 'Relnote: InvalidCase AnotherInvalidCase']
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_multiple_invalid_casecase(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
output = mock_stdout.getvalue()
self.assertIn(
'Error: The following words use CamelCase in the `Relnote:` '
'tag in the commit message, but are not surrounded by '
'backticks (`):',
output
)
self.assertIn('InvalidCase', output)
self.assertIn('AnotherInvalidCase', output)
self.assertIn(
'Suggested replacement:\nRelnote: `InvalidCase` `AnotherInvalidCase`',
output
)
@mock.patch(
'sys.argv',
['validate.py', '--commit', 'Relnote: Check `ValidC` fail InvalidC.']
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_mixed_casecase(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
output = mock_stdout.getvalue()
error_list, suggested = output.split('Suggested replacement:')
self.assertIn('InvalidC', error_list)
self.assertNotIn('ValidC', error_list)
self.assertIn(
'Relnote: Check `ValidC` fail `InvalidC`.',
suggested
)
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'Relnote: """Long relnote\nsplit multiple lines CamelCase entries"""']
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_multiline_relnote_with_quotes(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
output = mock_stdout.getvalue()
self.assertIn(
'Error: The following words use CamelCase in the `Relnote:` '
'tag in the commit message, but are not surrounded by '
'backticks (`):\nCamelCase',
output
)
self.assertIn(
'Suggested replacement:\nRelnote: """Long relnote\nsplit multiple lines `CamelCase` entries"""',
output
)
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'Relnote: """Long valid relnote\nmultiple lines `CamelCase` entries"""']
)
def test_multiline_valid_relnote_with_quotes(self):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 0)
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'Relnote: "Long relnote\nmultiple lines CamelCase entries"']
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_multiline_relnote_with_double_quotes_fails_camel_case(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
output = mock_stdout.getvalue()
self.assertIn(
'Error: The following words use CamelCase in the `Relnote:` '
'tag in the commit message, but are not surrounded by '
'backticks (`):\nCamelCase',
output
)
self.assertIn(
'Suggested replacement:\nRelnote: "Long relnote\nmultiple lines `CamelCase` entries"',
output
)
@mock.patch(
'sys.argv',
['validate.py', '--commit',
"Relnote: 'Long relnote\nmultiple lines CamelCase entries'"]
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_multiline_relnote_with_single_quotes_fails(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
self.assertIn('Error: Multi-line release notes must be surrounded by quotes (") or triple quotes (""").', mock_stdout.getvalue())
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'Relnote: "Valid long relnote\nmultiple lines `CamelCase` entries"']
)
def test_multiline_relnote_with_double_quotes_and_backticks_passes(self):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 0)
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'This is a message\n\nRelnote: This is a very long\nrelnote message here\nBug: 123\nTest: Things']
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_multiline_relnote_no_quotes(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
self.assertIn('Error: Multi-line release notes must be surrounded by quotes (") or triple quotes (""").', mock_stdout.getvalue())
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'This is a message\n\nRelnote: """This is a very long\nrelnote message here"""\nBug: 123\nTest: Things']
)
def test_multiline_relnote_triple_quotes(self):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 0)
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'This is a message\n\nRelnote: This is a single line relnote message here\nBug: 123\nTest: Things']
)
def test_single_line_relnote_no_quotes(self):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 0)
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'This is a message\n\nRelnote: """This is a very long\nrelnote message here"""\nRelnote: Bad long\nrelnote\nBug: 123\nTest: Things']
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_multiple_relnotes_one_bad(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
self.assertIn('Error: Multi-line release notes must be surrounded by quotes (") or triple quotes (""").', mock_stdout.getvalue())
@mock.patch('sys.argv', ['validate.py'])
@mock.patch('sys.stderr', new_callable=io.StringIO)
def test_no_commit_arg(self, mock_stderr):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 2)
self.assertIn(
'the following arguments are required: --commit',
mock_stderr.getvalue()
)
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'Relnote: First InvalidCase\n\nRelnote: Second InvalidCaseTwo']
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_multiple_relnotes_replacements(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
output = mock_stdout.getvalue()
self.assertIn(
'Suggested replacement:\nRelnote: First `InvalidCase`\n\nRelnote: Second `InvalidCaseTwo`',
output
)
@mock.patch(
'sys.argv',
['validate.py', '--commit',
'Relnote: First valid relnote\n\nRelnote: Second InvalidCaseTwo']
)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_multiple_relnotes_one_valid_replacements(self, mock_stdout):
with self.assertRaises(SystemExit) as cm:
validate.main()
self.assertEqual(cm.exception.code, 1)
output = mock_stdout.getvalue()
self.assertIn(
'Suggested replacement:\nRelnote: First valid relnote\n\nRelnote: Second `InvalidCaseTwo`',
output
)
if __name__ == '__main__':
unittest.main()