2 # Copyright 2012 The Chromium Authors
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
13 from PRESUBMIT_test_mocks import MockFile, MockAffectedFile
14 from PRESUBMIT_test_mocks import MockInputApi, MockOutputApi
17 _TEST_DATA_DIR = 'base/test/data/presubmit'
20 class VersionControlConflictsTest(unittest.TestCase):
21 def testTypicalConflict(self):
22 lines = ['<<<<<<< HEAD',
23 ' base::ScopedTempDir temp_dir_;',
25 ' ScopedTempDir temp_dir_;',
27 errors = PRESUBMIT._CheckForVersionControlConflictsInFile(
28 MockInputApi(), MockFile('some/path/foo_platform.cc', lines))
29 self.assertEqual(3, len(errors))
30 self.assertTrue('1' in errors[0])
31 self.assertTrue('3' in errors[1])
32 self.assertTrue('5' in errors[2])
34 def testIgnoresReadmes(self):
35 lines = ['A First Level Header',
36 '====================',
38 'A Second Level Header',
39 '---------------------']
40 errors = PRESUBMIT._CheckForVersionControlConflictsInFile(
41 MockInputApi(), MockFile('some/polymer/README.md', lines))
42 self.assertEqual(0, len(errors))
45 class BadExtensionsTest(unittest.TestCase):
46 def testBadRejFile(self):
47 mock_input_api = MockInputApi()
48 mock_input_api.files = [
49 MockFile('some/path/foo.cc', ''),
50 MockFile('some/path/foo.cc.rej', ''),
51 MockFile('some/path2/bar.h.rej', ''),
54 results = PRESUBMIT.CheckPatchFiles(mock_input_api, MockOutputApi())
55 self.assertEqual(1, len(results))
56 self.assertEqual(2, len(results[0].items))
57 self.assertTrue('foo.cc.rej' in results[0].items[0])
58 self.assertTrue('bar.h.rej' in results[0].items[1])
60 def testBadOrigFile(self):
61 mock_input_api = MockInputApi()
62 mock_input_api.files = [
63 MockFile('other/path/qux.h.orig', ''),
64 MockFile('other/path/qux.h', ''),
65 MockFile('other/path/qux.cc', ''),
68 results = PRESUBMIT.CheckPatchFiles(mock_input_api, MockOutputApi())
69 self.assertEqual(1, len(results))
70 self.assertEqual(1, len(results[0].items))
71 self.assertTrue('qux.h.orig' in results[0].items[0])
73 def testGoodFiles(self):
74 mock_input_api = MockInputApi()
75 mock_input_api.files = [
76 MockFile('other/path/qux.h', ''),
77 MockFile('other/path/qux.cc', ''),
79 results = PRESUBMIT.CheckPatchFiles(mock_input_api, MockOutputApi())
80 self.assertEqual(0, len(results))
83 class CheckForSuperfluousStlIncludesInHeadersTest(unittest.TestCase):
84 def testGoodFiles(self):
85 mock_input_api = MockInputApi()
86 mock_input_api.files = [
87 # The check is not smart enough to figure out which definitions correspond
89 MockFile('other/path/foo.h',
92 # The check is not smart enough to do IWYU.
93 MockFile('other/path/bar.h',
94 ['#include "base/check.h"',
96 MockFile('other/path/qux.h',
97 ['#include "base/stl_util.h"',
99 MockFile('other/path/baz.h',
100 ['#include "set/vector.h"',
102 # The check is only for header files.
103 MockFile('other/path/not_checked.cc',
104 ['#include <vector>',
107 results = PRESUBMIT.CheckForSuperfluousStlIncludesInHeaders(
108 mock_input_api, MockOutputApi())
109 self.assertEqual(0, len(results))
111 def testBadFiles(self):
112 mock_input_api = MockInputApi()
113 mock_input_api.files = [
114 MockFile('other/path/foo.h',
115 ['#include <vector>',
117 MockFile('other/path/bar.h',
118 ['#include <limits>',
120 'no_std_namespace']),
122 results = PRESUBMIT.CheckForSuperfluousStlIncludesInHeaders(
123 mock_input_api, MockOutputApi())
124 self.assertEqual(1, len(results))
125 self.assertTrue('foo.h: Includes STL' in results[0].message)
126 self.assertTrue('bar.h: Includes STL' in results[0].message)
129 class CheckSingletonInHeadersTest(unittest.TestCase):
130 def testSingletonInArbitraryHeader(self):
131 diff_singleton_h = ['base::subtle::AtomicWord '
132 'base::Singleton<Type, Traits, DifferentiatingType>::']
133 diff_foo_h = ['// base::Singleton<Foo> in comment.',
134 'friend class base::Singleton<Foo>']
135 diff_foo2_h = [' //Foo* bar = base::Singleton<Foo>::get();']
136 diff_bad_h = ['Foo* foo = base::Singleton<Foo>::get();']
137 mock_input_api = MockInputApi()
138 mock_input_api.files = [MockAffectedFile('base/memory/singleton.h',
140 MockAffectedFile('foo.h', diff_foo_h),
141 MockAffectedFile('foo2.h', diff_foo2_h),
142 MockAffectedFile('bad.h', diff_bad_h)]
143 warnings = PRESUBMIT.CheckSingletonInHeaders(mock_input_api,
145 self.assertEqual(1, len(warnings))
146 self.assertEqual(1, len(warnings[0].items))
147 self.assertEqual('error', warnings[0].type)
148 self.assertTrue('Found base::Singleton<T>' in warnings[0].message)
150 def testSingletonInCC(self):
151 diff_cc = ['Foo* foo = base::Singleton<Foo>::get();']
152 mock_input_api = MockInputApi()
153 mock_input_api.files = [MockAffectedFile('some/path/foo.cc', diff_cc)]
154 warnings = PRESUBMIT.CheckSingletonInHeaders(mock_input_api,
156 self.assertEqual(0, len(warnings))
159 class DeprecatedOSMacroNamesTest(unittest.TestCase):
160 def testDeprecatedOSMacroNames(self):
161 lines = ['#if defined(OS_WIN)',
162 ' #elif defined(OS_WINDOW)',
163 ' # if defined(OS_MAC) || defined(OS_CHROME)']
164 errors = PRESUBMIT._CheckForDeprecatedOSMacrosInFile(
165 MockInputApi(), MockFile('some/path/foo_platform.cc', lines))
166 self.assertEqual(len(lines) + 1, len(errors))
167 self.assertTrue(':1: defined(OS_WIN) -> BUILDFLAG(IS_WIN)' in errors[0])
170 class InvalidIfDefinedMacroNamesTest(unittest.TestCase):
171 def testInvalidIfDefinedMacroNames(self):
172 lines = ['#if defined(TARGET_IPHONE_SIMULATOR)',
173 '#if !defined(TARGET_IPHONE_SIMULATOR)',
174 '#elif defined(TARGET_IPHONE_SIMULATOR)',
175 '#ifdef TARGET_IPHONE_SIMULATOR',
176 ' # ifdef TARGET_IPHONE_SIMULATOR',
177 '# if defined(VALID) || defined(TARGET_IPHONE_SIMULATOR)',
178 '# else // defined(TARGET_IPHONE_SIMULATOR)',
179 '#endif // defined(TARGET_IPHONE_SIMULATOR)']
180 errors = PRESUBMIT._CheckForInvalidIfDefinedMacrosInFile(
181 MockInputApi(), MockFile('some/path/source.mm', lines))
182 self.assertEqual(len(lines), len(errors))
184 def testValidIfDefinedMacroNames(self):
185 lines = ['#if defined(FOO)',
187 errors = PRESUBMIT._CheckForInvalidIfDefinedMacrosInFile(
188 MockInputApi(), MockFile('some/path/source.cc', lines))
189 self.assertEqual(0, len(errors))
192 class CheckAddedDepsHaveTestApprovalsTest(unittest.TestCase):
194 def calculate(self, old_include_rules, old_specific_include_rules,
195 new_include_rules, new_specific_include_rules):
196 return PRESUBMIT._CalculateAddedDeps(
197 os.path, 'include_rules = %r\nspecific_include_rules = %r' % (
198 old_include_rules, old_specific_include_rules),
199 'include_rules = %r\nspecific_include_rules = %r' % (
200 new_include_rules, new_specific_include_rules))
202 def testCalculateAddedDeps(self):
203 old_include_rules = [
212 old_specific_include_rules = {
218 new_include_rules = [
225 '+grit/generated_resources.h",',
229 '+' + os.path.join('third_party', 'WebKit'),
231 new_specific_include_rules = {
241 os.path.join('chrome', 'DEPS'),
242 os.path.join('gpu', 'DEPS'),
243 os.path.join('components', 'DEPS'),
244 os.path.join('policy', 'DEPS'),
245 os.path.join('third_party', 'WebKit', 'DEPS'),
249 self.calculate(old_include_rules, old_specific_include_rules,
250 new_include_rules, new_specific_include_rules))
252 def testCalculateAddedDepsIgnoresPermutations(self):
253 old_include_rules = [
257 new_include_rules = [
261 self.assertEqual(set(),
262 self.calculate(old_include_rules, {}, new_include_rules,
266 class JSONParsingTest(unittest.TestCase):
267 def testSuccess(self):
268 input_api = MockInputApi()
269 filename = 'valid_json.json'
270 contents = ['// This is a comment.',
272 ' "key1": ["value1", "value2"],',
273 ' "key2": 3 // This is an inline comment.',
276 input_api.files = [MockFile(filename, contents)]
277 self.assertEqual(None,
278 PRESUBMIT._GetJSONParseError(input_api, filename))
280 def testFailure(self):
281 input_api = MockInputApi()
283 ('invalid_json_1.json',
285 'Expecting property name'),
286 ('invalid_json_2.json',
288 '{ "hello": "world }'],
289 'Unterminated string starting at:'),
290 ('invalid_json_3.json',
291 ['{ "a": "b", "c": "d", }'],
292 'Expecting property name'),
293 ('invalid_json_4.json',
294 ['{ "a": "b" "c": "d" }'],
295 "Expecting ',' delimiter:"),
298 input_api.files = [MockFile(filename, contents)
299 for (filename, contents, _) in test_data]
301 for (filename, _, expected_error) in test_data:
302 actual_error = PRESUBMIT._GetJSONParseError(input_api, filename)
303 self.assertTrue(expected_error in str(actual_error),
304 "'%s' not found in '%s'" % (expected_error, actual_error))
306 def testNoEatComments(self):
307 input_api = MockInputApi()
308 file_with_comments = 'file_with_comments.json'
309 contents_with_comments = ['// This is a comment.',
311 ' "key1": ["value1", "value2"],',
312 ' "key2": 3 // This is an inline comment.',
315 file_without_comments = 'file_without_comments.json'
316 contents_without_comments = ['{',
317 ' "key1": ["value1", "value2"],',
321 input_api.files = [MockFile(file_with_comments, contents_with_comments),
322 MockFile(file_without_comments,
323 contents_without_comments)]
325 self.assertNotEqual(None,
326 str(PRESUBMIT._GetJSONParseError(input_api,
328 eat_comments=False)))
329 self.assertEqual(None,
330 PRESUBMIT._GetJSONParseError(input_api,
331 file_without_comments,
335 class IDLParsingTest(unittest.TestCase):
336 def testSuccess(self):
337 input_api = MockInputApi()
338 filename = 'valid_idl_basics.idl'
339 contents = ['// Tests a valid IDL file.',
340 'namespace idl_basics {',
346 ' dictionary MyType1 {',
350 ' callback Callback1 = void();',
351 ' callback Callback2 = void(long x);',
352 ' callback Callback3 = void(MyType1 arg);',
353 ' callback Callback4 = void(EnumType type);',
355 ' interface Functions {',
356 ' static void function1();',
357 ' static void function2(long x);',
358 ' static void function3(MyType1 arg);',
359 ' static void function4(Callback1 cb);',
360 ' static void function5(Callback2 cb);',
361 ' static void function6(Callback3 cb);',
362 ' static void function7(Callback4 cb);',
365 ' interface Events {',
366 ' static void onFoo1();',
367 ' static void onFoo2(long x);',
368 ' static void onFoo2(MyType1 arg);',
369 ' static void onFoo3(EnumType type);',
373 input_api.files = [MockFile(filename, contents)]
374 self.assertEqual(None,
375 PRESUBMIT._GetIDLParseError(input_api, filename))
377 def testFailure(self):
378 input_api = MockInputApi()
380 ('invalid_idl_1.idl',
387 'Unexpected "{" after keyword "dictionary".\n'),
388 # TODO(yoz): Disabled because it causes the IDL parser to hang.
389 # See crbug.com/363830.
390 # ('invalid_idl_2.idl',
391 # (['namespace test {',
392 # ' dictionary MissingSemicolon {',
397 # 'Unexpected symbol DOMString after symbol a.'),
398 ('invalid_idl_3.idl',
401 ' enum MissingComma {',
406 'Unexpected symbol name2 after symbol name1.'),
407 ('invalid_idl_4.idl',
410 ' enum TrailingComma {',
415 'Trailing comma in block.'),
416 ('invalid_idl_5.idl',
419 ' callback Callback1 = void(;',
421 'Unexpected ";" after "(".'),
422 ('invalid_idl_6.idl',
425 ' callback Callback1 = void(long );',
427 'Unexpected ")" after symbol long.'),
428 ('invalid_idl_7.idl',
431 ' interace Events {',
432 ' static void onFoo1();',
435 'Unexpected symbol Events after symbol interace.'),
436 ('invalid_idl_8.idl',
439 ' interface NotEvent {',
440 ' static void onFoo1();',
443 'Did not process Interface Interface(NotEvent)'),
444 ('invalid_idl_9.idl',
448 ' static void function1();',
451 'Interface missing name.'),
454 input_api.files = [MockFile(filename, contents)
455 for (filename, contents, _) in test_data]
457 for (filename, _, expected_error) in test_data:
458 actual_error = PRESUBMIT._GetIDLParseError(input_api, filename)
459 self.assertTrue(expected_error in str(actual_error),
460 "'%s' not found in '%s'" % (expected_error, actual_error))
463 class UserMetricsActionTest(unittest.TestCase):
464 def testUserMetricsActionInActions(self):
465 input_api = MockInputApi()
466 file_with_user_action = 'file_with_user_action.cc'
467 contents_with_user_action = [
468 'base::UserMetricsAction("AboutChrome")'
471 input_api.files = [MockFile(file_with_user_action,
472 contents_with_user_action)]
475 [], PRESUBMIT.CheckUserActionUpdate(input_api, MockOutputApi()))
477 def testUserMetricsActionNotAddedToActions(self):
478 input_api = MockInputApi()
479 file_with_user_action = 'file_with_user_action.cc'
480 contents_with_user_action = [
481 'base::UserMetricsAction("NotInActionsXml")'
484 input_api.files = [MockFile(file_with_user_action,
485 contents_with_user_action)]
487 output = PRESUBMIT.CheckUserActionUpdate(input_api, MockOutputApi())
489 ('File %s line %d: %s is missing in '
490 'tools/metrics/actions/actions.xml. Please run '
491 'tools/metrics/actions/extract_actions.py to update.'
492 % (file_with_user_action, 1, 'NotInActionsXml')),
495 def testUserMetricsActionInTestFile(self):
496 input_api = MockInputApi()
497 file_with_user_action = 'file_with_user_action_unittest.cc'
498 contents_with_user_action = [
499 'base::UserMetricsAction("NotInActionsXml")'
502 input_api.files = [MockFile(file_with_user_action,
503 contents_with_user_action)]
506 [], PRESUBMIT.CheckUserActionUpdate(input_api, MockOutputApi()))
509 class PydepsNeedsUpdatingTest(unittest.TestCase):
511 def __init__(self, stdout_func):
512 self._stdout_func = stdout_func
515 self.stdout = io.StringIO(self._stdout_func())
518 class MockSubprocess:
519 CalledProcessError = subprocess.CalledProcessError
523 self._popen_func = None
525 def SetPopenCallback(self, func):
526 self._popen_func = func
528 def Popen(self, cmd, *args, **kwargs):
529 return PydepsNeedsUpdatingTest.MockPopen(lambda: self._popen_func(cmd))
531 def _MockParseGclientArgs(self, is_android=True):
532 return lambda: {'checkout_android': 'true' if is_android else 'false' }
535 mock_all_pydeps = ['A.pydeps', 'B.pydeps', 'D.pydeps']
536 self.old_ALL_PYDEPS_FILES = PRESUBMIT._ALL_PYDEPS_FILES
537 PRESUBMIT._ALL_PYDEPS_FILES = mock_all_pydeps
538 mock_android_pydeps = ['D.pydeps']
539 self.old_ANDROID_SPECIFIC_PYDEPS_FILES = (
540 PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES)
541 PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES = mock_android_pydeps
542 self.old_ParseGclientArgs = PRESUBMIT._ParseGclientArgs
543 PRESUBMIT._ParseGclientArgs = self._MockParseGclientArgs()
544 self.mock_input_api = MockInputApi()
545 self.mock_output_api = MockOutputApi()
546 self.mock_input_api.subprocess = PydepsNeedsUpdatingTest.MockSubprocess()
547 self.checker = PRESUBMIT.PydepsChecker(self.mock_input_api, mock_all_pydeps)
548 self.checker._file_cache = {
549 'A.pydeps': '# Generated by:\n# CMD --output A.pydeps A\nA.py\nC.py\n',
550 'B.pydeps': '# Generated by:\n# CMD --output B.pydeps B\nB.py\nC.py\n',
551 'D.pydeps': '# Generated by:\n# CMD --output D.pydeps D\nD.py\n',
555 PRESUBMIT._ALL_PYDEPS_FILES = self.old_ALL_PYDEPS_FILES
556 PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES = (
557 self.old_ANDROID_SPECIFIC_PYDEPS_FILES)
558 PRESUBMIT._ParseGclientArgs = self.old_ParseGclientArgs
561 return PRESUBMIT.CheckPydepsNeedsUpdating(self.mock_input_api,
562 self.mock_output_api,
563 checker_for_tests=self.checker)
565 def testAddedPydep(self):
566 # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
567 if not self.mock_input_api.platform.startswith('linux'):
570 self.mock_input_api.files = [
571 MockAffectedFile('new.pydeps', [], action='A'),
574 self.mock_input_api.CreateMockFileInPath(
575 [x.LocalPath() for x in self.mock_input_api.AffectedFiles(
576 include_deletes=True)])
577 results = self._RunCheck()
578 self.assertEqual(1, len(results))
579 self.assertIn('PYDEPS_FILES', str(results[0]))
581 def testPydepNotInSrc(self):
582 self.mock_input_api.files = [
583 MockAffectedFile('new.pydeps', [], action='A'),
585 self.mock_input_api.CreateMockFileInPath([])
586 results = self._RunCheck()
587 self.assertEqual(0, len(results))
589 def testRemovedPydep(self):
590 # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
591 if not self.mock_input_api.platform.startswith('linux'):
594 self.mock_input_api.files = [
595 MockAffectedFile(PRESUBMIT._ALL_PYDEPS_FILES[0], [], action='D'),
597 self.mock_input_api.CreateMockFileInPath(
598 [x.LocalPath() for x in self.mock_input_api.AffectedFiles(
599 include_deletes=True)])
600 results = self._RunCheck()
601 self.assertEqual(1, len(results))
602 self.assertIn('PYDEPS_FILES', str(results[0]))
604 def testRandomPyIgnored(self):
605 # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
606 if not self.mock_input_api.platform.startswith('linux'):
609 self.mock_input_api.files = [
610 MockAffectedFile('random.py', []),
613 results = self._RunCheck()
614 self.assertEqual(0, len(results), 'Unexpected results: %r' % results)
616 def testRelevantPyNoChange(self):
617 # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
618 if not self.mock_input_api.platform.startswith('linux'):
621 self.mock_input_api.files = [
622 MockAffectedFile('A.py', []),
625 def popen_callback(cmd):
626 self.assertEqual('CMD --output A.pydeps A --output ""', cmd)
627 return self.checker._file_cache['A.pydeps']
629 self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
631 results = self._RunCheck()
632 self.assertEqual(0, len(results), 'Unexpected results: %r' % results)
634 def testRelevantPyOneChange(self):
635 # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
636 if not self.mock_input_api.platform.startswith('linux'):
639 self.mock_input_api.files = [
640 MockAffectedFile('A.py', []),
643 def popen_callback(cmd):
644 self.assertEqual('CMD --output A.pydeps A --output ""', cmd)
645 return 'changed data'
647 self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
649 results = self._RunCheck()
650 self.assertEqual(1, len(results))
651 # Check that --output "" is not included.
652 self.assertNotIn('""', str(results[0]))
653 self.assertIn('File is stale', str(results[0]))
655 def testRelevantPyTwoChanges(self):
656 # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
657 if not self.mock_input_api.platform.startswith('linux'):
660 self.mock_input_api.files = [
661 MockAffectedFile('C.py', []),
664 def popen_callback(cmd):
665 return 'changed data'
667 self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
669 results = self._RunCheck()
670 self.assertEqual(2, len(results))
671 self.assertIn('File is stale', str(results[0]))
672 self.assertIn('File is stale', str(results[1]))
674 def testRelevantAndroidPyInNonAndroidCheckout(self):
675 # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
676 if not self.mock_input_api.platform.startswith('linux'):
679 self.mock_input_api.files = [
680 MockAffectedFile('D.py', []),
683 def popen_callback(cmd):
684 self.assertEqual('CMD --output D.pydeps D --output ""', cmd)
685 return 'changed data'
687 self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
688 PRESUBMIT._ParseGclientArgs = self._MockParseGclientArgs(is_android=False)
690 results = self._RunCheck()
691 self.assertEqual(1, len(results))
692 self.assertIn('Android', str(results[0]))
693 self.assertIn('D.pydeps', str(results[0]))
695 def testGnPathsAndMissingOutputFlag(self):
696 # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
697 if not self.mock_input_api.platform.startswith('linux'):
700 self.checker._file_cache = {
701 'A.pydeps': '# Generated by:\n# CMD --gn-paths A\n//A.py\n//C.py\n',
702 'B.pydeps': '# Generated by:\n# CMD --gn-paths B\n//B.py\n//C.py\n',
703 'D.pydeps': '# Generated by:\n# CMD --gn-paths D\n//D.py\n',
706 self.mock_input_api.files = [
707 MockAffectedFile('A.py', []),
710 def popen_callback(cmd):
711 self.assertEqual('CMD --gn-paths A --output A.pydeps --output ""', cmd)
712 return 'changed data'
714 self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
716 results = self._RunCheck()
717 self.assertEqual(1, len(results))
718 self.assertIn('File is stale', str(results[0]))
721 class IncludeGuardTest(unittest.TestCase):
722 def testIncludeGuardChecks(self):
723 mock_input_api = MockInputApi()
724 mock_output_api = MockOutputApi()
725 mock_input_api.files = [
726 MockAffectedFile('content/browser/thing/foo.h', [
728 '#ifndef CONTENT_BROWSER_THING_FOO_H_',
729 '#define CONTENT_BROWSER_THING_FOO_H_',
730 'struct McBoatFace;',
731 '#endif // CONTENT_BROWSER_THING_FOO_H_',
733 MockAffectedFile('content/browser/thing/bar.h', [
734 '#ifndef CONTENT_BROWSER_THING_BAR_H_',
735 '#define CONTENT_BROWSER_THING_BAR_H_',
736 'namespace content {',
737 '#endif // CONTENT_BROWSER_THING_BAR_H_',
738 '} // namespace content',
740 MockAffectedFile('content/browser/test1.h', [
741 'namespace content {',
742 '} // namespace content',
744 MockAffectedFile('content\\browser\\win.h', [
745 '#ifndef CONTENT_BROWSER_WIN_H_',
746 '#define CONTENT_BROWSER_WIN_H_',
747 'struct McBoatFace;',
748 '#endif // CONTENT_BROWSER_WIN_H_',
750 MockAffectedFile('content/browser/test2.h', [
752 '#ifndef CONTENT_BROWSER_TEST2_H_',
753 'struct McBoatFace;',
754 '#endif // CONTENT_BROWSER_TEST2_H_',
756 MockAffectedFile('content/browser/internal.h', [
758 '#ifndef CONTENT_BROWSER_INTERNAL_H_',
759 '#define CONTENT_BROWSER_INTERNAL_H_',
761 '#ifndef INTERNAL_CONTENT_BROWSER_INTERNAL_H_',
762 '#define INTERNAL_CONTENT_BROWSER_INTERNAL_H_',
763 'namespace internal {',
764 '} // namespace internal',
765 '#endif // INTERNAL_CONTENT_BROWSER_THING_BAR_H_',
766 'namespace content {',
767 '} // namespace content',
768 '#endif // CONTENT_BROWSER_THING_BAR_H_',
770 MockAffectedFile('content/browser/thing/foo.cc', [
771 '// This is a non-header.',
773 MockAffectedFile('content/browser/disabled.h', [
774 '// no-include-guard-because-multiply-included',
775 'struct McBoatFace;',
777 # New files don't allow misspelled include guards.
778 MockAffectedFile('content/browser/spleling.h', [
779 '#ifndef CONTENT_BROWSER_SPLLEING_H_',
780 '#define CONTENT_BROWSER_SPLLEING_H_',
781 'struct McBoatFace;',
782 '#endif // CONTENT_BROWSER_SPLLEING_H_',
784 # New files don't allow + in include guards.
785 MockAffectedFile('content/browser/foo+bar.h', [
786 '#ifndef CONTENT_BROWSER_FOO+BAR_H_',
787 '#define CONTENT_BROWSER_FOO+BAR_H_',
788 'struct McBoatFace;',
789 '#endif // CONTENT_BROWSER_FOO+BAR_H_',
791 # Old files allow misspelled include guards (for now).
792 MockAffectedFile('chrome/old.h', [
794 '#ifndef CHROME_ODL_H_',
795 '#define CHROME_ODL_H_',
796 '#endif // CHROME_ODL_H_',
799 '#ifndef CHROME_ODL_H_',
800 '#define CHROME_ODL_H_',
801 '#endif // CHROME_ODL_H_',
803 # Using a Blink style include guard outside Blink is wrong.
804 MockAffectedFile('content/NotInBlink.h', [
805 '#ifndef NotInBlink_h',
806 '#define NotInBlink_h',
807 'struct McBoatFace;',
808 '#endif // NotInBlink_h',
810 # Using a Blink style include guard in Blink is no longer ok.
811 MockAffectedFile('third_party/blink/InBlink.h', [
814 'struct McBoatFace;',
815 '#endif // InBlink_h',
817 # Using a bad include guard in Blink is not ok.
818 MockAffectedFile('third_party/blink/AlsoInBlink.h', [
819 '#ifndef WrongInBlink_h',
820 '#define WrongInBlink_h',
821 'struct McBoatFace;',
822 '#endif // WrongInBlink_h',
824 # Using a bad include guard in Blink is not supposed to be accepted even
825 # if it's an old file. However the current presubmit has accepted this
827 MockAffectedFile('third_party/blink/StillInBlink.h', [
829 '#ifndef AcceptedInBlink_h',
830 '#define AcceptedInBlink_h',
831 'struct McBoatFace;',
832 '#endif // AcceptedInBlink_h',
835 '#ifndef AcceptedInBlink_h',
836 '#define AcceptedInBlink_h',
837 'struct McBoatFace;',
838 '#endif // AcceptedInBlink_h',
840 # Using a non-Chromium include guard in third_party
841 # (outside blink) is accepted.
842 MockAffectedFile('third_party/foo/some_file.h', [
843 '#ifndef REQUIRED_RPCNDR_H_',
844 '#define REQUIRED_RPCNDR_H_',
845 'struct SomeFileFoo;',
846 '#endif // REQUIRED_RPCNDR_H_',
848 # Not having proper include guard in *_message_generator.h
849 # for old IPC messages is allowed.
850 MockAffectedFile('content/common/content_message_generator.h', [
851 '#undef CONTENT_COMMON_FOO_MESSAGES_H_',
852 '#include "content/common/foo_messages.h"',
853 '#ifndef CONTENT_COMMON_FOO_MESSAGES_H_',
854 '#error "Failed to include content/common/foo_messages.h"',
858 msgs = PRESUBMIT.CheckForIncludeGuards(
859 mock_input_api, mock_output_api)
860 expected_fail_count = 8
861 self.assertEqual(expected_fail_count, len(msgs),
862 'Expected %d items, found %d: %s'
863 % (expected_fail_count, len(msgs), msgs))
864 self.assertEqual(msgs[0].items, ['content/browser/thing/bar.h'])
865 self.assertEqual(msgs[0].message,
866 'Include guard CONTENT_BROWSER_THING_BAR_H_ '
867 'not covering the whole file')
869 self.assertIn('content/browser/test1.h', msgs[1].message)
870 self.assertIn('Recommended name: CONTENT_BROWSER_TEST1_H_',
873 self.assertEqual(msgs[2].items, ['content/browser/test2.h:3'])
874 self.assertEqual(msgs[2].message,
875 'Missing "#define CONTENT_BROWSER_TEST2_H_" for '
878 self.assertEqual(msgs[3].items, ['content/browser/spleling.h:1'])
879 self.assertEqual(msgs[3].message,
880 'Header using the wrong include guard name '
881 'CONTENT_BROWSER_SPLLEING_H_')
883 self.assertIn('content/browser/foo+bar.h', msgs[4].message)
884 self.assertIn('Recommended name: CONTENT_BROWSER_FOO_BAR_H_',
887 self.assertEqual(msgs[5].items, ['content/NotInBlink.h:1'])
888 self.assertEqual(msgs[5].message,
889 'Header using the wrong include guard name '
892 self.assertEqual(msgs[6].items, ['third_party/blink/InBlink.h:1'])
893 self.assertEqual(msgs[6].message,
894 'Header using the wrong include guard name '
897 self.assertEqual(msgs[7].items, ['third_party/blink/AlsoInBlink.h:1'])
898 self.assertEqual(msgs[7].message,
899 'Header using the wrong include guard name '
902 class AccessibilityRelnotesFieldTest(unittest.TestCase):
903 def testRelnotesPresent(self):
904 mock_input_api = MockInputApi()
905 mock_output_api = MockOutputApi()
907 mock_input_api.files = [MockAffectedFile('ui/accessibility/foo.bar', [''])]
908 mock_input_api.change.DescriptionText = lambda : 'Commit description'
909 mock_input_api.change.footers['AX-Relnotes'] = [
910 'Important user facing change']
912 msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
913 mock_input_api, mock_output_api)
914 self.assertEqual(0, len(msgs),
915 'Expected %d messages, found %d: %s'
916 % (0, len(msgs), msgs))
918 def testRelnotesMissingFromAccessibilityChange(self):
919 mock_input_api = MockInputApi()
920 mock_output_api = MockOutputApi()
922 mock_input_api.files = [
923 MockAffectedFile('some/file', ['']),
924 MockAffectedFile('ui/accessibility/foo.bar', ['']),
925 MockAffectedFile('some/other/file', [''])
927 mock_input_api.change.DescriptionText = lambda : 'Commit description'
929 msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
930 mock_input_api, mock_output_api)
931 self.assertEqual(1, len(msgs),
932 'Expected %d messages, found %d: %s'
933 % (1, len(msgs), msgs))
934 self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message,
935 'Missing AX-Relnotes field message not found in errors')
937 # The relnotes footer is not required for changes which do not touch any
938 # accessibility directories.
939 def testIgnoresNonAccessibilityCode(self):
940 mock_input_api = MockInputApi()
941 mock_output_api = MockOutputApi()
943 mock_input_api.files = [
944 MockAffectedFile('some/file', ['']),
945 MockAffectedFile('some/other/file', [''])
947 mock_input_api.change.DescriptionText = lambda : 'Commit description'
949 msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
950 mock_input_api, mock_output_api)
951 self.assertEqual(0, len(msgs),
952 'Expected %d messages, found %d: %s'
953 % (0, len(msgs), msgs))
955 # Test that our presubmit correctly raises an error for a set of known paths.
956 def testExpectedPaths(self):
958 "chrome/browser/accessibility/foo.py",
959 "chrome/browser/ash/arc/accessibility/foo.cc",
960 "chrome/browser/ui/views/accessibility/foo.h",
961 "chrome/browser/extensions/api/automation/foo.h",
962 "chrome/browser/extensions/api/automation_internal/foo.cc",
963 "chrome/renderer/extensions/accessibility_foo.h",
964 "chrome/tests/data/accessibility/foo.html",
965 "content/browser/accessibility/foo.cc",
966 "content/renderer/accessibility/foo.h",
967 "content/tests/data/accessibility/foo.cc",
968 "extensions/renderer/api/automation/foo.h",
969 "ui/accessibility/foo/bar/baz.cc",
970 "ui/views/accessibility/foo/bar/baz.h",
973 for testFile in filesToTest:
974 mock_input_api = MockInputApi()
975 mock_output_api = MockOutputApi()
977 mock_input_api.files = [
978 MockAffectedFile(testFile, [''])
980 mock_input_api.change.DescriptionText = lambda : 'Commit description'
982 msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
983 mock_input_api, mock_output_api)
984 self.assertEqual(1, len(msgs),
985 'Expected %d messages, found %d: %s, for file %s'
986 % (1, len(msgs), msgs, testFile))
987 self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message,
988 ('Missing AX-Relnotes field message not found in errors '
989 ' for file %s' % (testFile)))
991 # Test that AX-Relnotes field can appear in the commit description (as long
992 # as it appears at the beginning of a line).
993 def testRelnotesInCommitDescription(self):
994 mock_input_api = MockInputApi()
995 mock_output_api = MockOutputApi()
997 mock_input_api.files = [
998 MockAffectedFile('ui/accessibility/foo.bar', ['']),
1000 mock_input_api.change.DescriptionText = lambda : ('Description:\n' +
1001 'AX-Relnotes: solves all accessibility issues forever')
1003 msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
1004 mock_input_api, mock_output_api)
1005 self.assertEqual(0, len(msgs),
1006 'Expected %d messages, found %d: %s'
1007 % (0, len(msgs), msgs))
1009 # Test that we don't match AX-Relnotes if it appears in the middle of a line.
1010 def testRelnotesMustAppearAtBeginningOfLine(self):
1011 mock_input_api = MockInputApi()
1012 mock_output_api = MockOutputApi()
1014 mock_input_api.files = [
1015 MockAffectedFile('ui/accessibility/foo.bar', ['']),
1017 mock_input_api.change.DescriptionText = lambda : ('Description:\n' +
1018 'This change has no AX-Relnotes: we should print a warning')
1020 msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
1021 mock_input_api, mock_output_api)
1022 self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message,
1023 'Missing AX-Relnotes field message not found in errors')
1025 # Tests that the AX-Relnotes field can be lowercase and use a '=' in place
1027 def testRelnotesLowercaseWithEqualSign(self):
1028 mock_input_api = MockInputApi()
1029 mock_output_api = MockOutputApi()
1031 mock_input_api.files = [
1032 MockAffectedFile('ui/accessibility/foo.bar', ['']),
1034 mock_input_api.change.DescriptionText = lambda : ('Description:\n' +
1035 'ax-relnotes= this is a valid format for accessibility relnotes')
1037 msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
1038 mock_input_api, mock_output_api)
1039 self.assertEqual(0, len(msgs),
1040 'Expected %d messages, found %d: %s'
1041 % (0, len(msgs), msgs))
1043 class AccessibilityEventsTestsAreIncludedForAndroidTest(unittest.TestCase):
1044 # Test that no warning is raised when the Android file is also modified.
1045 def testAndroidChangeIncluded(self):
1046 mock_input_api = MockInputApi()
1048 mock_input_api.files = [
1049 MockAffectedFile('content/test/data/accessibility/event/foo.html',
1052 'accessibility/WebContentsAccessibilityEventsTest.java',
1056 msgs = PRESUBMIT.CheckAccessibilityEventsTestsAreIncludedForAndroid(
1057 mock_input_api, MockOutputApi())
1058 self.assertEqual(0, len(msgs),
1059 'Expected %d messages, found %d: %s'
1060 % (0, len(msgs), msgs))
1062 # Test that a warning is raised when the Android file is not modified.
1063 def testAndroidChangeMissing(self):
1064 mock_input_api = MockInputApi()
1066 mock_input_api.files = [
1067 MockAffectedFile('content/test/data/accessibility/event/foo.html',
1071 msgs = PRESUBMIT.CheckAccessibilityEventsTestsAreIncludedForAndroid(
1072 mock_input_api, MockOutputApi())
1073 self.assertEqual(1, len(msgs),
1074 'Expected %d messages, found %d: %s'
1075 % (1, len(msgs), msgs))
1077 # Test that Android change is not required when no html file is added/removed.
1078 def testIgnoreNonHtmlFiles(self):
1079 mock_input_api = MockInputApi()
1081 mock_input_api.files = [
1082 MockAffectedFile('content/test/data/accessibility/event/foo.txt',
1084 MockAffectedFile('content/test/data/accessibility/event/foo.cc',
1086 MockAffectedFile('content/test/data/accessibility/event/foo.h',
1088 MockAffectedFile('content/test/data/accessibility/event/foo.py',
1092 msgs = PRESUBMIT.CheckAccessibilityEventsTestsAreIncludedForAndroid(
1093 mock_input_api, MockOutputApi())
1094 self.assertEqual(0, len(msgs),
1095 'Expected %d messages, found %d: %s'
1096 % (0, len(msgs), msgs))
1098 # Test that Android change is not required for unrelated html files.
1099 def testIgnoreNonRelatedHtmlFiles(self):
1100 mock_input_api = MockInputApi()
1102 mock_input_api.files = [
1103 MockAffectedFile('content/test/data/accessibility/aria/foo.html',
1105 MockAffectedFile('content/test/data/accessibility/html/foo.html',
1107 MockAffectedFile('chrome/tests/data/accessibility/foo.html',
1111 msgs = PRESUBMIT.CheckAccessibilityEventsTestsAreIncludedForAndroid(
1112 mock_input_api, MockOutputApi())
1113 self.assertEqual(0, len(msgs),
1114 'Expected %d messages, found %d: %s'
1115 % (0, len(msgs), msgs))
1117 # Test that only modifying an html file will not trigger the warning.
1118 def testIgnoreModifiedFiles(self):
1119 mock_input_api = MockInputApi()
1121 mock_input_api.files = [
1122 MockAffectedFile('content/test/data/accessibility/event/foo.html',
1126 msgs = PRESUBMIT.CheckAccessibilityEventsTestsAreIncludedForAndroid(
1127 mock_input_api, MockOutputApi())
1128 self.assertEqual(0, len(msgs),
1129 'Expected %d messages, found %d: %s'
1130 % (0, len(msgs), msgs))
1132 # Test that deleting an html file will trigger the warning.
1133 def testAndroidChangeMissingOnDeletedFile(self):
1134 mock_input_api = MockInputApi()
1136 mock_input_api.files = [
1137 MockAffectedFile('content/test/data/accessibility/event/foo.html',
1141 msgs = PRESUBMIT.CheckAccessibilityEventsTestsAreIncludedForAndroid(
1142 mock_input_api, MockOutputApi())
1143 self.assertEqual(1, len(msgs),
1144 'Expected %d messages, found %d: %s'
1145 % (1, len(msgs), msgs))
1147 class AccessibilityTreeTestsAreIncludedForAndroidTest(unittest.TestCase):
1148 # Test that no warning is raised when the Android file is also modified.
1149 def testAndroidChangeIncluded(self):
1150 mock_input_api = MockInputApi()
1152 mock_input_api.files = [
1153 MockAffectedFile('content/test/data/accessibility/aria/foo.html',
1156 'accessibility/WebContentsAccessibilityTreeTest.java',
1160 msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1161 mock_input_api, MockOutputApi())
1162 self.assertEqual(0, len(msgs),
1163 'Expected %d messages, found %d: %s'
1164 % (0, len(msgs), msgs))
1166 # Test that no warning is raised when the Android file is also modified.
1167 def testAndroidChangeIncludedManyFiles(self):
1168 mock_input_api = MockInputApi()
1170 mock_input_api.files = [
1171 MockAffectedFile('content/test/data/accessibility/accname/foo.html',
1173 MockAffectedFile('content/test/data/accessibility/aria/foo.html',
1175 MockAffectedFile('content/test/data/accessibility/css/foo.html',
1177 MockAffectedFile('content/test/data/accessibility/html/foo.html',
1180 'accessibility/WebContentsAccessibilityTreeTest.java',
1184 msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1185 mock_input_api, MockOutputApi())
1186 self.assertEqual(0, len(msgs),
1187 'Expected %d messages, found %d: %s'
1188 % (0, len(msgs), msgs))
1190 # Test that a warning is raised when the Android file is not modified.
1191 def testAndroidChangeMissing(self):
1192 mock_input_api = MockInputApi()
1194 mock_input_api.files = [
1195 MockAffectedFile('content/test/data/accessibility/aria/foo.html',
1199 msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1200 mock_input_api, MockOutputApi())
1201 self.assertEqual(1, len(msgs),
1202 'Expected %d messages, found %d: %s'
1203 % (1, len(msgs), msgs))
1205 # Test that Android change is not required when no html file is added/removed.
1206 def testIgnoreNonHtmlFiles(self):
1207 mock_input_api = MockInputApi()
1209 mock_input_api.files = [
1210 MockAffectedFile('content/test/data/accessibility/accname/foo.txt',
1212 MockAffectedFile('content/test/data/accessibility/aria/foo.cc',
1214 MockAffectedFile('content/test/data/accessibility/css/foo.h',
1216 MockAffectedFile('content/test/data/accessibility/tree/foo.py',
1220 msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1221 mock_input_api, MockOutputApi())
1222 self.assertEqual(0, len(msgs),
1223 'Expected %d messages, found %d: %s'
1224 % (0, len(msgs), msgs))
1226 # Test that Android change is not required for unrelated html files.
1227 def testIgnoreNonRelatedHtmlFiles(self):
1228 mock_input_api = MockInputApi()
1230 mock_input_api.files = [
1231 MockAffectedFile('content/test/data/accessibility/event/foo.html',
1235 msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1236 mock_input_api, MockOutputApi())
1237 self.assertEqual(0, len(msgs),
1238 'Expected %d messages, found %d: %s'
1239 % (0, len(msgs), msgs))
1241 # Test that only modifying an html file will not trigger the warning.
1242 def testIgnoreModifiedFiles(self):
1243 mock_input_api = MockInputApi()
1245 mock_input_api.files = [
1246 MockAffectedFile('content/test/data/accessibility/aria/foo.html',
1250 msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1251 mock_input_api, MockOutputApi())
1252 self.assertEqual(0, len(msgs),
1253 'Expected %d messages, found %d: %s'
1254 % (0, len(msgs), msgs))
1256 # Test that deleting an html file will trigger the warning.
1257 def testAndroidChangeMissingOnDeletedFile(self):
1258 mock_input_api = MockInputApi()
1260 mock_input_api.files = [
1261 MockAffectedFile('content/test/data/accessibility/accname/foo.html',
1265 msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1266 mock_input_api, MockOutputApi())
1267 self.assertEqual(1, len(msgs),
1268 'Expected %d messages, found %d: %s'
1269 % (1, len(msgs), msgs))
1271 class AndroidDeprecatedTestAnnotationTest(unittest.TestCase):
1272 def testCheckAndroidTestAnnotationUsage(self):
1273 mock_input_api = MockInputApi()
1274 mock_output_api = MockOutputApi()
1276 mock_input_api.files = [
1277 MockAffectedFile('LalaLand.java', [
1280 MockAffectedFile('CorrectUsage.java', [
1281 'import android.support.test.filters.LargeTest;',
1282 'import android.support.test.filters.MediumTest;',
1283 'import android.support.test.filters.SmallTest;',
1285 MockAffectedFile('UsedDeprecatedLargeTestAnnotation.java', [
1286 'import android.test.suitebuilder.annotation.LargeTest;',
1288 MockAffectedFile('UsedDeprecatedMediumTestAnnotation.java', [
1289 'import android.test.suitebuilder.annotation.MediumTest;',
1291 MockAffectedFile('UsedDeprecatedSmallTestAnnotation.java', [
1292 'import android.test.suitebuilder.annotation.SmallTest;',
1294 MockAffectedFile('UsedDeprecatedSmokeAnnotation.java', [
1295 'import android.test.suitebuilder.annotation.Smoke;',
1298 msgs = PRESUBMIT._CheckAndroidTestAnnotationUsage(
1299 mock_input_api, mock_output_api)
1300 self.assertEqual(1, len(msgs),
1301 'Expected %d items, found %d: %s'
1302 % (1, len(msgs), msgs))
1303 self.assertEqual(4, len(msgs[0].items),
1304 'Expected %d items, found %d: %s'
1305 % (4, len(msgs[0].items), msgs[0].items))
1306 self.assertTrue('UsedDeprecatedLargeTestAnnotation.java:1' in msgs[0].items,
1307 'UsedDeprecatedLargeTestAnnotation not found in errors')
1308 self.assertTrue('UsedDeprecatedMediumTestAnnotation.java:1'
1310 'UsedDeprecatedMediumTestAnnotation not found in errors')
1311 self.assertTrue('UsedDeprecatedSmallTestAnnotation.java:1' in msgs[0].items,
1312 'UsedDeprecatedSmallTestAnnotation not found in errors')
1313 self.assertTrue('UsedDeprecatedSmokeAnnotation.java:1' in msgs[0].items,
1314 'UsedDeprecatedSmokeAnnotation not found in errors')
1317 class CheckNoDownstreamDepsTest(unittest.TestCase):
1318 def testInvalidDepFromUpstream(self):
1319 mock_input_api = MockInputApi()
1320 mock_output_api = MockOutputApi()
1322 mock_input_api.files = [
1323 MockAffectedFile('BUILD.gn', [
1325 ' "//clank/target:test",',
1328 MockAffectedFile('chrome/android/BUILD.gn', [
1329 'deps = [ "//clank/target:test" ]'
1331 MockAffectedFile('chrome/chrome_java_deps.gni', [
1333 ' "//clank/target:test",',
1337 mock_input_api.change.RepositoryRoot = lambda: 'chromium/src'
1338 msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1339 mock_input_api, mock_output_api)
1340 self.assertEqual(1, len(msgs),
1341 'Expected %d items, found %d: %s'
1342 % (1, len(msgs), msgs))
1343 self.assertEqual(3, len(msgs[0].items),
1344 'Expected %d items, found %d: %s'
1345 % (3, len(msgs[0].items), msgs[0].items))
1346 self.assertTrue(any('BUILD.gn:2' in item for item in msgs[0].items),
1347 'BUILD.gn not found in errors')
1349 any('chrome/android/BUILD.gn:1' in item for item in msgs[0].items),
1350 'chrome/android/BUILD.gn:1 not found in errors')
1352 any('chrome/chrome_java_deps.gni:2' in item for item in msgs[0].items),
1353 'chrome/chrome_java_deps.gni:2 not found in errors')
1355 def testAllowsComments(self):
1356 mock_input_api = MockInputApi()
1357 mock_output_api = MockOutputApi()
1359 mock_input_api.files = [
1360 MockAffectedFile('BUILD.gn', [
1361 '# real implementation in //clank/target:test',
1364 mock_input_api.change.RepositoryRoot = lambda: 'chromium/src'
1365 msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1366 mock_input_api, mock_output_api)
1367 self.assertEqual(0, len(msgs),
1368 'Expected %d items, found %d: %s'
1369 % (0, len(msgs), msgs))
1371 def testOnlyChecksBuildFiles(self):
1372 mock_input_api = MockInputApi()
1373 mock_output_api = MockOutputApi()
1375 mock_input_api.files = [
1376 MockAffectedFile('README.md', [
1377 'DEPS = [ "//clank/target:test" ]'
1379 MockAffectedFile('chrome/android/java/file.java', [
1380 '//clank/ only function'
1383 mock_input_api.change.RepositoryRoot = lambda: 'chromium/src'
1384 msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1385 mock_input_api, mock_output_api)
1386 self.assertEqual(0, len(msgs),
1387 'Expected %d items, found %d: %s'
1388 % (0, len(msgs), msgs))
1390 def testValidDepFromDownstream(self):
1391 mock_input_api = MockInputApi()
1392 mock_output_api = MockOutputApi()
1394 mock_input_api.files = [
1395 MockAffectedFile('BUILD.gn', [
1397 ' "//clank/target:test",',
1400 MockAffectedFile('java/BUILD.gn', [
1401 'DEPS = [ "//clank/target:test" ]'
1404 mock_input_api.change.RepositoryRoot = lambda: 'chromium/src/clank'
1405 msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1406 mock_input_api, mock_output_api)
1407 self.assertEqual(0, len(msgs),
1408 'Expected %d items, found %d: %s'
1409 % (0, len(msgs), msgs))
1411 class AndroidDeprecatedJUnitFrameworkTest(unittest.TestCase):
1412 def testCheckAndroidTestJUnitFramework(self):
1413 mock_input_api = MockInputApi()
1414 mock_output_api = MockOutputApi()
1416 mock_input_api.files = [
1417 MockAffectedFile('LalaLand.java', [
1420 MockAffectedFile('CorrectUsage.java', [
1421 'import org.junit.ABC',
1422 'import org.junit.XYZ;',
1424 MockAffectedFile('UsedDeprecatedJUnit.java', [
1425 'import junit.framework.*;',
1427 MockAffectedFile('UsedDeprecatedJUnitAssert.java', [
1428 'import junit.framework.Assert;',
1431 msgs = PRESUBMIT._CheckAndroidTestJUnitFrameworkImport(
1432 mock_input_api, mock_output_api)
1433 self.assertEqual(1, len(msgs),
1434 'Expected %d items, found %d: %s'
1435 % (1, len(msgs), msgs))
1436 self.assertEqual(2, len(msgs[0].items),
1437 'Expected %d items, found %d: %s'
1438 % (2, len(msgs[0].items), msgs[0].items))
1439 self.assertTrue('UsedDeprecatedJUnit.java:1' in msgs[0].items,
1440 'UsedDeprecatedJUnit.java not found in errors')
1441 self.assertTrue('UsedDeprecatedJUnitAssert.java:1'
1443 'UsedDeprecatedJUnitAssert not found in errors')
1446 class AndroidJUnitBaseClassTest(unittest.TestCase):
1447 def testCheckAndroidTestJUnitBaseClass(self):
1448 mock_input_api = MockInputApi()
1449 mock_output_api = MockOutputApi()
1451 mock_input_api.files = [
1452 MockAffectedFile('LalaLand.java', [
1455 MockAffectedFile('CorrectTest.java', [
1456 '@RunWith(ABC.class);'
1457 'public class CorrectTest {',
1460 MockAffectedFile('HistoricallyIncorrectTest.java', [
1461 'public class Test extends BaseCaseA {',
1464 'public class Test extends BaseCaseB {',
1467 MockAffectedFile('CorrectTestWithInterface.java', [
1468 '@RunWith(ABC.class);'
1469 'public class CorrectTest implement Interface {',
1472 MockAffectedFile('IncorrectTest.java', [
1473 'public class IncorrectTest extends TestCase {',
1476 MockAffectedFile('IncorrectWithInterfaceTest.java', [
1477 'public class Test implements X extends BaseClass {',
1480 MockAffectedFile('IncorrectMultiLineTest.java', [
1481 'public class Test implements X, Y, Z',
1482 ' extends TestBase {',
1486 msgs = PRESUBMIT._CheckAndroidTestJUnitInheritance(
1487 mock_input_api, mock_output_api)
1488 self.assertEqual(1, len(msgs),
1489 'Expected %d items, found %d: %s'
1490 % (1, len(msgs), msgs))
1491 self.assertEqual(3, len(msgs[0].items),
1492 'Expected %d items, found %d: %s'
1493 % (3, len(msgs[0].items), msgs[0].items))
1494 self.assertTrue('IncorrectTest.java:1' in msgs[0].items,
1495 'IncorrectTest not found in errors')
1496 self.assertTrue('IncorrectWithInterfaceTest.java:1'
1498 'IncorrectWithInterfaceTest not found in errors')
1499 self.assertTrue('IncorrectMultiLineTest.java:2' in msgs[0].items,
1500 'IncorrectMultiLineTest not found in errors')
1502 class AndroidDebuggableBuildTest(unittest.TestCase):
1504 def testCheckAndroidDebuggableBuild(self):
1505 mock_input_api = MockInputApi()
1506 mock_output_api = MockOutputApi()
1508 mock_input_api.files = [
1509 MockAffectedFile('RandomStuff.java', [
1512 MockAffectedFile('CorrectUsage.java', [
1513 'import org.chromium.base.BuildInfo;',
1514 'some random stuff',
1515 'boolean isOsDebuggable = BuildInfo.isDebugAndroid();',
1517 MockAffectedFile('JustCheckUserdebugBuild.java', [
1518 'import android.os.Build;',
1519 'some random stuff',
1520 'boolean isOsDebuggable = Build.TYPE.equals("userdebug")',
1522 MockAffectedFile('JustCheckEngineeringBuild.java', [
1523 'import android.os.Build;',
1524 'some random stuff',
1525 'boolean isOsDebuggable = "eng".equals(Build.TYPE)',
1527 MockAffectedFile('UsedBuildType.java', [
1528 'import android.os.Build;',
1529 'some random stuff',
1530 'boolean isOsDebuggable = Build.TYPE.equals("userdebug")'
1531 '|| "eng".equals(Build.TYPE)',
1533 MockAffectedFile('UsedExplicitBuildType.java', [
1534 'some random stuff',
1535 'boolean isOsDebuggable = android.os.Build.TYPE.equals("userdebug")'
1536 '|| "eng".equals(android.os.Build.TYPE)',
1540 msgs = PRESUBMIT._CheckAndroidDebuggableBuild(
1541 mock_input_api, mock_output_api)
1542 self.assertEqual(1, len(msgs),
1543 'Expected %d items, found %d: %s'
1544 % (1, len(msgs), msgs))
1545 self.assertEqual(4, len(msgs[0].items),
1546 'Expected %d items, found %d: %s'
1547 % (4, len(msgs[0].items), msgs[0].items))
1548 self.assertTrue('JustCheckUserdebugBuild.java:3' in msgs[0].items)
1549 self.assertTrue('JustCheckEngineeringBuild.java:3' in msgs[0].items)
1550 self.assertTrue('UsedBuildType.java:3' in msgs[0].items)
1551 self.assertTrue('UsedExplicitBuildType.java:2' in msgs[0].items)
1554 class LogUsageTest(unittest.TestCase):
1556 def testCheckAndroidCrLogUsage(self):
1557 mock_input_api = MockInputApi()
1558 mock_output_api = MockOutputApi()
1560 mock_input_api.files = [
1561 MockAffectedFile('RandomStuff.java', [
1564 MockAffectedFile('HasAndroidLog.java', [
1565 'import android.util.Log;',
1566 'some random stuff',
1567 'Log.d("TAG", "foo");',
1569 MockAffectedFile('HasExplicitUtilLog.java', [
1570 'some random stuff',
1571 'android.util.Log.d("TAG", "foo");',
1573 MockAffectedFile('IsInBasePackage.java', [
1574 'package org.chromium.base;',
1575 'private static final String TAG = "cr_Foo";',
1576 'Log.d(TAG, "foo");',
1578 MockAffectedFile('IsInBasePackageButImportsLog.java', [
1579 'package org.chromium.base;',
1580 'import android.util.Log;',
1581 'private static final String TAG = "cr_Foo";',
1582 'Log.d(TAG, "foo");',
1584 MockAffectedFile('HasBothLog.java', [
1585 'import org.chromium.base.Log;',
1586 'some random stuff',
1587 'private static final String TAG = "cr_Foo";',
1588 'Log.d(TAG, "foo");',
1589 'android.util.Log.d("TAG", "foo");',
1591 MockAffectedFile('HasCorrectTag.java', [
1592 'import org.chromium.base.Log;',
1593 'some random stuff',
1594 'private static final String TAG = "cr_Foo";',
1595 'Log.d(TAG, "foo");',
1597 MockAffectedFile('HasOldTag.java', [
1598 'import org.chromium.base.Log;',
1599 'some random stuff',
1600 'private static final String TAG = "cr.Foo";',
1601 'Log.d(TAG, "foo");',
1603 MockAffectedFile('HasDottedTag.java', [
1604 'import org.chromium.base.Log;',
1605 'some random stuff',
1606 'private static final String TAG = "cr_foo.bar";',
1607 'Log.d(TAG, "foo");',
1609 MockAffectedFile('HasDottedTagPublic.java', [
1610 'import org.chromium.base.Log;',
1611 'some random stuff',
1612 'public static final String TAG = "cr_foo.bar";',
1613 'Log.d(TAG, "foo");',
1615 MockAffectedFile('HasNoTagDecl.java', [
1616 'import org.chromium.base.Log;',
1617 'some random stuff',
1618 'Log.d(TAG, "foo");',
1620 MockAffectedFile('HasIncorrectTagDecl.java', [
1621 'import org.chromium.base.Log;',
1622 'private static final String TAHG = "cr_Foo";',
1623 'some random stuff',
1624 'Log.d(TAG, "foo");',
1626 MockAffectedFile('HasInlineTag.java', [
1627 'import org.chromium.base.Log;',
1628 'some random stuff',
1629 'private static final String TAG = "cr_Foo";',
1630 'Log.d("TAG", "foo");',
1632 MockAffectedFile('HasInlineTagWithSpace.java', [
1633 'import org.chromium.base.Log;',
1634 'some random stuff',
1635 'private static final String TAG = "cr_Foo";',
1636 'Log.d("log message", "foo");',
1638 MockAffectedFile('HasUnprefixedTag.java', [
1639 'import org.chromium.base.Log;',
1640 'some random stuff',
1641 'private static final String TAG = "rubbish";',
1642 'Log.d(TAG, "foo");',
1644 MockAffectedFile('HasTooLongTag.java', [
1645 'import org.chromium.base.Log;',
1646 'some random stuff',
1647 'private static final String TAG = "21_characters_long___";',
1648 'Log.d(TAG, "foo");',
1650 MockAffectedFile('HasTooLongTagWithNoLogCallsInDiff.java', [
1651 'import org.chromium.base.Log;',
1652 'some random stuff',
1653 'private static final String TAG = "21_characters_long___";',
1657 msgs = PRESUBMIT._CheckAndroidCrLogUsage(
1658 mock_input_api, mock_output_api)
1660 self.assertEqual(5, len(msgs),
1661 'Expected %d items, found %d: %s' % (5, len(msgs), msgs))
1663 # Declaration format
1664 nb = len(msgs[0].items)
1665 self.assertEqual(2, nb,
1666 'Expected %d items, found %d: %s' % (2, nb, msgs[0].items))
1667 self.assertTrue('HasNoTagDecl.java' in msgs[0].items)
1668 self.assertTrue('HasIncorrectTagDecl.java' in msgs[0].items)
1671 nb = len(msgs[1].items)
1672 self.assertEqual(2, nb,
1673 'Expected %d items, found %d: %s' % (2, nb, msgs[1].items))
1674 self.assertTrue('HasTooLongTag.java' in msgs[1].items)
1675 self.assertTrue('HasTooLongTagWithNoLogCallsInDiff.java' in msgs[1].items)
1677 # Tag must be a variable named TAG
1678 nb = len(msgs[2].items)
1679 self.assertEqual(3, nb,
1680 'Expected %d items, found %d: %s' % (3, nb, msgs[2].items))
1681 self.assertTrue('HasBothLog.java:5' in msgs[2].items)
1682 self.assertTrue('HasInlineTag.java:4' in msgs[2].items)
1683 self.assertTrue('HasInlineTagWithSpace.java:4' in msgs[2].items)
1686 nb = len(msgs[3].items)
1687 self.assertEqual(3, nb,
1688 'Expected %d items, found %d: %s' % (3, nb, msgs[3].items))
1689 self.assertTrue('HasAndroidLog.java:3' in msgs[3].items)
1690 self.assertTrue('HasExplicitUtilLog.java:2' in msgs[3].items)
1691 self.assertTrue('IsInBasePackageButImportsLog.java:4' in msgs[3].items)
1693 # Tag must not contain
1694 nb = len(msgs[4].items)
1695 self.assertEqual(3, nb,
1696 'Expected %d items, found %d: %s' % (2, nb, msgs[4].items))
1697 self.assertTrue('HasDottedTag.java' in msgs[4].items)
1698 self.assertTrue('HasDottedTagPublic.java' in msgs[4].items)
1699 self.assertTrue('HasOldTag.java' in msgs[4].items)
1702 class GoogleAnswerUrlFormatTest(unittest.TestCase):
1704 def testCatchAnswerUrlId(self):
1705 input_api = MockInputApi()
1707 MockFile('somewhere/file.cc',
1709 ' "https://support.google.com/chrome/answer/123456";']),
1710 MockFile('somewhere_else/file.cc',
1712 ' "https://support.google.com/chrome/a/answer/123456";']),
1715 warnings = PRESUBMIT.CheckGoogleSupportAnswerUrlOnUpload(
1716 input_api, MockOutputApi())
1717 self.assertEqual(1, len(warnings))
1718 self.assertEqual(2, len(warnings[0].items))
1720 def testAllowAnswerUrlParam(self):
1721 input_api = MockInputApi()
1723 MockFile('somewhere/file.cc',
1725 ' "https://support.google.com/chrome/?p=cpn_crash_reports";']),
1728 warnings = PRESUBMIT.CheckGoogleSupportAnswerUrlOnUpload(
1729 input_api, MockOutputApi())
1730 self.assertEqual(0, len(warnings))
1733 class HardcodedGoogleHostsTest(unittest.TestCase):
1735 def testWarnOnAssignedLiterals(self):
1736 input_api = MockInputApi()
1738 MockFile('content/file.cc',
1739 ['char* host = "https://www.google.com";']),
1740 MockFile('content/file.cc',
1741 ['char* host = "https://www.googleapis.com";']),
1742 MockFile('content/file.cc',
1743 ['char* host = "https://clients1.google.com";']),
1746 warnings = PRESUBMIT.CheckHardcodedGoogleHostsInLowerLayers(
1747 input_api, MockOutputApi())
1748 self.assertEqual(1, len(warnings))
1749 self.assertEqual(3, len(warnings[0].items))
1751 def testAllowInComment(self):
1752 input_api = MockInputApi()
1754 MockFile('content/file.cc',
1755 ['char* host = "https://www.aol.com"; // google.com'])
1758 warnings = PRESUBMIT.CheckHardcodedGoogleHostsInLowerLayers(
1759 input_api, MockOutputApi())
1760 self.assertEqual(0, len(warnings))
1763 class ChromeOsSyncedPrefRegistrationTest(unittest.TestCase):
1765 def testWarnsOnChromeOsDirectories(self):
1767 MockFile('ash/file.cc',
1768 ['PrefRegistrySyncable::SYNCABLE_PREF']),
1769 MockFile('chrome/browser/chromeos/file.cc',
1770 ['PrefRegistrySyncable::SYNCABLE_PREF']),
1771 MockFile('chromeos/file.cc',
1772 ['PrefRegistrySyncable::SYNCABLE_PREF']),
1773 MockFile('components/arc/file.cc',
1774 ['PrefRegistrySyncable::SYNCABLE_PREF']),
1775 MockFile('components/exo/file.cc',
1776 ['PrefRegistrySyncable::SYNCABLE_PREF']),
1778 input_api = MockInputApi()
1780 input_api.files = [file]
1781 warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1782 input_api, MockOutputApi())
1783 self.assertEqual(1, len(warnings))
1785 def testDoesNotWarnOnSyncOsPref(self):
1786 input_api = MockInputApi()
1788 MockFile('chromeos/file.cc',
1789 ['PrefRegistrySyncable::SYNCABLE_OS_PREF']),
1791 warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1792 input_api, MockOutputApi())
1793 self.assertEqual(0, len(warnings))
1795 def testDoesNotWarnOnOtherDirectories(self):
1796 input_api = MockInputApi()
1798 MockFile('chrome/browser/ui/file.cc',
1799 ['PrefRegistrySyncable::SYNCABLE_PREF']),
1800 MockFile('components/sync/file.cc',
1801 ['PrefRegistrySyncable::SYNCABLE_PREF']),
1802 MockFile('content/browser/file.cc',
1803 ['PrefRegistrySyncable::SYNCABLE_PREF']),
1804 MockFile('a/notchromeos/file.cc',
1805 ['PrefRegistrySyncable::SYNCABLE_PREF']),
1807 warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1808 input_api, MockOutputApi())
1809 self.assertEqual(0, len(warnings))
1811 def testSeparateWarningForPriorityPrefs(self):
1812 input_api = MockInputApi()
1814 MockFile('chromeos/file.cc',
1815 ['PrefRegistrySyncable::SYNCABLE_PREF',
1816 'PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF']),
1818 warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1819 input_api, MockOutputApi())
1820 self.assertEqual(2, len(warnings))
1823 class ForwardDeclarationTest(unittest.TestCase):
1824 def testCheckHeadersOnlyOutsideThirdParty(self):
1825 mock_input_api = MockInputApi()
1826 mock_input_api.files = [
1827 MockAffectedFile('somewhere/file.cc', [
1830 MockAffectedFile('third_party/header.h', [
1834 warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1836 self.assertEqual(0, len(warnings))
1838 def testNoNestedDeclaration(self):
1839 mock_input_api = MockInputApi()
1840 mock_input_api.files = [
1841 MockAffectedFile('somewhere/header.h', [
1842 'class SomeClass {',
1844 ' class NotAMatch;',
1848 warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1850 self.assertEqual(0, len(warnings))
1852 def testSubStrings(self):
1853 mock_input_api = MockInputApi()
1854 mock_input_api.files = [
1855 MockAffectedFile('somewhere/header.h', [
1856 'class NotUsefulClass;',
1857 'struct SomeStruct;',
1859 'SomeStructPtr *p2;'
1862 warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1864 self.assertEqual(2, len(warnings))
1866 def testUselessForwardDeclaration(self):
1867 mock_input_api = MockInputApi()
1868 mock_input_api.files = [
1869 MockAffectedFile('somewhere/header.h', [
1870 'class DummyClass;',
1871 'struct DummyStruct;',
1872 'class UsefulClass;',
1873 'std::unique_ptr<UsefulClass> p;'
1876 warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1878 self.assertEqual(2, len(warnings))
1880 def testBlinkHeaders(self):
1881 mock_input_api = MockInputApi()
1882 mock_input_api.files = [
1883 MockAffectedFile('third_party/blink/header.h', [
1884 'class DummyClass;',
1885 'struct DummyStruct;',
1887 MockAffectedFile('third_party\\blink\\header.h', [
1888 'class DummyClass;',
1889 'struct DummyStruct;',
1892 warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1894 self.assertEqual(4, len(warnings))
1897 class RelativeIncludesTest(unittest.TestCase):
1898 def testThirdPartyNotWebKitIgnored(self):
1899 mock_input_api = MockInputApi()
1900 mock_input_api.files = [
1901 MockAffectedFile('third_party/test.cpp', '#include "../header.h"'),
1902 MockAffectedFile('third_party/test/test.cpp', '#include "../header.h"'),
1905 mock_output_api = MockOutputApi()
1907 errors = PRESUBMIT.CheckForRelativeIncludes(
1908 mock_input_api, mock_output_api)
1909 self.assertEqual(0, len(errors))
1911 def testNonCppFileIgnored(self):
1912 mock_input_api = MockInputApi()
1913 mock_input_api.files = [
1914 MockAffectedFile('test.py', '#include "../header.h"'),
1917 mock_output_api = MockOutputApi()
1919 errors = PRESUBMIT.CheckForRelativeIncludes(
1920 mock_input_api, mock_output_api)
1921 self.assertEqual(0, len(errors))
1923 def testInnocuousChangesAllowed(self):
1924 mock_input_api = MockInputApi()
1925 mock_input_api.files = [
1926 MockAffectedFile('test.cpp', '#include "header.h"'),
1927 MockAffectedFile('test2.cpp', '../'),
1930 mock_output_api = MockOutputApi()
1932 errors = PRESUBMIT.CheckForRelativeIncludes(
1933 mock_input_api, mock_output_api)
1934 self.assertEqual(0, len(errors))
1936 def testRelativeIncludeNonWebKitProducesError(self):
1937 mock_input_api = MockInputApi()
1938 mock_input_api.files = [
1939 MockAffectedFile('test.cpp', ['#include "../header.h"']),
1942 mock_output_api = MockOutputApi()
1944 errors = PRESUBMIT.CheckForRelativeIncludes(
1945 mock_input_api, mock_output_api)
1946 self.assertEqual(1, len(errors))
1948 def testRelativeIncludeWebKitProducesError(self):
1949 mock_input_api = MockInputApi()
1950 mock_input_api.files = [
1951 MockAffectedFile('third_party/blink/test.cpp',
1952 ['#include "../header.h']),
1955 mock_output_api = MockOutputApi()
1957 errors = PRESUBMIT.CheckForRelativeIncludes(
1958 mock_input_api, mock_output_api)
1959 self.assertEqual(1, len(errors))
1962 class CCIncludeTest(unittest.TestCase):
1963 def testThirdPartyNotBlinkIgnored(self):
1964 mock_input_api = MockInputApi()
1965 mock_input_api.files = [
1966 MockAffectedFile('third_party/test.cpp', '#include "file.cc"'),
1969 mock_output_api = MockOutputApi()
1971 errors = PRESUBMIT.CheckForCcIncludes(
1972 mock_input_api, mock_output_api)
1973 self.assertEqual(0, len(errors))
1975 def testPythonFileIgnored(self):
1976 mock_input_api = MockInputApi()
1977 mock_input_api.files = [
1978 MockAffectedFile('test.py', '#include "file.cc"'),
1981 mock_output_api = MockOutputApi()
1983 errors = PRESUBMIT.CheckForCcIncludes(
1984 mock_input_api, mock_output_api)
1985 self.assertEqual(0, len(errors))
1987 def testIncFilesAccepted(self):
1988 mock_input_api = MockInputApi()
1989 mock_input_api.files = [
1990 MockAffectedFile('test.py', '#include "file.inc"'),
1993 mock_output_api = MockOutputApi()
1995 errors = PRESUBMIT.CheckForCcIncludes(
1996 mock_input_api, mock_output_api)
1997 self.assertEqual(0, len(errors))
1999 def testInnocuousChangesAllowed(self):
2000 mock_input_api = MockInputApi()
2001 mock_input_api.files = [
2002 MockAffectedFile('test.cpp', '#include "header.h"'),
2003 MockAffectedFile('test2.cpp', 'Something "file.cc"'),
2006 mock_output_api = MockOutputApi()
2008 errors = PRESUBMIT.CheckForCcIncludes(
2009 mock_input_api, mock_output_api)
2010 self.assertEqual(0, len(errors))
2012 def testCcIncludeNonBlinkProducesError(self):
2013 mock_input_api = MockInputApi()
2014 mock_input_api.files = [
2015 MockAffectedFile('test.cpp', ['#include "file.cc"']),
2018 mock_output_api = MockOutputApi()
2020 errors = PRESUBMIT.CheckForCcIncludes(
2021 mock_input_api, mock_output_api)
2022 self.assertEqual(1, len(errors))
2024 def testCppIncludeBlinkProducesError(self):
2025 mock_input_api = MockInputApi()
2026 mock_input_api.files = [
2027 MockAffectedFile('third_party/blink/test.cpp',
2028 ['#include "foo/file.cpp"']),
2031 mock_output_api = MockOutputApi()
2033 errors = PRESUBMIT.CheckForCcIncludes(
2034 mock_input_api, mock_output_api)
2035 self.assertEqual(1, len(errors))
2038 class GnGlobForwardTest(unittest.TestCase):
2039 def testAddBareGlobs(self):
2040 mock_input_api = MockInputApi()
2041 mock_input_api.files = [
2042 MockAffectedFile('base/stuff.gni', [
2043 'forward_variables_from(invoker, "*")']),
2044 MockAffectedFile('base/BUILD.gn', [
2045 'forward_variables_from(invoker, "*")']),
2047 warnings = PRESUBMIT.CheckGnGlobForward(mock_input_api, MockOutputApi())
2048 self.assertEqual(1, len(warnings))
2049 msg = '\n'.join(warnings[0].items)
2050 self.assertIn('base/stuff.gni', msg)
2051 # Should not check .gn files. Local templates don't need to care about
2052 # visibility / testonly.
2053 self.assertNotIn('base/BUILD.gn', msg)
2055 def testValidUses(self):
2056 mock_input_api = MockInputApi()
2057 mock_input_api.files = [
2058 MockAffectedFile('base/stuff.gni', [
2059 'forward_variables_from(invoker, "*", [])']),
2060 MockAffectedFile('base/stuff2.gni', [
2061 'forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)']),
2062 MockAffectedFile('base/stuff3.gni', [
2063 'forward_variables_from(invoker, [ "testonly" ])']),
2065 warnings = PRESUBMIT.CheckGnGlobForward(mock_input_api, MockOutputApi())
2066 self.assertEqual([], warnings)
2069 class NewHeaderWithoutGnChangeTest(unittest.TestCase):
2070 def testAddHeaderWithoutGn(self):
2071 mock_input_api = MockInputApi()
2072 mock_input_api.files = [
2073 MockAffectedFile('base/stuff.h', ''),
2075 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2076 mock_input_api, MockOutputApi())
2077 self.assertEqual(1, len(warnings))
2078 self.assertTrue('base/stuff.h' in warnings[0].items)
2080 def testModifyHeader(self):
2081 mock_input_api = MockInputApi()
2082 mock_input_api.files = [
2083 MockAffectedFile('base/stuff.h', '', action='M'),
2085 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2086 mock_input_api, MockOutputApi())
2087 self.assertEqual(0, len(warnings))
2089 def testDeleteHeader(self):
2090 mock_input_api = MockInputApi()
2091 mock_input_api.files = [
2092 MockAffectedFile('base/stuff.h', '', action='D'),
2094 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2095 mock_input_api, MockOutputApi())
2096 self.assertEqual(0, len(warnings))
2098 def testAddHeaderWithGn(self):
2099 mock_input_api = MockInputApi()
2100 mock_input_api.files = [
2101 MockAffectedFile('base/stuff.h', ''),
2102 MockAffectedFile('base/BUILD.gn', 'stuff.h'),
2104 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2105 mock_input_api, MockOutputApi())
2106 self.assertEqual(0, len(warnings))
2108 def testAddHeaderWithGni(self):
2109 mock_input_api = MockInputApi()
2110 mock_input_api.files = [
2111 MockAffectedFile('base/stuff.h', ''),
2112 MockAffectedFile('base/files.gni', 'stuff.h'),
2114 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2115 mock_input_api, MockOutputApi())
2116 self.assertEqual(0, len(warnings))
2118 def testAddHeaderWithOther(self):
2119 mock_input_api = MockInputApi()
2120 mock_input_api.files = [
2121 MockAffectedFile('base/stuff.h', ''),
2122 MockAffectedFile('base/stuff.cc', 'stuff.h'),
2124 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2125 mock_input_api, MockOutputApi())
2126 self.assertEqual(1, len(warnings))
2128 def testAddHeaderWithWrongGn(self):
2129 mock_input_api = MockInputApi()
2130 mock_input_api.files = [
2131 MockAffectedFile('base/stuff.h', ''),
2132 MockAffectedFile('base/BUILD.gn', 'stuff_h'),
2134 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2135 mock_input_api, MockOutputApi())
2136 self.assertEqual(1, len(warnings))
2138 def testAddHeadersWithGn(self):
2139 mock_input_api = MockInputApi()
2140 mock_input_api.files = [
2141 MockAffectedFile('base/stuff.h', ''),
2142 MockAffectedFile('base/another.h', ''),
2143 MockAffectedFile('base/BUILD.gn', 'another.h\nstuff.h'),
2145 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2146 mock_input_api, MockOutputApi())
2147 self.assertEqual(0, len(warnings))
2149 def testAddHeadersWithWrongGn(self):
2150 mock_input_api = MockInputApi()
2151 mock_input_api.files = [
2152 MockAffectedFile('base/stuff.h', ''),
2153 MockAffectedFile('base/another.h', ''),
2154 MockAffectedFile('base/BUILD.gn', 'another_h\nstuff.h'),
2156 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2157 mock_input_api, MockOutputApi())
2158 self.assertEqual(1, len(warnings))
2159 self.assertFalse('base/stuff.h' in warnings[0].items)
2160 self.assertTrue('base/another.h' in warnings[0].items)
2162 def testAddHeadersWithWrongGn2(self):
2163 mock_input_api = MockInputApi()
2164 mock_input_api.files = [
2165 MockAffectedFile('base/stuff.h', ''),
2166 MockAffectedFile('base/another.h', ''),
2167 MockAffectedFile('base/BUILD.gn', 'another_h\nstuff_h'),
2169 warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2170 mock_input_api, MockOutputApi())
2171 self.assertEqual(1, len(warnings))
2172 self.assertTrue('base/stuff.h' in warnings[0].items)
2173 self.assertTrue('base/another.h' in warnings[0].items)
2176 class CorrectProductNameInMessagesTest(unittest.TestCase):
2177 def testProductNameInDesc(self):
2178 mock_input_api = MockInputApi()
2179 mock_input_api.files = [
2180 MockAffectedFile('chrome/app/google_chrome_strings.grd', [
2181 '<message name="Foo" desc="Welcome to Chrome">',
2182 ' Welcome to Chrome!',
2185 MockAffectedFile('chrome/app/chromium_strings.grd', [
2186 '<message name="Bar" desc="Welcome to Chrome">',
2187 ' Welcome to Chromium!',
2191 warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2192 mock_input_api, MockOutputApi())
2193 self.assertEqual(0, len(warnings))
2195 def testChromeInChromium(self):
2196 mock_input_api = MockInputApi()
2197 mock_input_api.files = [
2198 MockAffectedFile('chrome/app/google_chrome_strings.grd', [
2199 '<message name="Foo" desc="Welcome to Chrome">',
2200 ' Welcome to Chrome!',
2203 MockAffectedFile('chrome/app/chromium_strings.grd', [
2204 '<message name="Bar" desc="Welcome to Chrome">',
2205 ' Welcome to Chrome!',
2209 warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2210 mock_input_api, MockOutputApi())
2211 self.assertEqual(1, len(warnings))
2212 self.assertTrue('chrome/app/chromium_strings.grd' in warnings[0].items[0])
2214 def testChromiumInChrome(self):
2215 mock_input_api = MockInputApi()
2216 mock_input_api.files = [
2217 MockAffectedFile('chrome/app/google_chrome_strings.grd', [
2218 '<message name="Foo" desc="Welcome to Chrome">',
2219 ' Welcome to Chromium!',
2222 MockAffectedFile('chrome/app/chromium_strings.grd', [
2223 '<message name="Bar" desc="Welcome to Chrome">',
2224 ' Welcome to Chromium!',
2228 warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2229 mock_input_api, MockOutputApi())
2230 self.assertEqual(1, len(warnings))
2232 'chrome/app/google_chrome_strings.grd:2' in warnings[0].items[0])
2234 def testMultipleInstances(self):
2235 mock_input_api = MockInputApi()
2236 mock_input_api.files = [
2237 MockAffectedFile('chrome/app/chromium_strings.grd', [
2238 '<message name="Bar" desc="Welcome to Chrome">',
2239 ' Welcome to Chrome!',
2241 '<message name="Baz" desc="A correct message">',
2242 ' Chromium is the software you are using.',
2244 '<message name="Bat" desc="An incorrect message">',
2245 ' Google Chrome is the software you are using.',
2249 warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2250 mock_input_api, MockOutputApi())
2251 self.assertEqual(1, len(warnings))
2253 'chrome/app/chromium_strings.grd:2' in warnings[0].items[0])
2255 'chrome/app/chromium_strings.grd:8' in warnings[0].items[1])
2257 def testMultipleWarnings(self):
2258 mock_input_api = MockInputApi()
2259 mock_input_api.files = [
2260 MockAffectedFile('chrome/app/chromium_strings.grd', [
2261 '<message name="Bar" desc="Welcome to Chrome">',
2262 ' Welcome to Chrome!',
2264 '<message name="Baz" desc="A correct message">',
2265 ' Chromium is the software you are using.',
2267 '<message name="Bat" desc="An incorrect message">',
2268 ' Google Chrome is the software you are using.',
2271 MockAffectedFile('components/components_google_chrome_strings.grd', [
2272 '<message name="Bar" desc="Welcome to Chrome">',
2273 ' Welcome to Chrome!',
2275 '<message name="Baz" desc="A correct message">',
2276 ' Chromium is the software you are using.',
2278 '<message name="Bat" desc="An incorrect message">',
2279 ' Google Chrome is the software you are using.',
2283 warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2284 mock_input_api, MockOutputApi())
2285 self.assertEqual(2, len(warnings))
2287 'components/components_google_chrome_strings.grd:5'
2288 in warnings[0].items[0])
2290 'chrome/app/chromium_strings.grd:2' in warnings[1].items[0])
2292 'chrome/app/chromium_strings.grd:8' in warnings[1].items[1])
2295 class _SecurityOwnersTestCase(unittest.TestCase):
2296 def _createMockInputApi(self):
2297 mock_input_api = MockInputApi()
2298 def FakeRepositoryRoot():
2299 return mock_input_api.os_path.join('chromium', 'src')
2300 mock_input_api.change.RepositoryRoot = FakeRepositoryRoot
2301 self._injectFakeOwnersClient(
2303 ['apple@chromium.org', 'orange@chromium.org'])
2304 return mock_input_api
2306 def _setupFakeChange(self, input_api):
2307 class FakeGerrit(object):
2308 def IsOwnersOverrideApproved(self, issue):
2311 input_api.change.issue = 123
2312 input_api.gerrit = FakeGerrit()
2314 def _injectFakeOwnersClient(self, input_api, owners):
2315 class FakeOwnersClient(object):
2316 def ListOwners(self, f):
2319 input_api.owners_client = FakeOwnersClient()
2321 def _injectFakeChangeOwnerAndReviewers(self, input_api, owner, reviewers):
2322 def MockOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
2323 return [owner, reviewers]
2324 input_api.canned_checks.GetCodereviewOwnerAndReviewers = \
2325 MockOwnerAndReviewers
2328 class IpcSecurityOwnerTest(_SecurityOwnersTestCase):
2330 ('*_messages.cc', 'scary_messages.cc'),
2331 ('*_messages*.h', 'scary_messages.h'),
2332 ('*_messages*.h', 'scary_messages_android.h'),
2333 ('*_param_traits*.*', 'scary_param_traits.h'),
2334 ('*_param_traits*.*', 'scary_param_traits_win.h'),
2335 ('*.mojom', 'scary.mojom'),
2336 ('*_mojom_traits*.*', 'scary_mojom_traits.h'),
2337 ('*_mojom_traits*.*', 'scary_mojom_traits_mac.h'),
2338 ('*_type_converter*.*', 'scary_type_converter.h'),
2339 ('*_type_converter*.*', 'scary_type_converter_nacl.h'),
2340 ('*.aidl', 'scary.aidl'),
2343 def testHasCorrectPerFileRulesAndSecurityReviewer(self):
2344 mock_input_api = self._createMockInputApi()
2345 new_owners_file_path = mock_input_api.os_path.join(
2346 'services', 'goat', 'public', 'OWNERS')
2348 'per-file *.mojom=set noparent',
2349 'per-file *.mojom=file://ipc/SECURITY_OWNERS'
2351 def FakeReadFile(filename):
2353 mock_input_api.os_path.join('chromium', 'src', new_owners_file_path),
2355 return '\n'.join(new_owners_file)
2356 mock_input_api.ReadFile = FakeReadFile
2357 mock_input_api.files = [
2359 new_owners_file_path, new_owners_file),
2361 mock_input_api.os_path.join(
2362 'services', 'goat', 'public', 'goat.mojom'),
2363 ['// Scary contents.'])]
2364 self._setupFakeChange(mock_input_api)
2365 self._injectFakeChangeOwnerAndReviewers(
2366 mock_input_api, 'owner@chromium.org', ['orange@chromium.org'])
2367 mock_input_api.is_committing = True
2368 mock_input_api.dry_run = False
2369 mock_output_api = MockOutputApi()
2370 results = PRESUBMIT.CheckSecurityOwners(
2371 mock_input_api, mock_output_api)
2372 self.assertEqual(0, len(results))
2374 def testMissingSecurityReviewerAtUpload(self):
2375 mock_input_api = self._createMockInputApi()
2376 new_owners_file_path = mock_input_api.os_path.join(
2377 'services', 'goat', 'public', 'OWNERS')
2379 'per-file *.mojom=set noparent',
2380 'per-file *.mojom=file://ipc/SECURITY_OWNERS'
2382 def FakeReadFile(filename):
2384 mock_input_api.os_path.join('chromium', 'src', new_owners_file_path),
2386 return '\n'.join(new_owners_file)
2387 mock_input_api.ReadFile = FakeReadFile
2388 mock_input_api.files = [
2390 new_owners_file_path, new_owners_file),
2392 mock_input_api.os_path.join(
2393 'services', 'goat', 'public', 'goat.mojom'),
2394 ['// Scary contents.'])]
2395 self._setupFakeChange(mock_input_api)
2396 self._injectFakeChangeOwnerAndReviewers(
2397 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2398 mock_input_api.is_committing = False
2399 mock_input_api.dry_run = False
2400 mock_output_api = MockOutputApi()
2401 results = PRESUBMIT.CheckSecurityOwners(
2402 mock_input_api, mock_output_api)
2403 self.assertEqual(1, len(results))
2404 self.assertEqual('notify', results[0].type)
2406 'Review from an owner in ipc/SECURITY_OWNERS is required for the '
2407 'following newly-added files:', results[0].message)
2409 def testMissingSecurityReviewerAtDryRunCommit(self):
2410 mock_input_api = self._createMockInputApi()
2411 new_owners_file_path = mock_input_api.os_path.join(
2412 'services', 'goat', 'public', 'OWNERS')
2414 'per-file *.mojom=set noparent',
2415 'per-file *.mojom=file://ipc/SECURITY_OWNERS'
2417 def FakeReadFile(filename):
2419 mock_input_api.os_path.join('chromium', 'src', new_owners_file_path),
2421 return '\n'.join(new_owners_file)
2422 mock_input_api.ReadFile = FakeReadFile
2423 mock_input_api.files = [
2425 new_owners_file_path, new_owners_file),
2427 mock_input_api.os_path.join(
2428 'services', 'goat', 'public', 'goat.mojom'),
2429 ['// Scary contents.'])]
2430 self._setupFakeChange(mock_input_api)
2431 self._injectFakeChangeOwnerAndReviewers(
2432 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2433 mock_input_api.is_committing = True
2434 mock_input_api.dry_run = True
2435 mock_output_api = MockOutputApi()
2436 results = PRESUBMIT.CheckSecurityOwners(
2437 mock_input_api, mock_output_api)
2438 self.assertEqual(1, len(results))
2439 self.assertEqual('error', results[0].type)
2441 'Review from an owner in ipc/SECURITY_OWNERS is required for the '
2442 'following newly-added files:', results[0].message)
2444 def testMissingSecurityApprovalAtRealCommit(self):
2445 mock_input_api = self._createMockInputApi()
2446 new_owners_file_path = mock_input_api.os_path.join(
2447 'services', 'goat', 'public', 'OWNERS')
2449 'per-file *.mojom=set noparent',
2450 'per-file *.mojom=file://ipc/SECURITY_OWNERS'
2452 def FakeReadFile(filename):
2454 mock_input_api.os_path.join('chromium', 'src', new_owners_file_path),
2456 return '\n'.join(new_owners_file)
2457 mock_input_api.ReadFile = FakeReadFile
2458 mock_input_api.files = [
2460 new_owners_file_path, new_owners_file),
2462 mock_input_api.os_path.join(
2463 'services', 'goat', 'public', 'goat.mojom'),
2464 ['// Scary contents.'])]
2465 self._setupFakeChange(mock_input_api)
2466 self._injectFakeChangeOwnerAndReviewers(
2467 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2468 mock_input_api.is_committing = True
2469 mock_input_api.dry_run = False
2470 mock_output_api = MockOutputApi()
2471 results = PRESUBMIT.CheckSecurityOwners(
2472 mock_input_api, mock_output_api)
2473 self.assertEqual('error', results[0].type)
2475 'Review from an owner in ipc/SECURITY_OWNERS is required for the '
2476 'following newly-added files:', results[0].message)
2478 def testIpcChangeNeedsSecurityOwner(self):
2479 for is_committing in [True, False]:
2480 for pattern, filename in self._test_cases:
2482 line=f'is_committing={is_committing}, filename={filename}'):
2483 mock_input_api = self._createMockInputApi()
2484 mock_input_api.files = [
2486 mock_input_api.os_path.join(
2487 'services', 'goat', 'public', filename),
2488 ['// Scary contents.'])]
2489 self._setupFakeChange(mock_input_api)
2490 self._injectFakeChangeOwnerAndReviewers(
2491 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2492 mock_input_api.is_committing = is_committing
2493 mock_input_api.dry_run = False
2494 mock_output_api = MockOutputApi()
2495 results = PRESUBMIT.CheckSecurityOwners(
2496 mock_input_api, mock_output_api)
2497 self.assertEqual(1, len(results))
2498 self.assertEqual('error', results[0].type)
2499 self.assertTrue(results[0].message.replace('\\', '/').startswith(
2500 'Found missing OWNERS lines for security-sensitive files. '
2501 'Please add the following lines to services/goat/public/OWNERS:'))
2502 self.assertEqual(['ipc-security-reviews@chromium.org'],
2503 mock_output_api.more_cc)
2506 def testServiceManifestChangeNeedsSecurityOwner(self):
2507 mock_input_api = self._createMockInputApi()
2508 mock_input_api.files = [
2510 mock_input_api.os_path.join(
2511 'services', 'goat', 'public', 'cpp', 'manifest.cc'),
2513 '#include "services/goat/public/cpp/manifest.h"',
2514 'const service_manager::Manifest& GetManifest() {}',
2516 self._setupFakeChange(mock_input_api)
2517 self._injectFakeChangeOwnerAndReviewers(
2518 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2519 mock_output_api = MockOutputApi()
2520 errors = PRESUBMIT.CheckSecurityOwners(
2521 mock_input_api, mock_output_api)
2522 self.assertEqual(1, len(errors))
2523 self.assertTrue(errors[0].message.replace('\\', '/').startswith(
2524 'Found missing OWNERS lines for security-sensitive files. '
2525 'Please add the following lines to services/goat/public/cpp/OWNERS:'))
2526 self.assertEqual(['ipc-security-reviews@chromium.org'], mock_output_api.more_cc)
2528 def testNonServiceManifestSourceChangesDoNotRequireSecurityOwner(self):
2529 mock_input_api = self._createMockInputApi()
2530 self._injectFakeChangeOwnerAndReviewers(
2531 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2532 mock_input_api.files = [
2533 MockAffectedFile('some/non/service/thing/foo_manifest.cc',
2535 'const char kNoEnforcement[] = "not a manifest!";',
2537 mock_output_api = MockOutputApi()
2538 errors = PRESUBMIT.CheckSecurityOwners(
2539 mock_input_api, mock_output_api)
2540 self.assertEqual([], errors)
2541 self.assertEqual([], mock_output_api.more_cc)
2544 class FuchsiaSecurityOwnerTest(_SecurityOwnersTestCase):
2545 def testFidlChangeNeedsSecurityOwner(self):
2546 mock_input_api = self._createMockInputApi()
2547 mock_input_api.files = [
2548 MockAffectedFile('potentially/scary/ipc.fidl',
2552 self._setupFakeChange(mock_input_api)
2553 self._injectFakeChangeOwnerAndReviewers(
2554 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2555 mock_output_api = MockOutputApi()
2556 errors = PRESUBMIT.CheckSecurityOwners(
2557 mock_input_api, mock_output_api)
2558 self.assertEqual(1, len(errors))
2559 self.assertTrue(errors[0].message.replace('\\', '/').startswith(
2560 'Found missing OWNERS lines for security-sensitive files. '
2561 'Please add the following lines to potentially/scary/OWNERS:'))
2563 def testComponentManifestV1ChangeNeedsSecurityOwner(self):
2564 mock_input_api = self._createMockInputApi()
2565 mock_input_api.files = [
2566 MockAffectedFile('potentially/scary/v2_manifest.cmx',
2568 '{ "that is no": "manifest!" }'
2570 self._setupFakeChange(mock_input_api)
2571 self._injectFakeChangeOwnerAndReviewers(
2572 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2573 mock_output_api = MockOutputApi()
2574 errors = PRESUBMIT.CheckSecurityOwners(
2575 mock_input_api, mock_output_api)
2576 self.assertEqual(1, len(errors))
2577 self.assertTrue(errors[0].message.replace('\\', '/').startswith(
2578 'Found missing OWNERS lines for security-sensitive files. '
2579 'Please add the following lines to potentially/scary/OWNERS:'))
2581 def testComponentManifestV2NeedsSecurityOwner(self):
2582 mock_input_api = self._createMockInputApi()
2583 mock_input_api.files = [
2584 MockAffectedFile('potentially/scary/v2_manifest.cml',
2586 '{ "that is no": "manifest!" }'
2588 self._setupFakeChange(mock_input_api)
2589 self._injectFakeChangeOwnerAndReviewers(
2590 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2591 mock_output_api = MockOutputApi()
2592 errors = PRESUBMIT.CheckSecurityOwners(
2593 mock_input_api, mock_output_api)
2594 self.assertEqual(1, len(errors))
2595 self.assertTrue(errors[0].message.replace('\\', '/').startswith(
2596 'Found missing OWNERS lines for security-sensitive files. '
2597 'Please add the following lines to potentially/scary/OWNERS:'))
2599 def testThirdPartyTestsDoNotRequireSecurityOwner(self):
2600 mock_input_api = MockInputApi()
2601 self._injectFakeChangeOwnerAndReviewers(
2602 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2603 mock_input_api.files = [
2604 MockAffectedFile('third_party/crashpad/test/tests.cmx',
2606 'const char kNoEnforcement[] = "Security?!? Pah!";',
2608 mock_output_api = MockOutputApi()
2609 errors = PRESUBMIT.CheckSecurityOwners(
2610 mock_input_api, mock_output_api)
2611 self.assertEqual([], errors)
2613 def testOtherFuchsiaChangesDoNotRequireSecurityOwner(self):
2614 mock_input_api = MockInputApi()
2615 self._injectFakeChangeOwnerAndReviewers(
2616 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2617 mock_input_api.files = [
2618 MockAffectedFile('some/non/service/thing/fuchsia_fidl_cml_cmx_magic.cc',
2620 'const char kNoEnforcement[] = "Security?!? Pah!";',
2622 mock_output_api = MockOutputApi()
2623 errors = PRESUBMIT.CheckSecurityOwners(
2624 mock_input_api, mock_output_api)
2625 self.assertEqual([], errors)
2628 class SecurityChangeTest(_SecurityOwnersTestCase):
2629 def testDiffGetServiceSandboxType(self):
2630 mock_input_api = MockInputApi()
2631 mock_input_api.files = [
2633 'services/goat/teleporter_host.cc',
2636 'inline content::SandboxType',
2637 'content::GetServiceSandboxType<chrome::mojom::GoatTeleporter>() {',
2638 '#if defined(OS_WIN)',
2639 ' return SandboxType::kGoaty;',
2641 ' return SandboxType::kNoSandbox;',
2642 '#endif // !defined(OS_WIN)',
2647 files_to_functions = PRESUBMIT._GetFilesUsingSecurityCriticalFunctions(
2650 'services/goat/teleporter_host.cc': set([
2651 'content::GetServiceSandboxType<>()'
2655 def testDiffRemovingLine(self):
2656 mock_input_api = MockInputApi()
2657 mock_file = MockAffectedFile('services/goat/teleporter_host.cc', '')
2658 mock_file._scm_diff = """--- old 2020-05-04 14:08:25.000000000 -0400
2659 +++ new 2020-05-04 14:08:32.000000000 -0400
2662 inline content::SandboxType
2663 -content::GetServiceSandboxType<chrome::mojom::GoatTeleporter>() {
2665 return SandboxType::kGoaty;
2667 mock_input_api.files = [mock_file]
2668 files_to_functions = PRESUBMIT._GetFilesUsingSecurityCriticalFunctions(
2671 'services/goat/teleporter_host.cc': set([
2672 'content::GetServiceSandboxType<>()'
2676 def testChangeOwnersMissing(self):
2677 mock_input_api = self._createMockInputApi()
2678 self._setupFakeChange(mock_input_api)
2679 self._injectFakeChangeOwnerAndReviewers(
2680 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2681 mock_input_api.is_committing = False
2682 mock_input_api.files = [
2683 MockAffectedFile('file.cc', ['GetServiceSandboxType<Goat>(Sandbox)'])
2685 mock_output_api = MockOutputApi()
2686 result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2687 self.assertEqual(1, len(result))
2688 self.assertEqual(result[0].type, 'notify')
2689 self.assertEqual(result[0].message,
2690 'The following files change calls to security-sensitive functions\n' \
2691 'that need to be reviewed by ipc/SECURITY_OWNERS.\n'
2693 ' content::GetServiceSandboxType<>()\n\n')
2695 def testChangeOwnersMissingAtCommit(self):
2696 mock_input_api = self._createMockInputApi()
2697 self._setupFakeChange(mock_input_api)
2698 self._injectFakeChangeOwnerAndReviewers(
2699 mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2700 mock_input_api.is_committing = True
2701 mock_input_api.dry_run = False
2702 mock_input_api.files = [
2703 MockAffectedFile('file.cc', ['GetServiceSandboxType<mojom::Goat>()'])
2705 mock_output_api = MockOutputApi()
2706 result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2707 self.assertEqual(1, len(result))
2708 self.assertEqual(result[0].type, 'error')
2709 self.assertEqual(result[0].message,
2710 'The following files change calls to security-sensitive functions\n' \
2711 'that need to be reviewed by ipc/SECURITY_OWNERS.\n'
2713 ' content::GetServiceSandboxType<>()\n\n')
2715 def testChangeOwnersPresent(self):
2716 mock_input_api = self._createMockInputApi()
2717 self._injectFakeChangeOwnerAndReviewers(
2718 mock_input_api, 'owner@chromium.org',
2719 ['apple@chromium.org', 'banana@chromium.org'])
2720 mock_input_api.files = [
2721 MockAffectedFile('file.cc', ['WithSandboxType(Sandbox)'])
2723 mock_output_api = MockOutputApi()
2724 result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2725 self.assertEqual(0, len(result))
2727 def testChangeOwnerIsSecurityOwner(self):
2728 mock_input_api = self._createMockInputApi()
2729 self._setupFakeChange(mock_input_api)
2730 self._injectFakeChangeOwnerAndReviewers(
2731 mock_input_api, 'orange@chromium.org', ['pear@chromium.org'])
2732 mock_input_api.files = [
2733 MockAffectedFile('file.cc', ['GetServiceSandboxType<T>(Sandbox)'])
2735 mock_output_api = MockOutputApi()
2736 result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2737 self.assertEqual(1, len(result))
2740 class BannedTypeCheckTest(unittest.TestCase):
2742 def testBannedCppFunctions(self):
2743 input_api = MockInputApi()
2745 MockFile('some/cpp/problematic/file.cc',
2746 ['using namespace std;']),
2747 MockFile('third_party/blink/problematic/file.cc',
2748 ['GetInterfaceProvider()']),
2749 MockFile('some/cpp/ok/file.cc',
2750 ['using std::string;']),
2751 MockFile('some/cpp/problematic/file2.cc',
2752 ['set_owned_by_client()']),
2753 MockFile('some/cpp/nocheck/file.cc',
2754 ['using namespace std; // nocheck']),
2755 MockFile('some/cpp/comment/file.cc',
2756 [' // A comment about `using namespace std;`']),
2759 results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2761 # warnings are results[0], errors are results[1]
2762 self.assertEqual(2, len(results))
2763 self.assertTrue('some/cpp/problematic/file.cc' in results[1].message)
2765 'third_party/blink/problematic/file.cc' in results[0].message)
2766 self.assertTrue('some/cpp/ok/file.cc' not in results[1].message)
2767 self.assertTrue('some/cpp/problematic/file2.cc' in results[0].message)
2768 self.assertFalse('some/cpp/nocheck/file.cc' in results[0].message)
2769 self.assertFalse('some/cpp/nocheck/file.cc' in results[1].message)
2770 self.assertFalse('some/cpp/comment/file.cc' in results[0].message)
2771 self.assertFalse('some/cpp/comment/file.cc' in results[1].message)
2773 def testBannedIosObjcFunctions(self):
2774 input_api = MockInputApi()
2776 MockFile('some/ios/file.mm',
2777 ['TEST(SomeClassTest, SomeInteraction) {',
2779 MockFile('some/mac/file.mm',
2780 ['TEST(SomeClassTest, SomeInteraction) {',
2782 MockFile('another/ios_file.mm',
2783 ['class SomeTest : public testing::Test {};']),
2784 MockFile('some/ios/file_egtest.mm',
2785 ['- (void)testSomething { EXPECT_OCMOCK_VERIFY(aMock); }']),
2786 MockFile('some/ios/file_unittest.mm',
2787 ['TEST_F(SomeTest, TestThis) { EXPECT_OCMOCK_VERIFY(aMock); }']),
2790 errors = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2791 self.assertEqual(1, len(errors))
2792 self.assertTrue('some/ios/file.mm' in errors[0].message)
2793 self.assertTrue('another/ios_file.mm' in errors[0].message)
2794 self.assertTrue('some/mac/file.mm' not in errors[0].message)
2795 self.assertTrue('some/ios/file_egtest.mm' in errors[0].message)
2796 self.assertTrue('some/ios/file_unittest.mm' not in errors[0].message)
2798 def testBannedMojoFunctions(self):
2799 input_api = MockInputApi()
2801 MockFile('some/cpp/problematic/file2.cc',
2802 ['mojo::ConvertTo<>']),
2803 MockFile('third_party/blink/ok/file3.cc',
2804 ['mojo::ConvertTo<>']),
2805 MockFile('content/renderer/ok/file3.cc',
2806 ['mojo::ConvertTo<>']),
2809 results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2811 # warnings are results[0], errors are results[1]
2812 self.assertEqual(1, len(results))
2813 self.assertTrue('some/cpp/problematic/file2.cc' in results[0].message)
2814 self.assertTrue('third_party/blink/ok/file3.cc' not in results[0].message)
2815 self.assertTrue('content/renderer/ok/file3.cc' not in results[0].message)
2817 def testBannedMojomPatterns(self):
2818 input_api = MockInputApi()
2820 MockFile('bad.mojom',
2822 ' handle<shared_buffer> buffer;',
2824 MockFile('good.mojom',
2826 ' mojo_base.mojom.ReadOnlySharedMemoryRegion region1;',
2827 ' mojo_base.mojom.WritableSharedMemoryRegion region2;',
2828 ' mojo_base.mojom.UnsafeSharedMemoryRegion region3;',
2832 results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2834 # warnings are results[0], errors are results[1]
2835 self.assertEqual(1, len(results))
2836 self.assertTrue('bad.mojom' in results[0].message)
2837 self.assertTrue('good.mojom' not in results[0].message)
2839 class NoProductionCodeUsingTestOnlyFunctionsTest(unittest.TestCase):
2840 def testTruePositives(self):
2841 mock_input_api = MockInputApi()
2842 mock_input_api.files = [
2843 MockFile('some/path/foo.cc', ['foo_for_testing();']),
2844 MockFile('some/path/foo.mm', ['FooForTesting();']),
2845 MockFile('some/path/foo.cxx', ['FooForTests();']),
2846 MockFile('some/path/foo.cpp', ['foo_for_test();']),
2849 results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctions(
2850 mock_input_api, MockOutputApi())
2851 self.assertEqual(1, len(results))
2852 self.assertEqual(4, len(results[0].items))
2853 self.assertTrue('foo.cc' in results[0].items[0])
2854 self.assertTrue('foo.mm' in results[0].items[1])
2855 self.assertTrue('foo.cxx' in results[0].items[2])
2856 self.assertTrue('foo.cpp' in results[0].items[3])
2858 def testFalsePositives(self):
2859 mock_input_api = MockInputApi()
2860 mock_input_api.files = [
2861 MockFile('some/path/foo.h', ['foo_for_testing();']),
2862 MockFile('some/path/foo.mm', ['FooForTesting() {']),
2863 MockFile('some/path/foo.cc', ['::FooForTests();']),
2864 MockFile('some/path/foo.cpp', ['// foo_for_test();']),
2867 results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctions(
2868 mock_input_api, MockOutputApi())
2869 self.assertEqual(0, len(results))
2871 def testAllowedFiles(self):
2872 mock_input_api = MockInputApi()
2873 mock_input_api.files = [
2874 MockFile('path/foo_unittest.cc', ['foo_for_testing();']),
2875 MockFile('path/bar_unittest_mac.cc', ['foo_for_testing();']),
2876 MockFile('path/baz_unittests.cc', ['foo_for_testing();']),
2879 results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctions(
2880 mock_input_api, MockOutputApi())
2881 self.assertEqual(0, len(results))
2884 class NoProductionJavaCodeUsingTestOnlyFunctionsTest(unittest.TestCase):
2885 def testTruePositives(self):
2886 mock_input_api = MockInputApi()
2887 mock_input_api.files = [
2888 MockFile('dir/java/src/foo.java', ['FooForTesting();']),
2889 MockFile('dir/java/src/bar.java', ['FooForTests(x);']),
2890 MockFile('dir/java/src/baz.java', ['FooForTest(', 'y', ');']),
2891 MockFile('dir/java/src/mult.java', [
2892 'int x = SomethingLongHere()',
2893 ' * SomethingLongHereForTesting();'
2897 results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctionsJava(
2898 mock_input_api, MockOutputApi())
2899 self.assertEqual(1, len(results))
2900 self.assertEqual(4, len(results[0].items))
2901 self.assertTrue('foo.java' in results[0].items[0])
2902 self.assertTrue('bar.java' in results[0].items[1])
2903 self.assertTrue('baz.java' in results[0].items[2])
2904 self.assertTrue('mult.java' in results[0].items[3])
2906 def testFalsePositives(self):
2907 mock_input_api = MockInputApi()
2908 mock_input_api.files = [
2909 MockFile('dir/java/src/foo.xml', ['FooForTesting();']),
2910 MockFile('dir/java/src/foo.java', ['FooForTests() {']),
2911 MockFile('dir/java/src/bar.java', ['// FooForTest();']),
2912 MockFile('dir/java/src/bar2.java', ['x = 1; // FooForTest();']),
2913 MockFile('dir/java/src/bar3.java', ['@VisibleForTesting']),
2914 MockFile('dir/java/src/bar4.java', ['@VisibleForTesting()']),
2915 MockFile('dir/java/src/bar5.java', [
2916 '@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)'
2918 MockFile('dir/javatests/src/baz.java', ['FooForTest(', 'y', ');']),
2919 MockFile('dir/junit/src/baz.java', ['FooForTest(', 'y', ');']),
2920 MockFile('dir/junit/src/javadoc.java', [
2921 '/** Use FooForTest(); to obtain foo in tests.'
2924 MockFile('dir/junit/src/javadoc2.java', [
2926 ' * Use FooForTest(); to obtain foo in tests.'
2931 results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctionsJava(
2932 mock_input_api, MockOutputApi())
2933 self.assertEqual(0, len(results))
2936 class NewImagesWarningTest(unittest.TestCase):
2937 def testTruePositives(self):
2938 mock_input_api = MockInputApi()
2939 mock_input_api.files = [
2940 MockFile('dir/android/res/drawable/foo.png', []),
2941 MockFile('dir/android/res/drawable-v21/bar.svg', []),
2942 MockFile('dir/android/res/mipmap-v21-en/baz.webp', []),
2943 MockFile('dir/android/res_gshoe/drawable-mdpi/foobar.png', []),
2946 results = PRESUBMIT._CheckNewImagesWarning(mock_input_api, MockOutputApi())
2947 self.assertEqual(1, len(results))
2948 self.assertEqual(4, len(results[0].items))
2949 self.assertTrue('foo.png' in results[0].items[0].LocalPath())
2950 self.assertTrue('bar.svg' in results[0].items[1].LocalPath())
2951 self.assertTrue('baz.webp' in results[0].items[2].LocalPath())
2952 self.assertTrue('foobar.png' in results[0].items[3].LocalPath())
2954 def testFalsePositives(self):
2955 mock_input_api = MockInputApi()
2956 mock_input_api.files = [
2957 MockFile('dir/pngs/README.md', []),
2958 MockFile('java/test/res/drawable/foo.png', []),
2959 MockFile('third_party/blink/foo.png', []),
2960 MockFile('dir/third_party/libpng/src/foo.cc', ['foobar']),
2961 MockFile('dir/resources.webp/.gitignore', ['foo.png']),
2964 results = PRESUBMIT._CheckNewImagesWarning(mock_input_api, MockOutputApi())
2965 self.assertEqual(0, len(results))
2967 class ProductIconsTest(unittest.TestCase):
2969 mock_input_api = MockInputApi()
2970 mock_input_api.files = [
2971 MockFile('components/vector_icons/google_jetpack.icon', []),
2972 MockFile('components/vector_icons/generic_jetpack.icon', []),
2975 results = PRESUBMIT.CheckNoProductIconsAddedToPublicRepo(mock_input_api, MockOutputApi())
2976 self.assertEqual(1, len(results))
2977 self.assertEqual(1, len(results[0].items))
2978 self.assertTrue('google_jetpack.icon' in results[0].items[0])
2980 class CheckUniquePtrTest(unittest.TestCase):
2981 def testTruePositivesNullptr(self):
2982 mock_input_api = MockInputApi()
2983 mock_input_api.files = [
2984 MockFile('dir/baz.cc', ['std::unique_ptr<T>()']),
2985 MockFile('dir/baz-p.cc', ['std::unique_ptr<T<P>>()']),
2988 results = PRESUBMIT.CheckUniquePtrOnUpload(mock_input_api, MockOutputApi())
2989 self.assertEqual(1, len(results))
2990 self.assertTrue('nullptr' in results[0].message)
2991 self.assertEqual(2, len(results[0].items))
2992 self.assertTrue('baz.cc' in results[0].items[0])
2993 self.assertTrue('baz-p.cc' in results[0].items[1])
2995 def testTruePositivesConstructor(self):
2996 mock_input_api = MockInputApi()
2997 mock_input_api.files = [
2998 MockFile('dir/foo.cc', ['return std::unique_ptr<T>(foo);']),
2999 MockFile('dir/bar.mm', ['bar = std::unique_ptr<T>(foo)']),
3000 MockFile('dir/mult.cc', [
3002 ' std::unique_ptr<T>(barVeryVeryLongFooSoThatItWouldNotFitAbove);'
3004 MockFile('dir/mult2.cc', [
3005 'barVeryVeryLongLongBaaaaaarSoThatTheLineLimitIsAlmostReached =',
3006 ' std::unique_ptr<T>(foo);'
3008 MockFile('dir/mult3.cc', [
3009 'bar = std::unique_ptr<T>(',
3010 ' fooVeryVeryVeryLongStillGoingWellThisWillTakeAWhileFinallyThere);'
3012 MockFile('dir/multi_arg.cc', [
3013 'auto p = std::unique_ptr<std::pair<T, D>>(new std::pair(T, D));']),
3016 results = PRESUBMIT.CheckUniquePtrOnUpload(mock_input_api, MockOutputApi())
3017 self.assertEqual(1, len(results))
3018 self.assertTrue('std::make_unique' in results[0].message)
3019 self.assertEqual(6, len(results[0].items))
3020 self.assertTrue('foo.cc' in results[0].items[0])
3021 self.assertTrue('bar.mm' in results[0].items[1])
3022 self.assertTrue('mult.cc' in results[0].items[2])
3023 self.assertTrue('mult2.cc' in results[0].items[3])
3024 self.assertTrue('mult3.cc' in results[0].items[4])
3025 self.assertTrue('multi_arg.cc' in results[0].items[5])
3027 def testFalsePositives(self):
3028 mock_input_api = MockInputApi()
3029 mock_input_api.files = [
3030 MockFile('dir/foo.cc', ['return std::unique_ptr<T[]>(foo);']),
3031 MockFile('dir/bar.mm', ['bar = std::unique_ptr<T[]>(foo)']),
3032 MockFile('dir/file.cc', ['std::unique_ptr<T> p = Foo();']),
3033 MockFile('dir/baz.cc', [
3034 'std::unique_ptr<T> result = std::make_unique<T>();'
3036 MockFile('dir/baz2.cc', [
3037 'std::unique_ptr<T> result = std::make_unique<T>('
3039 MockFile('dir/nested.cc', ['set<std::unique_ptr<T>>();']),
3040 MockFile('dir/nested2.cc', ['map<U, std::unique_ptr<T>>();']),
3042 # Two-argument invocation of std::unique_ptr is exempt because there is
3043 # no equivalent using std::make_unique.
3044 MockFile('dir/multi_arg.cc', [
3045 'auto p = std::unique_ptr<T, D>(new T(), D());']),
3048 results = PRESUBMIT.CheckUniquePtrOnUpload(mock_input_api, MockOutputApi())
3049 self.assertEqual(0, len(results))
3051 class CheckNoDirectIncludesHeadersWhichRedefineStrCat(unittest.TestCase):
3052 def testBlocksDirectIncludes(self):
3053 mock_input_api = MockInputApi()
3054 mock_input_api.files = [
3055 MockFile('dir/foo_win.cc', ['#include "shlwapi.h"']),
3056 MockFile('dir/bar.h', ['#include <propvarutil.h>']),
3057 MockFile('dir/baz.h', ['#include <atlbase.h>']),
3058 MockFile('dir/jumbo.h', ['#include "sphelper.h"']),
3060 results = PRESUBMIT.CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
3061 self.assertEqual(1, len(results))
3062 self.assertEqual(4, len(results[0].items))
3063 self.assertTrue('StrCat' in results[0].message)
3064 self.assertTrue('foo_win.cc' in results[0].items[0])
3065 self.assertTrue('bar.h' in results[0].items[1])
3066 self.assertTrue('baz.h' in results[0].items[2])
3067 self.assertTrue('jumbo.h' in results[0].items[3])
3069 def testAllowsToIncludeWrapper(self):
3070 mock_input_api = MockInputApi()
3071 mock_input_api.files = [
3072 MockFile('dir/baz_win.cc', ['#include "base/win/shlwapi.h"']),
3073 MockFile('dir/baz-win.h', ['#include "base/win/atl.h"']),
3075 results = PRESUBMIT.CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
3076 self.assertEqual(0, len(results))
3078 def testAllowsToCreateWrapper(self):
3079 mock_input_api = MockInputApi()
3080 mock_input_api.files = [
3081 MockFile('base/win/shlwapi.h', [
3082 '#include <shlwapi.h>',
3083 '#include "base/win/windows_defines.inc"']),
3085 results = PRESUBMIT.CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
3086 self.assertEqual(0, len(results))
3088 def testIgnoresNonImplAndHeaders(self):
3089 mock_input_api = MockInputApi()
3090 mock_input_api.files = [
3091 MockFile('dir/foo_win.txt', ['#include "shlwapi.h"']),
3092 MockFile('dir/bar.asm', ['#include <propvarutil.h>']),
3094 results = PRESUBMIT.CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
3095 self.assertEqual(0, len(results))
3098 class StringTest(unittest.TestCase):
3099 """Tests ICU syntax check and translation screenshots check."""
3101 # An empty grd file.
3102 OLD_GRD_CONTENTS = """<?xml version="1.0" encoding="UTF-8"?>
3103 <grit latest_public_release="1" current_release="1">
3105 <messages></messages>
3109 # A grd file with a single message.
3110 NEW_GRD_CONTENTS1 = """<?xml version="1.0" encoding="UTF-8"?>
3111 <grit latest_public_release="1" current_release="1">
3114 <message name="IDS_TEST1">
3117 <message name="IDS_TEST_STRING_NON_TRANSLATEABLE1"
3118 translateable="false">
3119 Non translateable message 1, should be ignored
3121 <message name="IDS_TEST_STRING_ACCESSIBILITY"
3122 is_accessibility_with_no_ui="true">
3123 Accessibility label 1, should be ignored
3129 # A grd file with two messages.
3130 NEW_GRD_CONTENTS2 = """<?xml version="1.0" encoding="UTF-8"?>
3131 <grit latest_public_release="1" current_release="1">
3134 <message name="IDS_TEST1">
3137 <message name="IDS_TEST2">
3140 <message name="IDS_TEST_STRING_NON_TRANSLATEABLE2"
3141 translateable="false">
3142 Non translateable message 2, should be ignored
3148 # A grd file with one ICU syntax message without syntax errors.
3149 NEW_GRD_CONTENTS_ICU_SYNTAX_OK1 = """<?xml version="1.0" encoding="UTF-8"?>
3150 <grit latest_public_release="1" current_release="1">
3153 <message name="IDS_TEST1">
3155 =1 {Test text for numeric one}
3156 other {Test text for plural with {NUM} as number}}
3162 # A grd file with one ICU syntax message without syntax errors.
3163 NEW_GRD_CONTENTS_ICU_SYNTAX_OK2 = """<?xml version="1.0" encoding="UTF-8"?>
3164 <grit latest_public_release="1" current_release="1">
3167 <message name="IDS_TEST1">
3169 =1 {Different test text for numeric one}
3170 other {Different test text for plural with {NUM} as number}}
3176 # A grd file with one ICU syntax message with syntax errors (misses a comma).
3177 NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR = """<?xml version="1.0" encoding="UTF-8"?>
3178 <grit latest_public_release="1" current_release="1">
3181 <message name="IDS_TEST1">
3183 =1 {Test text for numeric one}
3184 other {Test text for plural with {NUM} as number}}
3191 OLD_GRDP_CONTENTS = (
3192 '<?xml version="1.0" encoding="utf-8"?>',
3197 NEW_GRDP_CONTENTS1 = (
3198 '<?xml version="1.0" encoding="utf-8"?>',
3200 '<message name="IDS_PART_TEST1">',
3205 NEW_GRDP_CONTENTS2 = (
3206 '<?xml version="1.0" encoding="utf-8"?>',
3208 '<message name="IDS_PART_TEST1">',
3211 '<message name="IDS_PART_TEST2">',
3216 NEW_GRDP_CONTENTS3 = (
3217 '<?xml version="1.0" encoding="utf-8"?>',
3219 '<message name="IDS_PART_TEST1" desc="Description with typo.">',
3224 NEW_GRDP_CONTENTS4 = (
3225 '<?xml version="1.0" encoding="utf-8"?>',
3227 '<message name="IDS_PART_TEST1" desc="Description with typo fixed.">',
3232 NEW_GRDP_CONTENTS5 = (
3233 '<?xml version="1.0" encoding="utf-8"?>',
3235 '<message name="IDS_PART_TEST1" meaning="Meaning with typo.">',
3240 NEW_GRDP_CONTENTS6 = (
3241 '<?xml version="1.0" encoding="utf-8"?>',
3243 '<message name="IDS_PART_TEST1" meaning="Meaning with typo fixed.">',
3248 # A grdp file with one ICU syntax message without syntax errors.
3249 NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1 = (
3250 '<?xml version="1.0" encoding="utf-8"?>',
3252 '<message name="IDS_PART_TEST1">',
3254 '=1 {Test text for numeric one}',
3255 'other {Test text for plural with {NUM} as number}}',
3258 # A grdp file with one ICU syntax message without syntax errors.
3259 NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2 = (
3260 '<?xml version="1.0" encoding="utf-8"?>',
3262 '<message name="IDS_PART_TEST1">',
3264 '=1 {Different test text for numeric one}',
3265 'other {Different test text for plural with {NUM} as number}}',
3269 # A grdp file with one ICU syntax message with syntax errors (superfluent
3271 NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR = (
3272 '<?xml version="1.0" encoding="utf-8"?>',
3274 '<message name="IDS_PART_TEST1">',
3276 '= 1 {Test text for numeric one}',
3277 'other {Test text for plural with {NUM} as number}}',
3281 DO_NOT_UPLOAD_PNG_MESSAGE = ('Do not include actual screenshots in the '
3283 'tools/translate/upload_screenshots.py to '
3284 'upload them instead:')
3285 GENERATE_SIGNATURES_MESSAGE = ('You are adding or modifying UI strings.\n'
3286 'To ensure the best translations, take '
3287 'screenshots of the relevant UI '
3288 '(https://g.co/chrome/translation) and add '
3289 'these files to your changelist:')
3290 REMOVE_SIGNATURES_MESSAGE = ('You removed strings associated with these '
3292 ICU_SYNTAX_ERROR_MESSAGE = ('ICU syntax errors were found in the following '
3293 'strings (problems or feedback? Contact '
3294 'rainhard@chromium.org):')
3296 def makeInputApi(self, files):
3297 input_api = MockInputApi()
3298 input_api.files = files
3299 # Override os_path.exists because the presubmit uses the actual
3301 input_api.CreateMockFileInPath(
3302 [x.LocalPath() for x in input_api.AffectedFiles(include_deletes=True)])
3305 """ CL modified and added messages, but didn't add any screenshots."""
3306 def testNoScreenshots(self):
3307 # No new strings (file contents same). Should not warn.
3308 input_api = self.makeInputApi([
3309 MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS1,
3310 self.NEW_GRD_CONTENTS1, action='M'),
3311 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS1,
3312 self.NEW_GRDP_CONTENTS1, action='M')])
3313 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3314 self.assertEqual(0, len(warnings))
3316 # Add two new strings. Should have two warnings.
3317 input_api = self.makeInputApi([
3318 MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS2,
3319 self.NEW_GRD_CONTENTS1, action='M'),
3320 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS2,
3321 self.NEW_GRDP_CONTENTS1, action='M')])
3322 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3323 self.assertEqual(1, len(warnings))
3324 self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[0].message)
3325 self.assertEqual('error', warnings[0].type)
3327 os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3328 os.path.join('test_grd', 'IDS_TEST2.png.sha1')],
3331 # Add four new strings. Should have four warnings.
3332 input_api = self.makeInputApi([
3333 MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS2,
3334 self.OLD_GRD_CONTENTS, action='M'),
3335 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS2,
3336 self.OLD_GRDP_CONTENTS, action='M')])
3337 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3338 self.assertEqual(1, len(warnings))
3339 self.assertEqual('error', warnings[0].type)
3340 self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[0].message)
3342 os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3343 os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3344 os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3345 os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3346 ], warnings[0].items)
3348 def testModifiedMessageDescription(self):
3349 # CL modified a message description for a message that does not yet have a
3350 # screenshot. Should not warn.
3351 input_api = self.makeInputApi([
3352 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS3,
3353 self.NEW_GRDP_CONTENTS4, action='M')])
3354 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3355 self.assertEqual(0, len(warnings))
3357 # CL modified a message description for a message that already has a
3358 # screenshot. Should not warn.
3359 input_api = self.makeInputApi([
3360 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS3,
3361 self.NEW_GRDP_CONTENTS4, action='M'),
3362 MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3363 'binary', action='A')])
3364 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3365 self.assertEqual(0, len(warnings))
3367 def testModifiedMessageMeaning(self):
3368 # CL modified a message meaning for a message that does not yet have a
3369 # screenshot. Should warn.
3370 input_api = self.makeInputApi([
3371 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS5,
3372 self.NEW_GRDP_CONTENTS6, action='M')])
3373 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3374 self.assertEqual(1, len(warnings))
3376 # CL modified a message meaning for a message that already has a
3377 # screenshot. Should not warn.
3378 input_api = self.makeInputApi([
3379 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS5,
3380 self.NEW_GRDP_CONTENTS6, action='M'),
3381 MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3382 'binary', action='A')])
3383 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3384 self.assertEqual(0, len(warnings))
3386 def testPngAddedSha1NotAdded(self):
3387 # CL added one new message in a grd file and added the png file associated
3388 # with it, but did not add the corresponding sha1 file. This should warn
3390 # - Once for the added png file (because we don't want developers to upload
3392 # - Once for the missing .sha1 file
3393 input_api = self.makeInputApi([
3396 self.NEW_GRD_CONTENTS1,
3397 self.OLD_GRD_CONTENTS,
3400 os.path.join('test_grd', 'IDS_TEST1.png'), 'binary', action='A')
3402 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3403 self.assertEqual(2, len(warnings))
3404 self.assertEqual('error', warnings[0].type)
3405 self.assertEqual(self.DO_NOT_UPLOAD_PNG_MESSAGE, warnings[0].message)
3406 self.assertEqual([os.path.join('test_grd', 'IDS_TEST1.png')],
3408 self.assertEqual('error', warnings[1].type)
3409 self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[1].message)
3410 self.assertEqual([os.path.join('test_grd', 'IDS_TEST1.png.sha1')],
3413 # CL added two messages (one in grd, one in grdp) and added the png files
3414 # associated with the messages, but did not add the corresponding sha1
3415 # files. This should warn twice:
3416 # - Once for the added png files (because we don't want developers to upload
3418 # - Once for the missing .sha1 files
3419 input_api = self.makeInputApi([
3423 self.NEW_GRD_CONTENTS1,
3424 self.OLD_GRD_CONTENTS,
3428 self.NEW_GRDP_CONTENTS1,
3429 self.OLD_GRDP_CONTENTS,
3433 os.path.join('test_grd', 'IDS_TEST1.png'), 'binary', action='A'),
3435 os.path.join('part_grdp', 'IDS_PART_TEST1.png'), 'binary',
3438 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3439 self.assertEqual(2, len(warnings))
3440 self.assertEqual('error', warnings[0].type)
3441 self.assertEqual(self.DO_NOT_UPLOAD_PNG_MESSAGE, warnings[0].message)
3442 self.assertEqual([os.path.join('part_grdp', 'IDS_PART_TEST1.png'),
3443 os.path.join('test_grd', 'IDS_TEST1.png')],
3445 self.assertEqual('error', warnings[0].type)
3446 self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[1].message)
3447 self.assertEqual([os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3448 os.path.join('test_grd', 'IDS_TEST1.png.sha1')],
3451 def testScreenshotsWithSha1(self):
3452 # CL added four messages (two each in a grd and grdp) and their
3453 # corresponding .sha1 files. No warnings.
3454 input_api = self.makeInputApi([
3458 self.NEW_GRD_CONTENTS2,
3459 self.OLD_GRD_CONTENTS,
3463 self.NEW_GRDP_CONTENTS2,
3464 self.OLD_GRDP_CONTENTS,
3468 os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3472 os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3476 os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3480 os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3484 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3485 self.assertEqual([], warnings)
3487 def testScreenshotsRemovedWithSha1(self):
3488 # Replace new contents with old contents in grd and grp files, removing
3489 # IDS_TEST1, IDS_TEST2, IDS_PART_TEST1 and IDS_PART_TEST2.
3490 # Should warn to remove the sha1 files associated with these strings.
3491 input_api = self.makeInputApi([
3495 self.OLD_GRD_CONTENTS, # new_contents
3496 self.NEW_GRD_CONTENTS2, # old_contents
3500 self.OLD_GRDP_CONTENTS, # new_contents
3501 self.NEW_GRDP_CONTENTS2, # old_contents
3504 MockFile(os.path.join('test_grd', 'IDS_TEST1.png.sha1'), 'binary', ''),
3505 MockFile(os.path.join('test_grd', 'IDS_TEST2.png.sha1'), 'binary', ''),
3506 MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3508 MockFile(os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3511 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3512 self.assertEqual(1, len(warnings))
3513 self.assertEqual('error', warnings[0].type)
3514 self.assertEqual(self.REMOVE_SIGNATURES_MESSAGE, warnings[0].message)
3516 os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3517 os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3518 os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3519 os.path.join('test_grd', 'IDS_TEST2.png.sha1')
3520 ], warnings[0].items)
3522 # Same as above, but this time one of the .sha1 files is also removed.
3523 input_api = self.makeInputApi([
3527 self.OLD_GRD_CONTENTS, # new_contents
3528 self.NEW_GRD_CONTENTS2, # old_contents
3532 self.OLD_GRDP_CONTENTS, # new_contents
3533 self.NEW_GRDP_CONTENTS2, # old_contents
3536 MockFile(os.path.join('test_grd', 'IDS_TEST1.png.sha1'), 'binary', ''),
3537 MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3541 os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3546 os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3551 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3552 self.assertEqual(1, len(warnings))
3553 self.assertEqual('error', warnings[0].type)
3554 self.assertEqual(self.REMOVE_SIGNATURES_MESSAGE, warnings[0].message)
3555 self.assertEqual([os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3556 os.path.join('test_grd', 'IDS_TEST1.png.sha1')
3557 ], warnings[0].items)
3559 # Remove all sha1 files. There should be no warnings.
3560 input_api = self.makeInputApi([
3564 self.OLD_GRD_CONTENTS,
3565 self.NEW_GRD_CONTENTS2,
3569 self.OLD_GRDP_CONTENTS,
3570 self.NEW_GRDP_CONTENTS2,
3574 os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3578 os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3582 os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3586 os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3590 warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3591 self.assertEqual([], warnings)
3593 def testIcuSyntax(self):
3594 # Add valid ICU syntax string. Should not raise an error.
3595 input_api = self.makeInputApi([
3596 MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK2,
3597 self.NEW_GRD_CONTENTS1, action='M'),
3598 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2,
3599 self.NEW_GRDP_CONTENTS1, action='M')])
3600 results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3601 # We expect no ICU syntax errors.
3602 icu_errors = [e for e in results
3603 if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3604 self.assertEqual(0, len(icu_errors))
3606 # Valid changes in ICU syntax. Should not raise an error.
3607 input_api = self.makeInputApi([
3608 MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK2,
3609 self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK1, action='M'),
3610 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2,
3611 self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1, action='M')])
3612 results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3613 # We expect no ICU syntax errors.
3614 icu_errors = [e for e in results
3615 if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3616 self.assertEqual(0, len(icu_errors))
3618 # Add invalid ICU syntax strings. Should raise two errors.
3619 input_api = self.makeInputApi([
3620 MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR,
3621 self.NEW_GRD_CONTENTS1, action='M'),
3622 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR,
3623 self.NEW_GRD_CONTENTS1, action='M')])
3624 results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3625 # We expect 2 ICU syntax errors.
3626 icu_errors = [e for e in results
3627 if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3628 self.assertEqual(1, len(icu_errors))
3630 'IDS_TEST1: This message looks like an ICU plural, but does not follow '
3632 'IDS_PART_TEST1: Variant "= 1" is not valid for plural message'
3633 ], icu_errors[0].items)
3635 # Change two strings to have ICU syntax errors. Should raise two errors.
3636 input_api = self.makeInputApi([
3637 MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR,
3638 self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK1, action='M'),
3639 MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR,
3640 self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1, action='M')])
3641 results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3642 # We expect 2 ICU syntax errors.
3643 icu_errors = [e for e in results
3644 if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3645 self.assertEqual(1, len(icu_errors))
3647 'IDS_TEST1: This message looks like an ICU plural, but does not follow '
3649 'IDS_PART_TEST1: Variant "= 1" is not valid for plural message'
3650 ], icu_errors[0].items)
3653 class TranslationExpectationsTest(unittest.TestCase):
3654 ERROR_MESSAGE_FORMAT = (
3655 "Failed to get a list of translatable grd files. "
3656 "This happens when:\n"
3657 " - One of the modified grd or grdp files cannot be parsed or\n"
3658 " - %s is not updated.\n"
3661 REPO_ROOT = os.path.join('tools', 'translation', 'testdata')
3662 # This lists all .grd files under REPO_ROOT.
3663 EXPECTATIONS = os.path.join(REPO_ROOT,
3664 "translation_expectations.pyl")
3665 # This lists all .grd files under REPO_ROOT except unlisted.grd.
3666 EXPECTATIONS_WITHOUT_UNLISTED_FILE = os.path.join(
3667 REPO_ROOT, "translation_expectations_without_unlisted_file.pyl")
3669 # Tests that the presubmit doesn't return when no grd or grdp files are
3671 def testExpectationsNoModifiedGrd(self):
3672 input_api = MockInputApi()
3674 MockAffectedFile('not_used.txt', 'not used', 'not used', action='M')
3676 # Fake list of all grd files in the repo. This list is missing all grd/grdps
3677 # under tools/translation/testdata. This is OK because the presubmit won't
3678 # run in the first place since there are no modified grd/grps in input_api.
3679 grd_files = ['doesnt_exist_doesnt_matter.grd']
3680 warnings = PRESUBMIT.CheckTranslationExpectations(
3681 input_api, MockOutputApi(), self.REPO_ROOT, self.EXPECTATIONS,
3683 self.assertEqual(0, len(warnings))
3686 # Tests that the list of files passed to the presubmit matches the list of
3687 # files in the expectations.
3688 def testExpectationsSuccess(self):
3689 # Mock input file list needs a grd or grdp file in order to run the
3690 # presubmit. The file itself doesn't matter.
3691 input_api = MockInputApi()
3693 MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3695 # List of all grd files in the repo.
3696 grd_files = ['test.grd', 'unlisted.grd', 'not_translated.grd',
3698 warnings = PRESUBMIT.CheckTranslationExpectations(
3699 input_api, MockOutputApi(), self.REPO_ROOT, self.EXPECTATIONS,
3701 self.assertEqual(0, len(warnings))
3703 # Tests that the presubmit warns when a file is listed in expectations, but
3704 # does not actually exist.
3705 def testExpectationsMissingFile(self):
3706 # Mock input file list needs a grd or grdp file in order to run the
3708 input_api = MockInputApi()
3710 MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3712 # unlisted.grd is listed under tools/translation/testdata but is not
3713 # included in translation expectations.
3714 grd_files = ['unlisted.grd', 'not_translated.grd', 'internal.grd']
3715 warnings = PRESUBMIT.CheckTranslationExpectations(
3716 input_api, MockOutputApi(), self.REPO_ROOT, self.EXPECTATIONS,
3718 self.assertEqual(1, len(warnings))
3719 self.assertTrue(warnings[0].message.startswith(
3720 self.ERROR_MESSAGE_FORMAT % self.EXPECTATIONS))
3722 ("test.grd is listed in the translation expectations, "
3723 "but this grd file does not exist")
3724 in warnings[0].message)
3726 # Tests that the presubmit warns when a file is not listed in expectations but
3727 # does actually exist.
3728 def testExpectationsUnlistedFile(self):
3729 # Mock input file list needs a grd or grdp file in order to run the
3731 input_api = MockInputApi()
3733 MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3735 # unlisted.grd is listed under tools/translation/testdata but is not
3736 # included in translation expectations.
3737 grd_files = ['test.grd', 'unlisted.grd', 'not_translated.grd',
3739 warnings = PRESUBMIT.CheckTranslationExpectations(
3740 input_api, MockOutputApi(), self.REPO_ROOT,
3741 self.EXPECTATIONS_WITHOUT_UNLISTED_FILE, grd_files)
3742 self.assertEqual(1, len(warnings))
3743 self.assertTrue(warnings[0].message.startswith(
3744 self.ERROR_MESSAGE_FORMAT % self.EXPECTATIONS_WITHOUT_UNLISTED_FILE))
3746 ("unlisted.grd appears to be translatable "
3747 "(because it contains <file> or <message> elements), "
3748 "but is not listed in the translation expectations.")
3749 in warnings[0].message)
3751 # Tests that the presubmit warns twice:
3752 # - for a non-existing file listed in expectations
3753 # - for an existing file not listed in expectations
3754 def testMultipleWarnings(self):
3755 # Mock input file list needs a grd or grdp file in order to run the
3757 input_api = MockInputApi()
3759 MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3761 # unlisted.grd is listed under tools/translation/testdata but is not
3762 # included in translation expectations.
3763 # test.grd is not listed under tools/translation/testdata but is included
3764 # in translation expectations.
3765 grd_files = ['unlisted.grd', 'not_translated.grd', 'internal.grd']
3766 warnings = PRESUBMIT.CheckTranslationExpectations(
3767 input_api, MockOutputApi(), self.REPO_ROOT,
3768 self.EXPECTATIONS_WITHOUT_UNLISTED_FILE, grd_files)
3769 self.assertEqual(1, len(warnings))
3770 self.assertTrue(warnings[0].message.startswith(
3771 self.ERROR_MESSAGE_FORMAT % self.EXPECTATIONS_WITHOUT_UNLISTED_FILE))
3773 ("unlisted.grd appears to be translatable "
3774 "(because it contains <file> or <message> elements), "
3775 "but is not listed in the translation expectations.")
3776 in warnings[0].message)
3778 ("test.grd is listed in the translation expectations, "
3779 "but this grd file does not exist")
3780 in warnings[0].message)
3783 class DISABLETypoInTest(unittest.TestCase):
3785 def testPositive(self):
3786 # Verify the typo "DISABLE_" instead of "DISABLED_" in various contexts
3787 # where the desire is to disable a test.
3789 # Disabled on one platform:
3790 '#if defined(OS_WIN)\n'
3791 '#define MAYBE_FoobarTest DISABLE_FoobarTest\n'
3793 '#define MAYBE_FoobarTest FoobarTest\n'
3795 # Disabled on one platform spread cross lines:
3796 '#if defined(OS_WIN)\n'
3797 '#define MAYBE_FoobarTest \\\n'
3798 ' DISABLE_FoobarTest\n'
3800 '#define MAYBE_FoobarTest FoobarTest\n'
3802 # Disabled on all platforms:
3803 ' TEST_F(FoobarTest, DISABLE_Foo)\n{\n}',
3804 # Disabled on all platforms but multiple lines
3805 ' TEST_F(FoobarTest,\n DISABLE_foo){\n}\n',
3809 mock_input_api = MockInputApi()
3810 mock_input_api.files = [
3811 MockFile('some/path/foo_unittest.cc', test.splitlines()),
3814 results = PRESUBMIT.CheckNoDISABLETypoInTests(mock_input_api,
3819 msg=('expected len(results) == 1 but got %d in test: %s' %
3820 (len(results), test)))
3822 'foo_unittest.cc' in results[0].message,
3823 msg=('expected foo_unittest.cc in message but got %s in test %s' %
3824 (results[0].message, test)))
3826 def testIgnoreNotTestFiles(self):
3827 mock_input_api = MockInputApi()
3828 mock_input_api.files = [
3829 MockFile('some/path/foo.cc', 'TEST_F(FoobarTest, DISABLE_Foo)'),
3832 results = PRESUBMIT.CheckNoDISABLETypoInTests(mock_input_api,
3834 self.assertEqual(0, len(results))
3836 def testIgnoreDeletedFiles(self):
3837 mock_input_api = MockInputApi()
3838 mock_input_api.files = [
3839 MockFile('some/path/foo.cc', 'TEST_F(FoobarTest, Foo)', action='D'),
3842 results = PRESUBMIT.CheckNoDISABLETypoInTests(mock_input_api,
3844 self.assertEqual(0, len(results))
3846 class ForgettingMAYBEInTests(unittest.TestCase):
3847 def testPositive(self):
3849 '#if defined(HAS_ENERGY)\n'
3850 '#define MAYBE_CastExplosion DISABLED_CastExplosion\n'
3852 '#define MAYBE_CastExplosion CastExplosion\n'
3854 'TEST_F(ArchWizard, CastExplosion) {\n'
3855 '#if defined(ARCH_PRIEST_IN_PARTY)\n'
3856 '#define MAYBE_ArchPriest ArchPriest\n'
3858 '#define MAYBE_ArchPriest DISABLED_ArchPriest\n'
3860 'TEST_F(ArchPriest, CastNaturesBounty) {\n'
3861 '#if !defined(CRUSADER_IN_PARTY)\n'
3862 '#define MAYBE_Crusader \\\n'
3863 ' DISABLED_Crusader \n'
3865 '#define MAYBE_Crusader \\\n'
3871 '#if defined(LEARNED_BASIC_SKILLS)\n'
3872 '#define MAYBE_CastSteal \\\n'
3873 ' DISABLED_CastSteal \n'
3875 '#define MAYBE_CastSteal \\\n'
3882 mock_input_api = MockInputApi()
3883 mock_input_api.files = [
3884 MockFile('fantasyworld/classes_unittest.cc', test.splitlines()),
3886 results = PRESUBMIT.CheckForgettingMAYBEInTests(mock_input_api,
3888 self.assertEqual(4, len(results))
3889 self.assertTrue('CastExplosion' in results[0].message)
3890 self.assertTrue('fantasyworld/classes_unittest.cc:2' in results[0].message)
3891 self.assertTrue('ArchPriest' in results[1].message)
3892 self.assertTrue('fantasyworld/classes_unittest.cc:8' in results[1].message)
3893 self.assertTrue('Crusader' in results[2].message)
3894 self.assertTrue('fantasyworld/classes_unittest.cc:14' in results[2].message)
3895 self.assertTrue('CastSteal' in results[3].message)
3896 self.assertTrue('fantasyworld/classes_unittest.cc:24' in results[3].message)
3898 def testNegative(self):
3900 '#if defined(HAS_ENERGY)\n'
3901 '#define MAYBE_CastExplosion DISABLED_CastExplosion\n'
3903 '#define MAYBE_CastExplosion CastExplosion\n'
3905 'TEST_F(ArchWizard, MAYBE_CastExplosion) {\n'
3906 '#if defined(ARCH_PRIEST_IN_PARTY)\n'
3907 '#define MAYBE_ArchPriest ArchPriest\n'
3909 '#define MAYBE_ArchPriest DISABLED_ArchPriest\n'
3911 'TEST_F(MAYBE_ArchPriest, CastNaturesBounty) {\n'
3912 '#if !defined(CRUSADER_IN_PARTY)\n'
3913 '#define MAYBE_Crusader \\\n'
3914 ' DISABLED_Crusader \n'
3916 '#define MAYBE_Crusader \\\n'
3920 ' MAYBE_Crusader,\n'
3922 '#if defined(LEARNED_BASIC_SKILLS)\n'
3923 '#define MAYBE_CastSteal \\\n'
3924 ' DISABLED_CastSteal \n'
3926 '#define MAYBE_CastSteal \\\n'
3931 ' MAYBE_CastSteal) { }\n'
3934 mock_input_api = MockInputApi()
3935 mock_input_api.files = [
3936 MockFile('fantasyworld/classes_unittest.cc', test.splitlines()),
3938 results = PRESUBMIT.CheckForgettingMAYBEInTests(mock_input_api,
3940 self.assertEqual(0, len(results))
3942 class CheckFuzzTargetsTest(unittest.TestCase):
3944 def _check(self, files):
3945 mock_input_api = MockInputApi()
3946 mock_input_api.files = []
3947 for fname, contents in files.items():
3948 mock_input_api.files.append(MockFile(fname, contents.splitlines()))
3949 return PRESUBMIT.CheckFuzzTargetsOnUpload(mock_input_api, MockOutputApi())
3951 def testLibFuzzerSourcesIgnored(self):
3952 results = self._check({
3953 "third_party/lib/Fuzzer/FuzzerDriver.cpp": "LLVMFuzzerInitialize",
3955 self.assertEqual(results, [])
3957 def testNonCodeFilesIgnored(self):
3958 results = self._check({
3959 "README.md": "LLVMFuzzerInitialize",
3961 self.assertEqual(results, [])
3963 def testNoErrorHeaderPresent(self):
3964 results = self._check({
3966 "#include \"testing/libfuzzer/libfuzzer_exports.h\"\n" +
3967 "LLVMFuzzerInitialize"
3970 self.assertEqual(results, [])
3972 def testErrorMissingHeader(self):
3973 results = self._check({
3974 "fuzzer.cc": "LLVMFuzzerInitialize"
3976 self.assertEqual(len(results), 1)
3977 self.assertEqual(results[0].items, ['fuzzer.cc'])
3980 class SetNoParentTest(unittest.TestCase):
3981 def testSetNoParentTopLevelAllowed(self):
3982 mock_input_api = MockInputApi()
3983 mock_input_api.files = [
3984 MockAffectedFile('goat/OWNERS',
3987 'jochen@chromium.org',
3990 mock_output_api = MockOutputApi()
3991 errors = PRESUBMIT.CheckSetNoParent(mock_input_api, mock_output_api)
3992 self.assertEqual([], errors)
3994 def testSetNoParentMissing(self):
3995 mock_input_api = MockInputApi()
3996 mock_input_api.files = [
3997 MockAffectedFile('services/goat/OWNERS',
4000 'jochen@chromium.org',
4001 'per-file *.json=set noparent',
4002 'per-file *.json=jochen@chromium.org',
4005 mock_output_api = MockOutputApi()
4006 errors = PRESUBMIT.CheckSetNoParent(mock_input_api, mock_output_api)
4007 self.assertEqual(1, len(errors))
4008 self.assertTrue('goat/OWNERS:1' in errors[0].long_text)
4009 self.assertTrue('goat/OWNERS:3' in errors[0].long_text)
4011 def testSetNoParentWithCorrectRule(self):
4012 mock_input_api = MockInputApi()
4013 mock_input_api.files = [
4014 MockAffectedFile('services/goat/OWNERS',
4017 'file://ipc/SECURITY_OWNERS',
4018 'per-file *.json=set noparent',
4019 'per-file *.json=file://ipc/SECURITY_OWNERS',
4022 mock_output_api = MockOutputApi()
4023 errors = PRESUBMIT.CheckSetNoParent(mock_input_api, mock_output_api)
4024 self.assertEqual([], errors)
4027 class MojomStabilityCheckTest(unittest.TestCase):
4028 def runTestWithAffectedFiles(self, affected_files):
4029 mock_input_api = MockInputApi()
4030 mock_input_api.files = affected_files
4031 mock_output_api = MockOutputApi()
4032 return PRESUBMIT.CheckStableMojomChanges(
4033 mock_input_api, mock_output_api)
4035 def testSafeChangePasses(self):
4036 errors = self.runTestWithAffectedFiles([
4037 MockAffectedFile('foo/foo.mojom',
4038 ['[Stable] struct S { [MinVersion=1] int32 x; };'],
4039 old_contents=['[Stable] struct S {};'])
4041 self.assertEqual([], errors)
4043 def testBadChangeFails(self):
4044 errors = self.runTestWithAffectedFiles([
4045 MockAffectedFile('foo/foo.mojom',
4046 ['[Stable] struct S { int32 x; };'],
4047 old_contents=['[Stable] struct S {};'])
4049 self.assertEqual(1, len(errors))
4050 self.assertTrue('not backward-compatible' in errors[0].message)
4052 def testDeletedFile(self):
4053 """Regression test for https://crbug.com/1091407."""
4054 errors = self.runTestWithAffectedFiles([
4055 MockAffectedFile('a.mojom', [], old_contents=['struct S {};'],
4057 MockAffectedFile('b.mojom',
4058 ['struct S {}; struct T { S s; };'],
4059 old_contents=['import "a.mojom"; struct T { S s; };'])
4061 self.assertEqual([], errors)
4063 class CheckForUseOfChromeAppsDeprecationsTest(unittest.TestCase):
4065 ERROR_MSG_PIECE = 'technologies which will soon be deprecated'
4067 # Each positive test is also a naive negative test for the other cases.
4069 def testWarningNMF(self):
4070 mock_input_api = MockInputApi()
4071 mock_input_api.files = [
4074 ['"program"', '"Z":"content"', 'B'],
4076 scm_diff='\n'.join([
4077 '--- foo.NMF.old 2020-12-02 20:40:54.430676385 +0100',
4078 '+++ foo.NMF.new 2020-12-02 20:41:02.086700197 +0100',
4085 mock_output_api = MockOutputApi()
4086 errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4088 self.assertEqual(1, len(errors))
4089 self.assertTrue( self.ERROR_MSG_PIECE in errors[0].message)
4090 self.assertTrue( 'foo.NMF' in errors[0].message)
4092 def testWarningManifest(self):
4093 mock_input_api = MockInputApi()
4094 mock_input_api.files = [
4097 ['"app":', '"Z":"content"', 'B'],
4099 scm_diff='\n'.join([
4100 '--- manifest.json.old 2020-12-02 20:40:54.430676385 +0100',
4101 '+++ manifest.json.new 2020-12-02 20:41:02.086700197 +0100',
4108 mock_output_api = MockOutputApi()
4109 errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4111 self.assertEqual(1, len(errors))
4112 self.assertTrue( self.ERROR_MSG_PIECE in errors[0].message)
4113 self.assertTrue( 'manifest.json' in errors[0].message)
4115 def testOKWarningManifestWithoutApp(self):
4116 mock_input_api = MockInputApi()
4117 mock_input_api.files = [
4120 ['"name":', '"Z":"content"', 'B'],
4122 scm_diff='\n'.join([
4123 '--- manifest.json.old 2020-12-02 20:40:54.430676385 +0100',
4124 '+++ manifest.json.new 2020-12-02 20:41:02.086700197 +0100',
4131 mock_output_api = MockOutputApi()
4132 errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4134 self.assertEqual(0, len(errors))
4136 def testWarningPPAPI(self):
4137 mock_input_api = MockInputApi()
4138 mock_input_api.files = [
4141 ['A', '#include <ppapi.h>', 'B'],
4143 scm_diff='\n'.join([
4144 '--- foo.hpp.old 2020-12-02 20:40:54.430676385 +0100',
4145 '+++ foo.hpp.new 2020-12-02 20:41:02.086700197 +0100',
4148 '+#include <ppapi.h>',
4152 mock_output_api = MockOutputApi()
4153 errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4155 self.assertEqual(1, len(errors))
4156 self.assertTrue( self.ERROR_MSG_PIECE in errors[0].message)
4157 self.assertTrue( 'foo.hpp' in errors[0].message)
4159 def testNoWarningPPAPI(self):
4160 mock_input_api = MockInputApi()
4161 mock_input_api.files = [
4164 ['A', 'Peppapig', 'B'],
4166 scm_diff='\n'.join([
4167 '--- foo.txt.old 2020-12-02 20:40:54.430676385 +0100',
4168 '+++ foo.txt.new 2020-12-02 20:41:02.086700197 +0100',
4175 mock_output_api = MockOutputApi()
4176 errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4178 self.assertEqual(0, len(errors))
4180 class CheckDeprecationOfPreferencesTest(unittest.TestCase):
4181 # Test that a warning is generated if a preference registration is removed
4182 # from a random file.
4183 def testWarning(self):
4184 mock_input_api = MockInputApi()
4185 mock_input_api.files = [
4189 ['A', 'prefs->RegisterStringPref("foo", "default");', 'B'],
4190 scm_diff='\n'.join([
4191 '--- foo.cc.old 2020-12-02 20:40:54.430676385 +0100',
4192 '+++ foo.cc.new 2020-12-02 20:41:02.086700197 +0100',
4195 '-prefs->RegisterStringPref("foo", "default");',
4199 mock_output_api = MockOutputApi()
4200 errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
4202 self.assertEqual(1, len(errors))
4204 'Discovered possible removal of preference registrations' in
4207 # Test that a warning is inhibited if the preference registration was moved
4208 # to the deprecation functions in browser prefs.
4209 def testNoWarningForMigration(self):
4210 mock_input_api = MockInputApi()
4211 mock_input_api.files = [
4212 # RegisterStringPref was removed from foo.cc.
4216 ['A', 'prefs->RegisterStringPref("foo", "default");', 'B'],
4217 scm_diff='\n'.join([
4218 '--- foo.cc.old 2020-12-02 20:40:54.430676385 +0100',
4219 '+++ foo.cc.new 2020-12-02 20:41:02.086700197 +0100',
4222 '-prefs->RegisterStringPref("foo", "default");',
4225 # But the preference was properly migrated.
4227 'chrome/browser/prefs/browser_prefs.cc',
4229 '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4230 '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4231 '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4232 'prefs->RegisterStringPref("foo", "default");',
4233 '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4236 '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4237 '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4238 '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4239 '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4241 scm_diff='\n'.join([
4242 '--- browser_prefs.cc.old 2020-12-02 20:51:40.812686731 +0100',
4243 '+++ browser_prefs.cc.new 2020-12-02 20:52:02.936755539 +0100',
4245 ' // END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4246 ' // BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4247 '+prefs->RegisterStringPref("foo", "default");',
4248 ' // END_MIGRATE_OBSOLETE_PROFILE_PREFS']),
4251 mock_output_api = MockOutputApi()
4252 errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
4254 self.assertEqual(0, len(errors))
4256 # Test that a warning is NOT inhibited if the preference registration was
4257 # moved to a place outside of the migration functions in browser_prefs.cc
4258 def testWarningForImproperMigration(self):
4259 mock_input_api = MockInputApi()
4260 mock_input_api.files = [
4261 # RegisterStringPref was removed from foo.cc.
4265 ['A', 'prefs->RegisterStringPref("foo", "default");', 'B'],
4266 scm_diff='\n'.join([
4267 '--- foo.cc.old 2020-12-02 20:40:54.430676385 +0100',
4268 '+++ foo.cc.new 2020-12-02 20:41:02.086700197 +0100',
4271 '-prefs->RegisterStringPref("foo", "default");',
4274 # The registration call was moved to a place in browser_prefs.cc that
4275 # is outside the migration functions.
4277 'chrome/browser/prefs/browser_prefs.cc',
4279 'prefs->RegisterStringPref("foo", "default");',
4280 '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4281 '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4282 '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4283 '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4286 '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4287 '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4288 '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4289 '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4291 scm_diff='\n'.join([
4292 '--- browser_prefs.cc.old 2020-12-02 20:51:40.812686731 +0100',
4293 '+++ browser_prefs.cc.new 2020-12-02 20:52:02.936755539 +0100',
4295 '+prefs->RegisterStringPref("foo", "default");',
4296 ' // BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4297 ' // END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS']),
4300 mock_output_api = MockOutputApi()
4301 errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
4303 self.assertEqual(1, len(errors))
4305 'Discovered possible removal of preference registrations' in
4308 # Check that the presubmit fails if a marker line in browser_prefs.cc is
4310 def testDeletedMarkerRaisesError(self):
4311 mock_input_api = MockInputApi()
4312 mock_input_api.files = [
4313 MockAffectedFile('chrome/browser/prefs/browser_prefs.cc',
4315 '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4316 '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4317 '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4318 # The following line is deleted for this test
4319 # '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4322 mock_output_api = MockOutputApi()
4323 errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
4325 self.assertEqual(1, len(errors))
4327 'Broken .*MIGRATE_OBSOLETE_.*_PREFS markers in browser_prefs.cc.',
4330 class MPArchApiUsage(unittest.TestCase):
4332 self, expected_uses, expect_fyi, msg, local_path, new_contents):
4333 mock_input_api = MockInputApi()
4334 mock_output_api = MockOutputApi()
4335 mock_input_api.files = [
4336 MockFile(local_path, new_contents),
4338 result = PRESUBMIT.CheckMPArchApiUsage(mock_input_api, mock_output_api)
4340 watchlist_email = ('mparch-reviews+watchfyi@chromium.org'
4341 if expect_fyi else 'mparch-reviews+watch@chromium.org')
4343 bool(expected_uses or expect_fyi),
4344 watchlist_email in mock_output_api.more_cc,
4347 self.assertEqual(1, len(result), msg)
4348 self.assertEqual(result[0].type, 'notify', msg)
4349 self.assertEqual(sorted(result[0].items), sorted(expected_uses), msg)
4351 self.assertEqual(0, len(result), msg)
4353 def testNotify(self):
4354 self._assert_notify(
4357 'Introduce IsInMainFrame',
4358 'chrome/my_feature.cc',
4359 ['void DoSomething(content::NavigationHandle* navigation_handle) {',
4360 ' if (navigation_handle->IsInMainFrame())',
4361 ' all_of_our_page_state.reset();',
4364 self._assert_notify(
4365 ['FromRenderFrameHost'],
4367 'Introduce WC::FromRenderFrameHost',
4368 'chrome/my_feature.cc',
4369 ['void DoSomething(content::RenderFrameHost* rfh) {',
4370 ' auto* wc = content::WebContents::FromRenderFrameHost(rfh);',
4371 ' ChangeTabState(wc);',
4376 self._assert_notify(
4379 'Introduce WCO and WCUD',
4380 'chrome/my_feature.h',
4382 ' : public content::WebContentsObserver,',
4383 ' public content::WebContentsUserData<MyFeature> {};',
4385 self._assert_notify(
4388 'Introduce WCO override',
4389 'chrome/my_feature.h',
4390 ['void DidFinishNavigation(',
4391 ' content::NavigationHandle* navigation_handle) override;',
4394 def testNoNotify(self):
4395 self._assert_notify(
4399 'chrome/my_feature.cc',
4400 ['void DoSomething() {',
4401 ' // TODO: Something',
4404 # Something under a top level directory we're not concerned about happens
4405 # to share a name with a content API.
4406 self._assert_notify(
4409 'Uninteresting top level directory',
4410 'third_party/my_dep/my_code.cc',
4411 ['bool HasParent(Node* node) {',
4412 ' return node->GetParent();',
4415 # We're not concerned with usage in test code.
4416 self._assert_notify(
4419 'Usage in test code',
4420 'chrome/my_feature_unittest.cc',
4421 ['TEST_F(MyFeatureTest, DoesSomething) {',
4422 ' EXPECT_TRUE(rfh()->GetMainFrame());',
4427 class AssertAshOnlyCodeTest(unittest.TestCase):
4428 def testErrorsOnlyOnAshDirectories(self):
4430 MockFile('ash/BUILD.gn', []),
4431 MockFile('chrome/browser/ash/BUILD.gn', []),
4434 MockFile('chrome/browser/BUILD.gn', []),
4435 MockFile('chrome/browser/BUILD.gn', ['assert(is_chromeos_ash)']),
4437 input_api = MockInputApi()
4438 input_api.files = files_in_ash
4439 errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4440 self.assertEqual(2, len(errors))
4442 input_api.files = other_files
4443 errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4444 self.assertEqual(0, len(errors))
4446 def testDoesNotErrorOnNonGNFiles(self):
4447 input_api = MockInputApi()
4449 MockFile('ash/test.h', ['assert(is_chromeos_ash)']),
4450 MockFile('chrome/browser/ash/test.cc',
4451 ['assert(is_chromeos_ash)']),
4453 errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4454 self.assertEqual(0, len(errors))
4456 def testDeletedFile(self):
4457 input_api = MockInputApi()
4459 MockFile('ash/BUILD.gn', []),
4460 MockFile('ash/foo/BUILD.gn', [], action='D'),
4462 errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4463 self.assertEqual(1, len(errors))
4465 def testDoesNotErrorWithAssertion(self):
4466 input_api = MockInputApi()
4468 MockFile('ash/BUILD.gn', ['assert(is_chromeos_ash)']),
4469 MockFile('chrome/browser/ash/BUILD.gn',
4470 ['assert(is_chromeos_ash)']),
4471 MockFile('chrome/browser/ash/BUILD.gn',
4472 ['assert(is_chromeos_ash, "test")']),
4474 errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4475 self.assertEqual(0, len(errors))
4478 class CheckRawPtrUsageTest(unittest.TestCase):
4479 def testAllowedCases(self):
4480 mock_input_api = MockInputApi()
4481 mock_input_api.files = [
4482 # Browser-side files are allowed.
4483 MockAffectedFile('test10/browser/foo.h', ['raw_ptr<int>']),
4484 MockAffectedFile('test11/browser/foo.cc', ['raw_ptr<int>']),
4485 MockAffectedFile('test12/blink/common/foo.cc', ['raw_ptr<int>']),
4486 MockAffectedFile('test13/blink/public/common/foo.cc', ['raw_ptr<int>']),
4487 MockAffectedFile('test14/blink/public/platform/foo.cc',
4490 # Non-C++ files are allowed.
4491 MockAffectedFile('test20/renderer/foo.md', ['raw_ptr<int>']),
4493 # Mentions in a comment are allowed.
4494 MockAffectedFile('test30/renderer/foo.cc', ['//raw_ptr<int>']),
4496 mock_output_api = MockOutputApi()
4497 errors = PRESUBMIT.CheckRawPtrUsage(mock_input_api, mock_output_api)
4498 self.assertFalse(errors)
4500 def testDisallowedCases(self):
4501 mock_input_api = MockInputApi()
4502 mock_input_api.files = [
4503 MockAffectedFile('test1/renderer/foo.h', ['raw_ptr<int>']),
4504 MockAffectedFile('test2/renderer/foo.cc', ['raw_ptr<int>']),
4505 MockAffectedFile('test3/blink/public/web/foo.cc', ['raw_ptr<int>']),
4507 mock_output_api = MockOutputApi()
4508 errors = PRESUBMIT.CheckRawPtrUsage(mock_input_api, mock_output_api)
4509 self.assertEqual(len(mock_input_api.files), len(errors))
4510 for error in errors:
4512 'raw_ptr<T> should not be used in Renderer-only code' in
4516 class AssertPythonShebangTest(unittest.TestCase):
4517 def testError(self):
4518 input_api = MockInputApi()
4520 MockFile('ash/test.py', ['#!/usr/bin/python']),
4521 MockFile('chrome/test.py', ['#!/usr/bin/python2']),
4522 MockFile('third_party/blink/test.py', ['#!/usr/bin/python3']),
4523 MockFile('empty.py', []),
4525 errors = PRESUBMIT.CheckPythonShebang(input_api, MockOutputApi())
4526 self.assertEqual(3, len(errors))
4528 def testNonError(self):
4529 input_api = MockInputApi()
4531 MockFile('chrome/browser/BUILD.gn', ['#!/usr/bin/python']),
4532 MockFile('third_party/blink/web_tests/external/test.py',
4533 ['#!/usr/bin/python2']),
4534 MockFile('third_party/test/test.py', ['#!/usr/bin/python3']),
4536 errors = PRESUBMIT.CheckPythonShebang(input_api, MockOutputApi())
4537 self.assertEqual(0, len(errors))
4539 class VerifyDcheckParentheses(unittest.TestCase):
4540 def testPermissibleUsage(self):
4541 input_api = MockInputApi()
4543 MockFile('okay1.cc', ['DCHECK_IS_ON()']),
4544 MockFile('okay2.cc', ['#if DCHECK_IS_ON()']),
4546 # Other constructs that aren't exactly `DCHECK_IS_ON()` do their
4547 # own thing at their own risk.
4548 MockFile('okay3.cc', ['PA_DCHECK_IS_ON']),
4549 MockFile('okay4.cc', ['#if PA_DCHECK_IS_ON']),
4550 MockFile('okay6.cc', ['BUILDFLAG(PA_DCHECK_IS_ON)']),
4552 errors = PRESUBMIT.CheckDCHECK_IS_ONHasBraces(input_api, MockOutputApi())
4553 self.assertEqual(0, len(errors))
4555 def testMissingParentheses(self):
4556 input_api = MockInputApi()
4558 MockFile('bad1.cc', ['DCHECK_IS_ON']),
4559 MockFile('bad2.cc', ['#if DCHECK_IS_ON']),
4560 MockFile('bad3.cc', ['DCHECK_IS_ON && foo']),
4562 errors = PRESUBMIT.CheckDCHECK_IS_ONHasBraces(input_api, MockOutputApi())
4563 self.assertEqual(3, len(errors))
4564 for error in errors:
4565 self.assertRegex(error.message, r'DCHECK_IS_ON().+parentheses')
4568 class CheckBatchAnnotation(unittest.TestCase):
4569 """Test the CheckBatchAnnotation presubmit check."""
4571 def testTruePositives(self):
4572 """Examples of when there is no @Batch or @DoNotBatch is correctly flagged.
4574 mock_input = MockInputApi()
4575 mock_input.files = [
4576 MockFile('path/OneTest.java', ['public class OneTest']),
4577 MockFile('path/TwoTest.java', ['public class TwoTest']),
4578 MockFile('path/ThreeTest.java',
4579 ['@Batch(Batch.PER_CLASS)',
4580 'import org.chromium.base.test.BaseRobolectricTestRunner;',
4581 'public class Three {']),
4582 MockFile('path/FourTest.java',
4583 ['@DoNotBatch(reason = "dummy reason 1")',
4584 'import org.chromium.base.test.BaseRobolectricTestRunner;',
4585 'public class Four {']),
4587 errors = PRESUBMIT.CheckBatchAnnotation(mock_input, MockOutputApi())
4588 self.assertEqual(2, len(errors))
4589 self.assertEqual(2, len(errors[0].items))
4590 self.assertIn('OneTest.java', errors[0].items[0])
4591 self.assertIn('TwoTest.java', errors[0].items[1])
4592 self.assertEqual(2, len(errors[1].items))
4593 self.assertIn('ThreeTest.java', errors[1].items[0])
4594 self.assertIn('FourTest.java', errors[1].items[1])
4597 def testAnnotationsPresent(self):
4598 """Examples of when there is @Batch or @DoNotBatch is correctly flagged."""
4599 mock_input = MockInputApi()
4600 mock_input.files = [
4601 MockFile('path/OneTest.java',
4602 ['@Batch(Batch.PER_CLASS)', 'public class One {']),
4603 MockFile('path/TwoTest.java',
4604 ['@DoNotBatch(reason = "dummy reasons.")', 'public class Two {'
4606 MockFile('path/ThreeTest.java',
4607 ['@Batch(Batch.PER_CLASS)',
4608 'public class Three extends BaseTestA {'],
4609 ['@Batch(Batch.PER_CLASS)',
4610 'public class Three extends BaseTestB {']),
4611 MockFile('path/FourTest.java',
4612 ['@DoNotBatch(reason = "dummy reason 1")',
4613 'public class Four extends BaseTestA {'],
4614 ['@DoNotBatch(reason = "dummy reason 2")',
4615 'public class Four extends BaseTestB {']),
4616 MockFile('path/FiveTest.java',
4617 ['import androidx.test.uiautomator.UiDevice;',
4618 'public class Five extends BaseTestA {'],
4619 ['import androidx.test.uiautomator.UiDevice;',
4620 'public class Five extends BaseTestB {']),
4621 MockFile('path/SixTest.java',
4622 ['import org.chromium.base.test.BaseRobolectricTestRunner;',
4623 'public class Six extends BaseTestA {'],
4624 ['import org.chromium.base.test.BaseRobolectricTestRunner;',
4625 'public class Six extends BaseTestB {']),
4626 MockFile('path/SevenTest.java',
4627 ['import org.robolectric.annotation.Config;',
4628 'public class Seven extends BaseTestA {'],
4629 ['import org.robolectric.annotation.Config;',
4630 'public class Seven extends BaseTestB {']),
4632 'path/OtherClass.java',
4633 ['public class OtherClass {'],
4635 MockFile('path/PRESUBMIT.py',
4636 ['@Batch(Batch.PER_CLASS)',
4637 '@DoNotBatch(reason = "dummy reason)']),
4639 errors = PRESUBMIT.CheckBatchAnnotation(mock_input, MockOutputApi())
4640 self.assertEqual(0, len(errors))
4643 class CheckMockAnnotation(unittest.TestCase):
4644 """Test the CheckMockAnnotation presubmit check."""
4646 def testTruePositives(self):
4647 """Examples of @Mock or @Spy being used and nothing should be flagged."""
4648 mock_input = MockInputApi()
4649 mock_input.files = [
4650 MockFile('path/OneTest.java', [
4651 'import a.b.c.Bar;',
4652 'import a.b.c.Foo;',
4654 'public static Foo f = new Foo();',
4655 'Mockito.mock(new Bar(a, b, c))'
4657 MockFile('path/TwoTest.java', [
4659 'import static org.mockito.Mockito.spy;',
4661 'public static FooBar<Baz> f;',
4662 'a = spy(Baz.class)'
4665 errors = PRESUBMIT.CheckMockAnnotation(mock_input, MockOutputApi())
4666 self.assertEqual(1, len(errors))
4667 self.assertEqual(2, len(errors[0].items))
4668 self.assertIn('a.b.c.Bar in path/OneTest.java', errors[0].items)
4669 self.assertIn('x.y.z.Baz in path/TwoTest.java', errors[0].items)
4671 def testTrueNegatives(self):
4672 """Examples of when we should not be flagging mock() or spy() calls."""
4673 mock_input = MockInputApi()
4674 mock_input.files = [
4675 MockFile('path/OneTest.java', [
4677 'import org.chromium.base.test.BaseRobolectricTestRunner;',
4678 'Mockito.mock(Abc.class)'
4680 MockFile('path/TwoTest.java', [
4682 'import androidx.test.uiautomator.UiDevice;',
4683 'Mockito.spy(new Def())'
4685 MockFile('path/ThreeTest.java', [
4687 'import static org.mockito.Mockito.spy;',
4689 'public static Foo f = new Abc();',
4690 'a = spy(Foo.class)'
4692 MockFile('path/FourTest.java', [
4694 'import static org.mockito.Mockito.mock;',
4696 'public static Bar b = new Abc(a, b, c, d);',
4697 ' mock(new Bar(a,b,c))'
4699 MockFile('path/FiveTest.java', [
4702 'public static Baz<abc> b;',
4703 'Mockito.mock(Baz.class)']),
4704 MockFile('path/SixTest.java', [
4706 'import android.view.View;',
4707 'import java.ArrayList;',
4708 'Mockito.spy(new View())',
4709 'Mockito.mock(ArrayList.class)'
4712 errors = PRESUBMIT.CheckMockAnnotation(mock_input, MockOutputApi())
4713 self.assertEqual(0, len(errors))
4716 class LayoutInTestsTest(unittest.TestCase):
4717 def testLayoutInTest(self):
4718 mock_input = MockInputApi()
4719 mock_input.files = [
4720 MockFile('path/to/foo_unittest.cc',
4721 [' foo->Layout();', ' bar.Layout();']),
4723 errors = PRESUBMIT.CheckNoLayoutCallsInTests(mock_input, MockOutputApi())
4724 self.assertNotEqual(0, len(errors))
4726 def testNoTriggerOnLayoutOverride(self):
4727 mock_input = MockInputApi();
4728 mock_input.files = [
4729 MockFile('path/to/foo_unittest.cc',
4730 ['class TestView: public views::View {',
4732 ' void Layout(); override {',
4733 ' views::View::Layout();',
4734 ' // perform bespoke layout',
4738 errors = PRESUBMIT.CheckNoLayoutCallsInTests(mock_input, MockOutputApi())
4739 self.assertEqual(0, len(errors))
4741 if __name__ == '__main__':