1#!/usr/bin/env python3 2# -*- coding:utf-8 -*- 3# Copyright 2016 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Unittests for the hooks module.""" 18 19from __future__ import print_function 20 21import os 22import sys 23import unittest 24 25_path = os.path.realpath(__file__ + '/../..') 26if sys.path[0] != _path: 27 sys.path.insert(0, _path) 28del _path 29 30# We have to import our local modules after the sys.path tweak. We can't use 31# relative imports because this is an executable program, not a module. 32# pylint: disable=wrong-import-position 33import rh 34import rh.config 35import rh.hooks 36from rh.sixish import mock 37from rh.sixish import string_types 38 39 40class HooksDocsTests(unittest.TestCase): 41 """Make sure all hook features are documented. 42 43 Note: These tests are a bit hokey in that they parse README.md. But they 44 get the job done, so that's all that matters right? 45 """ 46 47 def setUp(self): 48 self.readme = os.path.join(os.path.dirname(os.path.dirname( 49 os.path.realpath(__file__))), 'README.md') 50 51 def _grab_section(self, section): 52 """Extract the |section| text out of the readme.""" 53 ret = [] 54 in_section = False 55 with open(self.readme) as fp: 56 for line in fp: 57 if not in_section: 58 # Look for the section like "## [Tool Paths]". 59 if (line.startswith('#') and 60 line.lstrip('#').strip() == section): 61 in_section = True 62 else: 63 # Once we hit the next section (higher or lower), break. 64 if line[0] == '#': 65 break 66 ret.append(line) 67 return ''.join(ret) 68 69 def testBuiltinHooks(self): 70 """Verify builtin hooks are documented.""" 71 data = self._grab_section('[Builtin Hooks]') 72 for hook in rh.hooks.BUILTIN_HOOKS: 73 self.assertIn('* `%s`:' % (hook,), data, 74 msg='README.md missing docs for hook "%s"' % (hook,)) 75 76 def testToolPaths(self): 77 """Verify tools are documented.""" 78 data = self._grab_section('[Tool Paths]') 79 for tool in rh.hooks.TOOL_PATHS: 80 self.assertIn('* `%s`:' % (tool,), data, 81 msg='README.md missing docs for tool "%s"' % (tool,)) 82 83 def testPlaceholders(self): 84 """Verify placeholder replacement vars are documented.""" 85 data = self._grab_section('Placeholders') 86 for var in rh.hooks.Placeholders.vars(): 87 self.assertIn('* `${%s}`:' % (var,), data, 88 msg='README.md missing docs for var "%s"' % (var,)) 89 90 91class PlaceholderTests(unittest.TestCase): 92 """Verify behavior of replacement variables.""" 93 94 def setUp(self): 95 self._saved_environ = os.environ.copy() 96 os.environ.update({ 97 'PREUPLOAD_COMMIT_MESSAGE': 'commit message', 98 'PREUPLOAD_COMMIT': '5c4c293174bb61f0f39035a71acd9084abfa743d', 99 }) 100 self.replacer = rh.hooks.Placeholders( 101 [rh.git.RawDiffEntry(file=x) 102 for x in ['path1/file1', 'path2/file2']]) 103 104 def tearDown(self): 105 os.environ.clear() 106 os.environ.update(self._saved_environ) 107 108 def testVars(self): 109 """Light test for the vars inspection generator.""" 110 ret = list(self.replacer.vars()) 111 self.assertGreater(len(ret), 4) 112 self.assertIn('PREUPLOAD_COMMIT', ret) 113 114 @mock.patch.object(rh.git, 'find_repo_root', return_value='/ ${BUILD_OS}') 115 def testExpandVars(self, _m): 116 """Verify the replacement actually works.""" 117 input_args = [ 118 # Verify ${REPO_ROOT} is updated, but not REPO_ROOT. 119 # We also make sure that things in ${REPO_ROOT} are not double 120 # expanded (which is why the return includes ${BUILD_OS}). 121 '${REPO_ROOT}/some/prog/REPO_ROOT/ok', 122 # Verify lists are merged rather than inserted. 123 '${PREUPLOAD_FILES}', 124 # Verify each file is preceded with '--file=' prefix. 125 '--file=${PREUPLOAD_FILES_PREFIXED}', 126 # Verify each file is preceded with '--file' argument. 127 '--file', 128 '${PREUPLOAD_FILES_PREFIXED}', 129 # Verify values with whitespace don't expand into multiple args. 130 '${PREUPLOAD_COMMIT_MESSAGE}', 131 # Verify multiple values get replaced. 132 '${PREUPLOAD_COMMIT}^${PREUPLOAD_COMMIT_MESSAGE}', 133 # Unknown vars should be left alone. 134 '${THIS_VAR_IS_GOOD}', 135 ] 136 output_args = self.replacer.expand_vars(input_args) 137 exp_args = [ 138 '/ ${BUILD_OS}/some/prog/REPO_ROOT/ok', 139 'path1/file1', 140 'path2/file2', 141 '--file=path1/file1', 142 '--file=path2/file2', 143 '--file', 144 'path1/file1', 145 '--file', 146 'path2/file2', 147 'commit message', 148 '5c4c293174bb61f0f39035a71acd9084abfa743d^commit message', 149 '${THIS_VAR_IS_GOOD}', 150 ] 151 self.assertEqual(output_args, exp_args) 152 153 def testTheTester(self): 154 """Make sure we have a test for every variable.""" 155 for var in self.replacer.vars(): 156 self.assertIn('test%s' % (var,), dir(self), 157 msg='Missing unittest for variable %s' % (var,)) 158 159 def testPREUPLOAD_COMMIT_MESSAGE(self): 160 """Verify handling of PREUPLOAD_COMMIT_MESSAGE.""" 161 self.assertEqual(self.replacer.get('PREUPLOAD_COMMIT_MESSAGE'), 162 'commit message') 163 164 def testPREUPLOAD_COMMIT(self): 165 """Verify handling of PREUPLOAD_COMMIT.""" 166 self.assertEqual(self.replacer.get('PREUPLOAD_COMMIT'), 167 '5c4c293174bb61f0f39035a71acd9084abfa743d') 168 169 def testPREUPLOAD_FILES(self): 170 """Verify handling of PREUPLOAD_FILES.""" 171 self.assertEqual(self.replacer.get('PREUPLOAD_FILES'), 172 ['path1/file1', 'path2/file2']) 173 174 @mock.patch.object(rh.git, 'find_repo_root', return_value='/repo!') 175 def testREPO_ROOT(self, m): 176 """Verify handling of REPO_ROOT.""" 177 self.assertEqual(self.replacer.get('REPO_ROOT'), m.return_value) 178 179 @mock.patch.object(rh.hooks, '_get_build_os_name', return_value='vapier os') 180 def testBUILD_OS(self, m): 181 """Verify handling of BUILD_OS.""" 182 self.assertEqual(self.replacer.get('BUILD_OS'), m.return_value) 183 184 185class HookOptionsTests(unittest.TestCase): 186 """Verify behavior of HookOptions object.""" 187 188 @mock.patch.object(rh.hooks, '_get_build_os_name', return_value='vapier os') 189 def testExpandVars(self, m): 190 """Verify expand_vars behavior.""" 191 # Simple pass through. 192 args = ['who', 'goes', 'there ?'] 193 self.assertEqual(args, rh.hooks.HookOptions.expand_vars(args)) 194 195 # At least one replacement. Most real testing is in PlaceholderTests. 196 args = ['who', 'goes', 'there ?', '${BUILD_OS} is great'] 197 exp_args = ['who', 'goes', 'there ?', '%s is great' % (m.return_value,)] 198 self.assertEqual(exp_args, rh.hooks.HookOptions.expand_vars(args)) 199 200 def testArgs(self): 201 """Verify args behavior.""" 202 # Verify initial args to __init__ has higher precedent. 203 args = ['start', 'args'] 204 options = rh.hooks.HookOptions('hook name', args, {}) 205 self.assertEqual(options.args(), args) 206 self.assertEqual(options.args(default_args=['moo']), args) 207 208 # Verify we fall back to default_args. 209 args = ['default', 'args'] 210 options = rh.hooks.HookOptions('hook name', [], {}) 211 self.assertEqual(options.args(), []) 212 self.assertEqual(options.args(default_args=args), args) 213 214 def testToolPath(self): 215 """Verify tool_path behavior.""" 216 options = rh.hooks.HookOptions('hook name', [], { 217 'cpplint': 'my cpplint', 218 }) 219 # Check a builtin (and not overridden) tool. 220 self.assertEqual(options.tool_path('pylint'), 'pylint') 221 # Check an overridden tool. 222 self.assertEqual(options.tool_path('cpplint'), 'my cpplint') 223 # Check an unknown tool fails. 224 self.assertRaises(AssertionError, options.tool_path, 'extra_tool') 225 226 227class UtilsTests(unittest.TestCase): 228 """Verify misc utility functions.""" 229 230 def testRunCommand(self): 231 """Check _run behavior.""" 232 # Most testing is done against the utils.RunCommand already. 233 # pylint: disable=protected-access 234 ret = rh.hooks._run(['true']) 235 self.assertEqual(ret.returncode, 0) 236 237 def testBuildOs(self): 238 """Check _get_build_os_name behavior.""" 239 # Just verify it returns something and doesn't crash. 240 # pylint: disable=protected-access 241 ret = rh.hooks._get_build_os_name() 242 self.assertTrue(isinstance(ret, string_types)) 243 self.assertNotEqual(ret, '') 244 245 def testGetHelperPath(self): 246 """Check get_helper_path behavior.""" 247 # Just verify it doesn't crash. It's a dirt simple func. 248 ret = rh.hooks.get_helper_path('booga') 249 self.assertTrue(isinstance(ret, string_types)) 250 self.assertNotEqual(ret, '') 251 252 253 254@mock.patch.object(rh.utils, 'run') 255@mock.patch.object(rh.hooks, '_check_cmd', return_value=['check_cmd']) 256class BuiltinHooksTests(unittest.TestCase): 257 """Verify the builtin hooks.""" 258 259 def setUp(self): 260 self.project = rh.Project(name='project-name', dir='/.../repo/dir', 261 remote='remote') 262 self.options = rh.hooks.HookOptions('hook name', [], {}) 263 264 def _test_commit_messages(self, func, accept, msgs, files=None): 265 """Helper for testing commit message hooks. 266 267 Args: 268 func: The hook function to test. 269 accept: Whether all the |msgs| should be accepted. 270 msgs: List of messages to test. 271 files: List of files to pass to the hook. 272 """ 273 if files: 274 diff = [rh.git.RawDiffEntry(file=x) for x in files] 275 else: 276 diff = [] 277 for desc in msgs: 278 ret = func(self.project, 'commit', desc, diff, options=self.options) 279 if accept: 280 self.assertFalse( 281 bool(ret), msg='Should have accepted: {{{%s}}}' % (desc,)) 282 else: 283 self.assertTrue( 284 bool(ret), msg='Should have rejected: {{{%s}}}' % (desc,)) 285 286 def _test_file_filter(self, mock_check, func, files): 287 """Helper for testing hooks that filter by files and run external tools. 288 289 Args: 290 mock_check: The mock of _check_cmd. 291 func: The hook function to test. 292 files: A list of files that we'd check. 293 """ 294 # First call should do nothing as there are no files to check. 295 ret = func(self.project, 'commit', 'desc', (), options=self.options) 296 self.assertEqual(ret, None) 297 self.assertFalse(mock_check.called) 298 299 # Second call should include some checks. 300 diff = [rh.git.RawDiffEntry(file=x) for x in files] 301 ret = func(self.project, 'commit', 'desc', diff, options=self.options) 302 self.assertEqual(ret, mock_check.return_value) 303 304 def testTheTester(self, _mock_check, _mock_run): 305 """Make sure we have a test for every hook.""" 306 for hook in rh.hooks.BUILTIN_HOOKS: 307 self.assertIn('test_%s' % (hook,), dir(self), 308 msg='Missing unittest for builtin hook %s' % (hook,)) 309 310 def test_bpfmt(self, mock_check, _mock_run): 311 """Verify the bpfmt builtin hook.""" 312 # First call should do nothing as there are no files to check. 313 ret = rh.hooks.check_bpfmt( 314 self.project, 'commit', 'desc', (), options=self.options) 315 self.assertIsNone(ret) 316 self.assertFalse(mock_check.called) 317 318 # Second call will have some results. 319 diff = [rh.git.RawDiffEntry(file='Android.bp')] 320 ret = rh.hooks.check_bpfmt( 321 self.project, 'commit', 'desc', diff, options=self.options) 322 self.assertIsNotNone(ret) 323 324 def test_checkpatch(self, mock_check, _mock_run): 325 """Verify the checkpatch builtin hook.""" 326 ret = rh.hooks.check_checkpatch( 327 self.project, 'commit', 'desc', (), options=self.options) 328 self.assertEqual(ret, mock_check.return_value) 329 330 def test_clang_format(self, mock_check, _mock_run): 331 """Verify the clang_format builtin hook.""" 332 ret = rh.hooks.check_clang_format( 333 self.project, 'commit', 'desc', (), options=self.options) 334 self.assertEqual(ret, mock_check.return_value) 335 336 def test_google_java_format(self, mock_check, _mock_run): 337 """Verify the google_java_format builtin hook.""" 338 ret = rh.hooks.check_google_java_format( 339 self.project, 'commit', 'desc', (), options=self.options) 340 self.assertEqual(ret, mock_check.return_value) 341 342 def test_commit_msg_bug_field(self, _mock_check, _mock_run): 343 """Verify the commit_msg_bug_field builtin hook.""" 344 # Check some good messages. 345 self._test_commit_messages( 346 rh.hooks.check_commit_msg_bug_field, True, ( 347 'subj\n\nBug: 1234\n', 348 'subj\n\nBug: 1234\nChange-Id: blah\n', 349 )) 350 351 # Check some bad messages. 352 self._test_commit_messages( 353 rh.hooks.check_commit_msg_bug_field, False, ( 354 'subj', 355 'subj\n\nBUG=1234\n', 356 'subj\n\nBUG: 1234\n', 357 'subj\n\nBug: N/A\n', 358 'subj\n\nBug:\n', 359 )) 360 361 def test_commit_msg_changeid_field(self, _mock_check, _mock_run): 362 """Verify the commit_msg_changeid_field builtin hook.""" 363 # Check some good messages. 364 self._test_commit_messages( 365 rh.hooks.check_commit_msg_changeid_field, True, ( 366 'subj\n\nChange-Id: I1234\n', 367 )) 368 369 # Check some bad messages. 370 self._test_commit_messages( 371 rh.hooks.check_commit_msg_changeid_field, False, ( 372 'subj', 373 'subj\n\nChange-Id: 1234\n', 374 'subj\n\nChange-ID: I1234\n', 375 )) 376 377 def test_commit_msg_prebuilt_apk_fields(self, _mock_check, _mock_run): 378 """Verify the check_commit_msg_prebuilt_apk_fields builtin hook.""" 379 # Commits without APKs should pass. 380 self._test_commit_messages( 381 rh.hooks.check_commit_msg_prebuilt_apk_fields, 382 True, 383 ( 384 'subj\nTest: test case\nBug: bug id\n', 385 ), 386 ['foo.cpp', 'bar.py',] 387 ) 388 389 # Commits with APKs and all the required messages should pass. 390 self._test_commit_messages( 391 rh.hooks.check_commit_msg_prebuilt_apk_fields, 392 True, 393 ( 394 ('Test App\n\nbar.apk\npackage: name=\'com.foo.bar\'\n' 395 'versionCode=\'1001\'\nversionName=\'1.0.1001-A\'\n' 396 'platformBuildVersionName=\'\'\ncompileSdkVersion=\'28\'\n' 397 'compileSdkVersionCodename=\'9\'\nsdkVersion:\'16\'\n' 398 'targetSdkVersion:\'28\'\n\nBuilt here:\n' 399 'http://foo.bar.com/builder\n\n' 400 'This build IS suitable for public release.\n\n' 401 'Bug: 123\nTest: test\nChange-Id: XXXXXXX\n'), 402 ('Test App\n\nBuilt here:\nhttp://foo.bar.com/builder\n\n' 403 'This build IS NOT suitable for public release.\n\n' 404 'bar.apk\npackage: name=\'com.foo.bar\'\n' 405 'versionCode=\'1001\'\nversionName=\'1.0.1001-A\'\n' 406 'platformBuildVersionName=\'\'\ncompileSdkVersion=\'28\'\n' 407 'compileSdkVersionCodename=\'9\'\nsdkVersion:\'16\'\n' 408 'targetSdkVersion:\'28\'\n\nBug: 123\nTest: test\n' 409 'Change-Id: XXXXXXX\n'), 410 ('Test App\n\nbar.apk\npackage: name=\'com.foo.bar\'\n' 411 'versionCode=\'1001\'\nversionName=\'1.0.1001-A\'\n' 412 'platformBuildVersionName=\'\'\ncompileSdkVersion=\'28\'\n' 413 'compileSdkVersionCodename=\'9\'\nsdkVersion:\'16\'\n' 414 'targetSdkVersion:\'28\'\n\nBuilt here:\n' 415 'http://foo.bar.com/builder\n\n' 416 'This build IS suitable for preview release but IS NOT ' 417 'suitable for public release.\n\n' 418 'Bug: 123\nTest: test\nChange-Id: XXXXXXX\n'), 419 ('Test App\n\nbar.apk\npackage: name=\'com.foo.bar\'\n' 420 'versionCode=\'1001\'\nversionName=\'1.0.1001-A\'\n' 421 'platformBuildVersionName=\'\'\ncompileSdkVersion=\'28\'\n' 422 'compileSdkVersionCodename=\'9\'\nsdkVersion:\'16\'\n' 423 'targetSdkVersion:\'28\'\n\nBuilt here:\n' 424 'http://foo.bar.com/builder\n\n' 425 'This build IS NOT suitable for preview or public release.\n\n' 426 'Bug: 123\nTest: test\nChange-Id: XXXXXXX\n'), 427 ), 428 ['foo.apk', 'bar.py',] 429 ) 430 431 # Commits with APKs and without all the required messages should fail. 432 self._test_commit_messages( 433 rh.hooks.check_commit_msg_prebuilt_apk_fields, 434 False, 435 ( 436 'subj\nTest: test case\nBug: bug id\n', 437 # Missing 'package'. 438 ('Test App\n\nbar.apk\n' 439 'versionCode=\'1001\'\nversionName=\'1.0.1001-A\'\n' 440 'platformBuildVersionName=\'\'\ncompileSdkVersion=\'28\'\n' 441 'compileSdkVersionCodename=\'9\'\nsdkVersion:\'16\'\n' 442 'targetSdkVersion:\'28\'\n\nBuilt here:\n' 443 'http://foo.bar.com/builder\n\n' 444 'This build IS suitable for public release.\n\n' 445 'Bug: 123\nTest: test\nChange-Id: XXXXXXX\n'), 446 # Missing 'sdkVersion'. 447 ('Test App\n\nbar.apk\npackage: name=\'com.foo.bar\'\n' 448 'versionCode=\'1001\'\nversionName=\'1.0.1001-A\'\n' 449 'platformBuildVersionName=\'\'\ncompileSdkVersion=\'28\'\n' 450 'compileSdkVersionCodename=\'9\'\n' 451 'targetSdkVersion:\'28\'\n\nBuilt here:\n' 452 'http://foo.bar.com/builder\n\n' 453 'This build IS suitable for public release.\n\n' 454 'Bug: 123\nTest: test\nChange-Id: XXXXXXX\n'), 455 # Missing 'targetSdkVersion'. 456 ('Test App\n\nbar.apk\npackage: name=\'com.foo.bar\'\n' 457 'versionCode=\'1001\'\nversionName=\'1.0.1001-A\'\n' 458 'platformBuildVersionName=\'\'\ncompileSdkVersion=\'28\'\n' 459 'compileSdkVersionCodename=\'9\'\nsdkVersion:\'16\'\n' 460 'Built here:\nhttp://foo.bar.com/builder\n\n' 461 'This build IS suitable for public release.\n\n' 462 'Bug: 123\nTest: test\nChange-Id: XXXXXXX\n'), 463 # Missing build location. 464 ('Test App\n\nbar.apk\npackage: name=\'com.foo.bar\'\n' 465 'versionCode=\'1001\'\nversionName=\'1.0.1001-A\'\n' 466 'platformBuildVersionName=\'\'\ncompileSdkVersion=\'28\'\n' 467 'compileSdkVersionCodename=\'9\'\nsdkVersion:\'16\'\n' 468 'targetSdkVersion:\'28\'\n\n' 469 'This build IS suitable for public release.\n\n' 470 'Bug: 123\nTest: test\nChange-Id: XXXXXXX\n'), 471 # Missing public release indication. 472 ('Test App\n\nbar.apk\npackage: name=\'com.foo.bar\'\n' 473 'versionCode=\'1001\'\nversionName=\'1.0.1001-A\'\n' 474 'platformBuildVersionName=\'\'\ncompileSdkVersion=\'28\'\n' 475 'compileSdkVersionCodename=\'9\'\nsdkVersion:\'16\'\n' 476 'targetSdkVersion:\'28\'\n\nBuilt here:\n' 477 'http://foo.bar.com/builder\n\n' 478 'Bug: 123\nTest: test\nChange-Id: XXXXXXX\n'), 479 ), 480 ['foo.apk', 'bar.py',] 481 ) 482 483 def test_commit_msg_test_field(self, _mock_check, _mock_run): 484 """Verify the commit_msg_test_field builtin hook.""" 485 # Check some good messages. 486 self._test_commit_messages( 487 rh.hooks.check_commit_msg_test_field, True, ( 488 'subj\n\nTest: i did done dood it\n', 489 )) 490 491 # Check some bad messages. 492 self._test_commit_messages( 493 rh.hooks.check_commit_msg_test_field, False, ( 494 'subj', 495 'subj\n\nTEST=1234\n', 496 'subj\n\nTEST: I1234\n', 497 )) 498 499 def test_commit_msg_relnote_field_format(self, _mock_check, _mock_run): 500 """Verify the commit_msg_relnote_field_format builtin hook.""" 501 # Check some good messages. 502 self._test_commit_messages( 503 rh.hooks.check_commit_msg_relnote_field_format, 504 True, 505 ( 506 'subj', 507 'subj\n\nTest: i did done dood it\n', 508 'subj\n\nMore content\n\nTest: i did done dood it\n', 509 'subj\n\nRelnote: This is a release note\n', 510 'subj\n\nRelnote:This is a release note\n', 511 'subj\n\nRelnote: This is a release note.\nBug: 1234', 512 'subj\n\nRelnote: "This is a release note."\nBug: 1234', 513 'subj\n\nRelnote: This is a release note.\nChange-Id: 1234', 514 'subj\n\nRelnote: This is a release note.\n\nChange-Id: 1234', 515 ('subj\n\nRelnote: "This is a release note."\n\n' 516 'Change-Id: 1234'), 517 ('subj\n\nRelnote: This is a release note.\n\n' 518 'It has more info, but it is not part of the release note' 519 '\nChange-Id: 1234'), 520 ('subj\n\nRelnote: "This is a release note.\n' 521 'It contains a correct second line."'), 522 ('subj\n\nRelnote:"This is a release note.\n' 523 'It contains a correct second line."'), 524 ('subj\n\nRelnote: "This is a release note.\n' 525 'It contains a correct second line.\n' 526 'And even a third line."\n' 527 'Bug: 1234'), 528 ('subj\n\nRelnote: This is release note 1.\n' 529 'Relnote: This is release note 2.\n' 530 'Bug: 1234'), 531 ('subj\n\nRelnote: This is release note 1.\n' 532 'Relnote: "This is release note 2, and it\n' 533 'contains a correctly formatted third line."\n' 534 'Bug: 1234'), 535 ('subj\n\nRelnote: "This is release note 1 with\n' 536 'a correctly formatted second line."\n\n' 537 'Relnote: "This is release note 2, and it\n' 538 'contains a correctly formatted second line."\n' 539 'Bug: 1234'), 540 )) 541 542 # Check some bad messages. 543 self._test_commit_messages( 544 rh.hooks.check_commit_msg_relnote_field_format, 545 False, 546 ( 547 'subj\n\nReleaseNote: This is a release note.\n', 548 'subj\n\nRelnotes: This is a release note.\n', 549 'subj\n\nRel-note: This is a release note.\n', 550 'subj\n\nrelnoTes: This is a release note.\n', 551 'subj\n\nrel-Note: This is a release note.\n', 552 ('subj\n\nRelnote: This is a release note.\n' 553 'It contains an incorrect second line.'), 554 ('subj\n\nRelnote: "This is a release note.\n' 555 'It contains multiple lines.\n' 556 'But it does not provide an ending quote.\n'), 557 ('subj\n\nRelnote: "This is a release note.\n' 558 'It contains multiple lines but no closing quote.\n' 559 'Test: my test "hello world"\n'), 560 ('subj\n\nRelnote: This is release note 1.\n' 561 'Relnote: "This is release note 2, and it\n' 562 'contains an incorrectly formatted third line.\n' 563 'Bug: 1234'), 564 ('subj\n\nRelnote: This is release note 1 with\n' 565 'an incorrectly formatted second line.\n\n' 566 'Relnote: "This is release note 2, and it\n' 567 'contains a correctly formatted second line."\n' 568 'Bug: 1234'), 569 ('subj\n\nRelnote: "This is release note 1 with\n' 570 'a correctly formatted second line."\n\n' 571 'Relnote: This is release note 2, and it\n' 572 'contains an incorrectly formatted second line.\n' 573 'Bug: 1234'), 574 )) 575 576 def test_commit_msg_relnote_for_current_txt(self, _mock_check, _mock_run): 577 """Verify the commit_msg_relnote_for_current_txt builtin hook.""" 578 diff_without_current_txt = ['bar/foo.txt', 579 'foo.cpp', 580 'foo.java', 581 'foo_current.java', 582 'foo_current.txt', 583 'baz/current.java', 584 'baz/foo_current.txt'] 585 diff_with_current_txt = diff_without_current_txt + ['current.txt'] 586 diff_with_subdir_current_txt = \ 587 diff_without_current_txt + ['foo/current.txt'] 588 diff_with_experimental_current_txt = \ 589 diff_without_current_txt + ['public_plus_experimental_current.txt'] 590 # Check some good messages. 591 self._test_commit_messages( 592 rh.hooks.check_commit_msg_relnote_for_current_txt, 593 True, 594 ( 595 'subj\n\nRelnote: This is a release note\n', 596 'subj\n\nRelnote: This is a release note.\n\nChange-Id: 1234', 597 ('subj\n\nRelnote: This is release note 1 with\n' 598 'an incorrectly formatted second line.\n\n' 599 'Relnote: "This is release note 2, and it\n' 600 'contains a correctly formatted second line."\n' 601 'Bug: 1234'), 602 ), 603 files=diff_with_current_txt, 604 ) 605 # Check some good messages. 606 self._test_commit_messages( 607 rh.hooks.check_commit_msg_relnote_for_current_txt, 608 True, 609 ( 610 'subj\n\nRelnote: This is a release note\n', 611 'subj\n\nRelnote: This is a release note.\n\nChange-Id: 1234', 612 ('subj\n\nRelnote: This is release note 1 with\n' 613 'an incorrectly formatted second line.\n\n' 614 'Relnote: "This is release note 2, and it\n' 615 'contains a correctly formatted second line."\n' 616 'Bug: 1234'), 617 ), 618 files=diff_with_experimental_current_txt, 619 ) 620 # Check some good messages. 621 self._test_commit_messages( 622 rh.hooks.check_commit_msg_relnote_for_current_txt, 623 True, 624 ( 625 'subj\n\nRelnote: This is a release note\n', 626 'subj\n\nRelnote: This is a release note.\n\nChange-Id: 1234', 627 ('subj\n\nRelnote: This is release note 1 with\n' 628 'an incorrectly formatted second line.\n\n' 629 'Relnote: "This is release note 2, and it\n' 630 'contains a correctly formatted second line."\n' 631 'Bug: 1234'), 632 ), 633 files=diff_with_subdir_current_txt, 634 ) 635 # Check some good messages. 636 self._test_commit_messages( 637 rh.hooks.check_commit_msg_relnote_for_current_txt, 638 True, 639 ( 640 'subj', 641 'subj\nBug: 12345\nChange-Id: 1234', 642 'subj\n\nRelnote: This is a release note\n', 643 'subj\n\nRelnote: This is a release note.\n\nChange-Id: 1234', 644 ('subj\n\nRelnote: This is release note 1 with\n' 645 'an incorrectly formatted second line.\n\n' 646 'Relnote: "This is release note 2, and it\n' 647 'contains a correctly formatted second line."\n' 648 'Bug: 1234'), 649 ), 650 files=diff_without_current_txt, 651 ) 652 # Check some bad messages. 653 self._test_commit_messages( 654 rh.hooks.check_commit_msg_relnote_for_current_txt, 655 False, 656 ( 657 'subj' 658 'subj\nBug: 12345\nChange-Id: 1234', 659 ), 660 files=diff_with_current_txt, 661 ) 662 # Check some bad messages. 663 self._test_commit_messages( 664 rh.hooks.check_commit_msg_relnote_for_current_txt, 665 False, 666 ( 667 'subj' 668 'subj\nBug: 12345\nChange-Id: 1234', 669 ), 670 files=diff_with_experimental_current_txt, 671 ) 672 # Check some bad messages. 673 self._test_commit_messages( 674 rh.hooks.check_commit_msg_relnote_for_current_txt, 675 False, 676 ( 677 'subj' 678 'subj\nBug: 12345\nChange-Id: 1234', 679 ), 680 files=diff_with_subdir_current_txt, 681 ) 682 683 def test_cpplint(self, mock_check, _mock_run): 684 """Verify the cpplint builtin hook.""" 685 self._test_file_filter(mock_check, rh.hooks.check_cpplint, 686 ('foo.cpp', 'foo.cxx')) 687 688 def test_gofmt(self, mock_check, _mock_run): 689 """Verify the gofmt builtin hook.""" 690 # First call should do nothing as there are no files to check. 691 ret = rh.hooks.check_gofmt( 692 self.project, 'commit', 'desc', (), options=self.options) 693 self.assertEqual(ret, None) 694 self.assertFalse(mock_check.called) 695 696 # Second call will have some results. 697 diff = [rh.git.RawDiffEntry(file='foo.go')] 698 ret = rh.hooks.check_gofmt( 699 self.project, 'commit', 'desc', diff, options=self.options) 700 self.assertNotEqual(ret, None) 701 702 def test_jsonlint(self, mock_check, _mock_run): 703 """Verify the jsonlint builtin hook.""" 704 # First call should do nothing as there are no files to check. 705 ret = rh.hooks.check_json( 706 self.project, 'commit', 'desc', (), options=self.options) 707 self.assertEqual(ret, None) 708 self.assertFalse(mock_check.called) 709 710 # TODO: Actually pass some valid/invalid json data down. 711 712 def test_pylint(self, mock_check, _mock_run): 713 """Verify the pylint builtin hook.""" 714 self._test_file_filter(mock_check, rh.hooks.check_pylint2, 715 ('foo.py',)) 716 717 def test_pylint2(self, mock_check, _mock_run): 718 """Verify the pylint2 builtin hook.""" 719 self._test_file_filter(mock_check, rh.hooks.check_pylint2, 720 ('foo.py',)) 721 722 def test_pylint3(self, mock_check, _mock_run): 723 """Verify the pylint3 builtin hook.""" 724 self._test_file_filter(mock_check, rh.hooks.check_pylint3, 725 ('foo.py',)) 726 727 def test_rustfmt(self, mock_check, _mock_run): 728 self._test_file_filter(mock_check, rh.hooks.check_rustfmt, 729 ('foo.rs',)) 730 731 def test_xmllint(self, mock_check, _mock_run): 732 """Verify the xmllint builtin hook.""" 733 self._test_file_filter(mock_check, rh.hooks.check_xmllint, 734 ('foo.xml',)) 735 736 def test_android_test_mapping_format(self, mock_check, _mock_run): 737 """Verify the android_test_mapping_format builtin hook.""" 738 # First call should do nothing as there are no files to check. 739 ret = rh.hooks.check_android_test_mapping( 740 self.project, 'commit', 'desc', (), options=self.options) 741 self.assertIsNone(ret) 742 self.assertFalse(mock_check.called) 743 744 # Second call will have some results. 745 diff = [rh.git.RawDiffEntry(file='TEST_MAPPING')] 746 ret = rh.hooks.check_android_test_mapping( 747 self.project, 'commit', 'desc', diff, options=self.options) 748 self.assertIsNotNone(ret) 749 750 751if __name__ == '__main__': 752 unittest.main() 753