presubmit_canned_checks_test.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2021 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import os.path
  6. import subprocess
  7. import sys
  8. import unittest
  9. from unittest import mock
  10. ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  11. sys.path.insert(0, ROOT_DIR)
  12. from testing_support.presubmit_canned_checks_test_mocks import (
  13. MockFile, MockAffectedFile, MockInputApi, MockOutputApi, MockChange)
  14. import presubmit_canned_checks
  15. # TODO: Should fix these warnings.
  16. # pylint: disable=line-too-long
  17. class InclusiveLanguageCheckTest(unittest.TestCase):
  18. def testBlockedTerms(self):
  19. input_api = MockInputApi()
  20. input_api.change.RepositoryRoot = lambda: ''
  21. input_api.presubmit_local_path = ''
  22. input_api.files = [
  23. MockFile(
  24. os.path.normpath(
  25. 'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
  26. 'some/dir 2 1',
  27. 'some/other/dir 2 1',
  28. ]),
  29. MockFile(
  30. os.path.normpath('some/ios/file.mm'),
  31. [
  32. 'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
  33. '}'
  34. ]),
  35. MockFile(
  36. os.path.normpath('some/mac/file.mm'),
  37. [
  38. 'TEST(SomeClassTest, SomeInteraction, BlackList) {', # nocheck
  39. '}'
  40. ]),
  41. MockFile(os.path.normpath('another/ios_file.mm'),
  42. ['class SomeTest : public testing::Test blocklist {};']),
  43. MockFile(os.path.normpath('some/ios/file_egtest.mm'),
  44. ['- (void)testSomething { V(whitelist); }']), # nocheck
  45. MockFile(
  46. os.path.normpath('some/ios/file_unittest.mm'),
  47. ['TEST_F(SomeTest, Whitelist) { V(allowlist); }']), # nocheck
  48. MockFile(
  49. os.path.normpath('some/doc/file.md'),
  50. [
  51. '# Title',
  52. 'Some markdown text includes master.', # nocheck
  53. ]),
  54. MockFile(
  55. os.path.normpath('some/doc/ok_file.md'),
  56. [
  57. '# Title',
  58. # This link contains a '//' which the matcher thinks is a
  59. # C-style comment, and the 'master' term appears after the
  60. # '//' in the URL, so it gets ignored as a side-effect.
  61. '[Ignored](https://git/project.git/+/master/foo)', # nocheck
  62. ]),
  63. MockFile(
  64. os.path.normpath('some/doc/branch_name_file.md'),
  65. [
  66. '# Title',
  67. # Matches appearing before `//` still trigger the check.
  68. '[src/master](https://git/p.git/+/master/foo)', # nocheck
  69. ]),
  70. MockFile(
  71. os.path.normpath('some/java/file/TestJavaDoc.java'),
  72. [
  73. '/**',
  74. ' * This line contains the word master,', # nocheck
  75. '* ignored because this is a comment. See {@link',
  76. ' * https://s/src/+/master:tools/README.md}', # nocheck
  77. ' */'
  78. ]),
  79. MockFile(
  80. os.path.normpath('some/java/file/TestJava.java'),
  81. [
  82. 'class TestJava {',
  83. ' public String master;', # nocheck
  84. '}'
  85. ]),
  86. MockFile(
  87. os.path.normpath('some/html/file.html'),
  88. [
  89. '<-- an existing html multiline comment',
  90. 'says "master" here', # nocheck
  91. 'in the comment -->'
  92. ])
  93. ]
  94. errors = presubmit_canned_checks.CheckInclusiveLanguage(
  95. input_api, MockOutputApi())
  96. self.assertEqual(1, len(errors))
  97. self.assertTrue(
  98. os.path.normpath('some/ios/file.mm') in errors[0].message)
  99. self.assertTrue(
  100. os.path.normpath('another/ios_file.mm') not in errors[0].message)
  101. self.assertTrue(
  102. os.path.normpath('some/mac/file.mm') in errors[0].message)
  103. self.assertTrue(
  104. os.path.normpath('some/ios/file_egtest.mm') in errors[0].message)
  105. self.assertTrue(
  106. os.path.normpath('some/ios/file_unittest.mm') in errors[0].message)
  107. self.assertTrue(
  108. os.path.normpath('some/doc/file.md') not in errors[0].message)
  109. self.assertTrue(
  110. os.path.normpath('some/doc/ok_file.md') not in errors[0].message)
  111. self.assertTrue(
  112. os.path.normpath('some/doc/branch_name_file.md') not in
  113. errors[0].message)
  114. self.assertTrue(
  115. os.path.normpath('some/java/file/TestJavaDoc.java') not in
  116. errors[0].message)
  117. self.assertTrue(
  118. os.path.normpath('some/java/file/TestJava.java') not in
  119. errors[0].message)
  120. self.assertTrue(
  121. os.path.normpath('some/html/file.html') not in errors[0].message)
  122. def testBlockedTermsWithLegacy(self):
  123. input_api = MockInputApi()
  124. input_api.change.RepositoryRoot = lambda: ''
  125. input_api.presubmit_local_path = ''
  126. input_api.files = [
  127. MockFile(
  128. os.path.normpath(
  129. 'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
  130. 'some/ios 2 1',
  131. 'some/other/dir 2 1',
  132. ]),
  133. MockFile(
  134. os.path.normpath('some/ios/file.mm'),
  135. [
  136. 'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
  137. '}'
  138. ]),
  139. MockFile(
  140. os.path.normpath('some/ios/subdir/file.mm'),
  141. [
  142. 'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
  143. '}'
  144. ]),
  145. MockFile(
  146. os.path.normpath('some/mac/file.mm'),
  147. [
  148. 'TEST(SomeClassTest, SomeInteraction, BlackList) {', # nocheck
  149. '}'
  150. ]),
  151. MockFile(os.path.normpath('another/ios_file.mm'),
  152. ['class SomeTest : public testing::Test blocklist {};']),
  153. MockFile(os.path.normpath('some/ios/file_egtest.mm'),
  154. ['- (void)testSomething { V(whitelist); }']), # nocheck
  155. MockFile(
  156. os.path.normpath('some/ios/file_unittest.mm'),
  157. ['TEST_F(SomeTest, Whitelist) { V(allowlist); }']), # nocheck
  158. ]
  159. errors = presubmit_canned_checks.CheckInclusiveLanguage(
  160. input_api, MockOutputApi())
  161. self.assertEqual(1, len(errors))
  162. self.assertTrue(
  163. os.path.normpath('some/ios/file.mm') not in errors[0].message)
  164. self.assertTrue(
  165. os.path.normpath('some/ios/subdir/file.mm') in errors[0].message)
  166. self.assertTrue(
  167. os.path.normpath('another/ios_file.mm') not in errors[0].message)
  168. self.assertTrue(
  169. os.path.normpath('some/mac/file.mm') in errors[0].message)
  170. self.assertTrue(
  171. os.path.normpath('some/ios/file_egtest.mm') not in
  172. errors[0].message)
  173. self.assertTrue(
  174. os.path.normpath('some/ios/file_unittest.mm') not in
  175. errors[0].message)
  176. def testBlockedTermsWithNocheck(self):
  177. input_api = MockInputApi()
  178. input_api.change.RepositoryRoot = lambda: ''
  179. input_api.presubmit_local_path = ''
  180. input_api.files = [
  181. MockFile(
  182. os.path.normpath(
  183. 'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
  184. 'some/dir 2 1',
  185. 'some/other/dir 2 1',
  186. ]),
  187. MockFile(
  188. os.path.normpath('some/ios/file.mm'),
  189. [
  190. 'TEST(SomeClassTest, SomeInteraction, ',
  191. ' blacklist) { // nocheck', # nocheck
  192. '}'
  193. ]),
  194. MockFile(
  195. os.path.normpath('some/mac/file.mm'),
  196. [
  197. 'TEST(SomeClassTest, SomeInteraction, ',
  198. 'BlackList) { // nocheck', # nocheck
  199. '}'
  200. ]),
  201. MockFile(os.path.normpath('another/ios_file.mm'),
  202. ['class SomeTest : public testing::Test blocklist {};']),
  203. MockFile(os.path.normpath('some/ios/file_egtest.mm'),
  204. ['- (void)testSomething { ', 'V(whitelist); } // nocheck'
  205. ]), # nocheck
  206. MockFile(
  207. os.path.normpath('some/ios/file_unittest.mm'),
  208. [
  209. 'TEST_F(SomeTest, Whitelist) // nocheck', # nocheck
  210. ' { V(allowlist); }'
  211. ]),
  212. MockFile(
  213. os.path.normpath('some/doc/file.md'),
  214. [
  215. 'Master in markdown <!-- nocheck -->', # nocheck
  216. '## Subheading is okay'
  217. ]),
  218. MockFile(
  219. os.path.normpath('some/java/file/TestJava.java'),
  220. [
  221. 'class TestJava {',
  222. ' public String master; // nocheck', # nocheck
  223. '}'
  224. ]),
  225. MockFile(
  226. os.path.normpath('some/html/file.html'),
  227. [
  228. '<-- an existing html multiline comment',
  229. 'says "master" here --><!-- nocheck -->', # nocheck
  230. '<!-- in the comment -->'
  231. ])
  232. ]
  233. errors = presubmit_canned_checks.CheckInclusiveLanguage(
  234. input_api, MockOutputApi())
  235. self.assertEqual(0, len(errors))
  236. def testTopLevelDirExcempt(self):
  237. input_api = MockInputApi()
  238. input_api.change.RepositoryRoot = lambda: ''
  239. input_api.presubmit_local_path = ''
  240. input_api.files = [
  241. MockFile(
  242. os.path.normpath(
  243. 'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
  244. '. 2 1',
  245. 'some/other/dir 2 1',
  246. ]),
  247. MockFile(
  248. os.path.normpath('presubmit_canned_checks_test.py'),
  249. [
  250. 'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
  251. '}'
  252. ]),
  253. MockFile(
  254. os.path.normpath('presubmit_canned_checks.py'),
  255. ['- (void)testSth { V(whitelist); } // nocheck']), # nocheck
  256. ]
  257. errors = presubmit_canned_checks.CheckInclusiveLanguage(
  258. input_api, MockOutputApi())
  259. self.assertEqual(1, len(errors))
  260. self.assertTrue(
  261. os.path.normpath('presubmit_canned_checks_test.py') in
  262. errors[0].message)
  263. self.assertTrue(
  264. os.path.normpath('presubmit_canned_checks.py') not in
  265. errors[0].message)
  266. def testChangeIsForSomeOtherRepo(self):
  267. input_api = MockInputApi()
  268. input_api.change.RepositoryRoot = lambda: 'v8'
  269. input_api.presubmit_local_path = ''
  270. input_api.files = [
  271. MockFile(
  272. os.path.normpath('some_file'),
  273. [
  274. '# this is a blacklist', # nocheck
  275. ]),
  276. ]
  277. errors = presubmit_canned_checks.CheckInclusiveLanguage(
  278. input_api, MockOutputApi())
  279. self.assertEqual([], errors)
  280. def testDirExemptWithComment(self):
  281. input_api = MockInputApi()
  282. input_api.change.RepositoryRoot = lambda: ''
  283. input_api.presubmit_local_path = ''
  284. input_api.files = [
  285. MockFile(
  286. os.path.normpath(
  287. 'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
  288. '# this is a comment',
  289. 'dir1',
  290. '# dir2',
  291. ]),
  292. # this should be excluded
  293. MockFile(
  294. os.path.normpath('dir1/1.py'),
  295. [
  296. 'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
  297. '}'
  298. ]),
  299. # this should not be excluded
  300. MockFile(os.path.normpath('dir2/2.py'),
  301. ['- (void)testSth { V(whitelist); }']), # nocheck
  302. ]
  303. errors = presubmit_canned_checks.CheckInclusiveLanguage(
  304. input_api, MockOutputApi())
  305. self.assertEqual(1, len(errors))
  306. self.assertTrue(os.path.normpath('dir1/1.py') not in errors[0].message)
  307. self.assertTrue(os.path.normpath('dir2/2.py') in errors[0].message)
  308. class DescriptionChecksTest(unittest.TestCase):
  309. def testCheckDescriptionUsesColonInsteadOfEquals(self):
  310. input_api = MockInputApi()
  311. input_api.change.RepositoryRoot = lambda: ''
  312. input_api.presubmit_local_path = ''
  313. # Verify error in case of the attempt to use "Bug=".
  314. input_api.change = MockChange([], 'Broken description\nBug=123')
  315. errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
  316. input_api, MockOutputApi())
  317. self.assertEqual(1, len(errors))
  318. self.assertTrue('Bug=' in errors[0].message)
  319. # Verify error in case of the attempt to use "Fixed=".
  320. input_api.change = MockChange([], 'Broken description\nFixed=123')
  321. errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
  322. input_api, MockOutputApi())
  323. self.assertEqual(1, len(errors))
  324. self.assertTrue('Fixed=' in errors[0].message)
  325. # Verify error in case of the attempt to use the lower case "bug=".
  326. input_api.change = MockChange([],
  327. 'Broken description lowercase\nbug=123')
  328. errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
  329. input_api, MockOutputApi())
  330. self.assertEqual(1, len(errors))
  331. self.assertTrue('Bug=' in errors[0].message)
  332. # Verify no error in case of "Bug:"
  333. input_api.change = MockChange([], 'Correct description\nBug: 123')
  334. errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
  335. input_api, MockOutputApi())
  336. self.assertEqual(0, len(errors))
  337. # Verify no error in case of "Fixed:"
  338. input_api.change = MockChange([], 'Correct description\nFixed: 123')
  339. errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
  340. input_api, MockOutputApi())
  341. self.assertEqual(0, len(errors))
  342. class ChromiumDependencyMetadataCheckTest(unittest.TestCase):
  343. def testDefaultFileFilter(self):
  344. """Checks the default file filter limits the scope to Chromium dependency
  345. metadata files.
  346. """
  347. input_api = MockInputApi()
  348. input_api.change.RepositoryRoot = lambda: ''
  349. input_api.files = [
  350. MockFile(os.path.normpath('foo/README.md'), ['Shipped: no?']),
  351. MockFile(os.path.normpath('foo/main.py'), ['Shipped: yes?']),
  352. ]
  353. results = presubmit_canned_checks.CheckChromiumDependencyMetadata(
  354. input_api, MockOutputApi())
  355. self.assertEqual(len(results), 0)
  356. def testSkipDeletedFiles(self):
  357. """Checks validation is skipped for deleted files."""
  358. input_api = MockInputApi()
  359. input_api.change.RepositoryRoot = lambda: ''
  360. input_api.files = [
  361. MockFile(os.path.normpath('foo/README.chromium'), ['No fields'],
  362. action='D'),
  363. ]
  364. results = presubmit_canned_checks.CheckChromiumDependencyMetadata(
  365. input_api, MockOutputApi())
  366. self.assertEqual(len(results), 0)
  367. def testFeedbackForNoMetadata(self):
  368. """Checks presubmit results are returned for files without any metadata."""
  369. input_api = MockInputApi()
  370. input_api.change.RepositoryRoot = lambda: ''
  371. input_api.files = [
  372. MockFile(os.path.normpath('foo/README.chromium'), ['No fields']),
  373. ]
  374. results = presubmit_canned_checks.CheckChromiumDependencyMetadata(
  375. input_api, MockOutputApi())
  376. self.assertEqual(len(results), 1)
  377. self.assertTrue("No dependency metadata" in results[0].message)
  378. def testFeedbackForInvalidMetadata(self):
  379. """Checks presubmit results are returned for files with invalid metadata."""
  380. input_api = MockInputApi()
  381. input_api.change.RepositoryRoot = lambda: ''
  382. test_file = MockFile(os.path.normpath('foo/README.chromium'),
  383. ['Shipped: yes?'])
  384. input_api.files = [test_file]
  385. results = presubmit_canned_checks.CheckChromiumDependencyMetadata(
  386. input_api, MockOutputApi())
  387. # There should be 9 results due to
  388. # - missing 5 mandatory fields: Name, URL, Version, License, and
  389. # Security Critical
  390. # - 1 error for insufficent versioning info
  391. # - missing 2 required fields: License File, and
  392. # License Android Compatible
  393. # - Shipped should be only 'yes' or 'no'.
  394. self.assertEqual(len(results), 9)
  395. # Check each presubmit result is associated with the test file.
  396. for result in results:
  397. self.assertEqual(len(result.items), 1)
  398. self.assertEqual(result.items[0], test_file)
  399. class CheckUpdateOwnersFileReferences(unittest.TestCase):
  400. def testShowsWarningIfDeleting(self):
  401. input_api = MockInputApi()
  402. input_api.files = [
  403. MockFile(os.path.normpath('foo/OWNERS'), [], [], action='D'),
  404. ]
  405. results = presubmit_canned_checks.CheckUpdateOwnersFileReferences(
  406. input_api, MockOutputApi())
  407. self.assertEqual(1, len(results))
  408. self.assertEqual('warning', results[0].type)
  409. self.assertEqual(1, len(results[0].items))
  410. def testShowsWarningIfMoving(self):
  411. input_api = MockInputApi()
  412. input_api.files = [
  413. MockFile(os.path.normpath('new_directory/OWNERS'), [], [],
  414. action='A'),
  415. MockFile(os.path.normpath('old_directory/OWNERS'), [], [],
  416. action='D'),
  417. ]
  418. results = presubmit_canned_checks.CheckUpdateOwnersFileReferences(
  419. input_api, MockOutputApi())
  420. self.assertEqual(1, len(results))
  421. self.assertEqual('warning', results[0].type)
  422. self.assertEqual(1, len(results[0].items))
  423. def testNoWarningIfAdding(self):
  424. input_api = MockInputApi()
  425. input_api.files = [
  426. MockFile(os.path.normpath('foo/OWNERS'), [], [], action='A'),
  427. ]
  428. results = presubmit_canned_checks.CheckUpdateOwnersFileReferences(
  429. input_api, MockOutputApi())
  430. self.assertEqual(0, len(results))
  431. class CheckNoNewGitFilesAddedInDependenciesTest(unittest.TestCase):
  432. @mock.patch('presubmit_canned_checks._readDeps')
  433. def testNonNested(self, readDeps):
  434. readDeps.return_value = '''deps = {
  435. 'src/foo': {'url': 'bar', 'condition': 'non_git_source'},
  436. 'src/components/foo/bar': {'url': 'bar', 'condition': 'non_git_source'},
  437. }'''
  438. input_api = MockInputApi()
  439. input_api.files = [
  440. MockFile('components/foo/file1.java', ['otherFunction']),
  441. MockFile('components/foo/file2.java', ['hasSyncConsent']),
  442. MockFile('chrome/foo/file3.java', ['canSyncFeatureStart']),
  443. MockFile('chrome/foo/file4.java', ['isSyncFeatureEnabled']),
  444. MockFile('chrome/foo/file5.java', ['isSyncFeatureActive']),
  445. ]
  446. results = presubmit_canned_checks.CheckNoNewGitFilesAddedInDependencies(
  447. input_api, MockOutputApi())
  448. self.assertEqual(0, len(results))
  449. @mock.patch('presubmit_canned_checks._readDeps')
  450. def testCollision(self, readDeps):
  451. readDeps.return_value = '''deps = {
  452. 'src/foo': {'url': 'bar', 'condition': 'non_git_source'},
  453. 'src/baz': {'url': 'baz'},
  454. }'''
  455. input_api = MockInputApi()
  456. input_api.files = [
  457. MockAffectedFile('fo', 'content'), # no conflict
  458. MockAffectedFile('foo', 'content'), # conflict
  459. MockAffectedFile('foo/bar', 'content'), # conflict
  460. MockAffectedFile('baz/qux', 'content'), # conflict, but ignored
  461. ]
  462. results = presubmit_canned_checks.CheckNoNewGitFilesAddedInDependencies(
  463. input_api, MockOutputApi())
  464. self.assertEqual(2, len(results))
  465. self.assertIn('File: foo', str(results))
  466. self.assertIn('File: foo/bar', str(results))
  467. @mock.patch('presubmit_canned_checks._readDeps')
  468. def testNoDeps(self, readDeps):
  469. readDeps.return_value = '' # Empty deps
  470. input_api = MockInputApi()
  471. input_api.files = [
  472. MockAffectedFile('fo', 'content'), # no conflict
  473. MockAffectedFile('foo', 'content'), # conflict
  474. MockAffectedFile('foo/bar', 'content'), # conflict
  475. MockAffectedFile('baz/qux', 'content'), # conflict, but ignored
  476. ]
  477. results = presubmit_canned_checks.CheckNoNewGitFilesAddedInDependencies(
  478. input_api, MockOutputApi())
  479. self.assertEqual(0, len(results))
  480. class CheckNewDEPSHooksHasRequiredReviewersTest(unittest.TestCase):
  481. def setUp(self):
  482. self.input_api = MockInputApi()
  483. self.input_api.change = MockChange([], issue=123)
  484. self.input_api.change.RepositoryRoot = lambda: ''
  485. def test_no_gerrit_cl(self):
  486. self.input_api.change = MockChange([], issue=None)
  487. results = presubmit_canned_checks.CheckNewDEPSHooksHasRequiredReviewers(
  488. self.input_api, MockOutputApi())
  489. self.assertEqual(0, len(results))
  490. def test_no_deps_file_change(self):
  491. self.input_api.files = [
  492. MockAffectedFile('foo.py', 'content'),
  493. ]
  494. results = presubmit_canned_checks.CheckNewDEPSHooksHasRequiredReviewers(
  495. self.input_api, MockOutputApi())
  496. self.assertEqual(0, len(results))
  497. def test_new_deps_hook(self):
  498. gerrit_mock = mock.Mock()
  499. self.input_api.gerrit = gerrit_mock
  500. test_cases = [
  501. {
  502. 'name': 'no new hooks',
  503. 'old_contents': ['hooks = []'],
  504. 'new_contents': ['hooks = []'],
  505. 'reviewers': [],
  506. },
  507. {
  508. 'name':
  509. 'add new hook and require review',
  510. 'old_contents': ['hooks = [{"name": "old_hook"}]'],
  511. 'new_contents': [
  512. 'hooks = [{"name": "old_hook"}, {"name": "new_hook"}, {"name": "new_hook_2"}]'
  513. ],
  514. 'reviewers': [],
  515. 'expected_error_msg':
  516. 'New DEPS hooks (new_hook, new_hook_2) are found. Please '
  517. 'request review from one of the following reviewers:\n '
  518. '* foo@chromium.org\n * bar@chromium.org\n * baz@chromium.org'
  519. },
  520. {
  521. 'name':
  522. 'add new hook and require approval',
  523. 'old_contents': ['hooks = [{"name": "old_hook"}]'],
  524. 'new_contents': [
  525. 'hooks = [{"name": "old_hook"}, {"name": "new_hook"}, {"name": "new_hook_2"}]'
  526. ],
  527. 'submitting':
  528. True,
  529. 'reviewers': ['not_relevant@chromium.org'],
  530. 'expected_error_msg':
  531. 'New DEPS hooks (new_hook, new_hook_2) are found. The CL must '
  532. 'be approved by one of the following reviewers:\n'
  533. ' * foo@chromium.org\n * bar@chromium.org\n * baz@chromium.org'
  534. },
  535. {
  536. 'name':
  537. 'add new hook and reviewer is already added',
  538. 'old_contents': ['hooks = [{"name": "old_hook"}]'],
  539. 'new_contents': [
  540. 'hooks = [{"name": "old_hook"}, {"name": "new_hook"}, {"name": "new_hook_2"}]'
  541. ],
  542. 'reviewers': ['baz@chromium.org'],
  543. },
  544. {
  545. 'name':
  546. 'add new hook and reviewer already approves',
  547. 'old_contents': ['hooks = [{"name": "old_hook"}]'],
  548. 'new_contents': [
  549. 'hooks = [{"name": "old_hook"}, {"name": "new_hook"}, {"name": "new_hook_2"}]'
  550. ],
  551. 'submitting':
  552. True,
  553. 'reviewers': ['foo@chromium.org'],
  554. },
  555. {
  556. 'name':
  557. 'change existing hook',
  558. 'old_contents': [
  559. 'hooks = [{"name": "existing_hook", "action": ["run", "./test.sh"]}]'
  560. ],
  561. 'new_contents': [
  562. 'hooks = [{"name": "existing_hook", "action": ["run", "./test_v2.sh"]}]'
  563. ],
  564. 'reviewers': [],
  565. },
  566. {
  567. 'name':
  568. 'remove hook',
  569. 'old_contents':
  570. ['hooks = [{"name": "old_hook"}, {"name": "hook_to_remove"}]'],
  571. 'new_contents': ['hooks = [{"name": "old_hook"}]'],
  572. 'reviewers': [],
  573. },
  574. ]
  575. for case in test_cases:
  576. with self.subTest(case_name=case['name']):
  577. self.input_api.files = [
  578. MockFile('OWNERS', [
  579. 'per-file DEPS=foo@chromium.org # For new DEPS hook',
  580. 'per-file DEPS=bar@chromium.org, baz@chromium.org # For new DEPS hook'
  581. ]),
  582. MockAffectedFile('DEPS',
  583. old_contents=case['old_contents'],
  584. new_contents=case['new_contents']),
  585. ]
  586. if case.get('submitting', False):
  587. self.input_api.is_committing = True
  588. self.input_api.dry_run = False
  589. gerrit_mock.GetChangeReviewers.return_value = case['reviewers']
  590. results = presubmit_canned_checks.CheckNewDEPSHooksHasRequiredReviewers(
  591. self.input_api,
  592. MockOutputApi(),
  593. )
  594. if 'expected_error_msg' in case:
  595. self.assertEqual(1, len(results))
  596. self.assertEqual(case['expected_error_msg'],
  597. results[0].message)
  598. else:
  599. self.assertEqual(0, len(results))
  600. if __name__ == '__main__':
  601. unittest.main()