[M120 Migration][VD] Enable direct rendering for TVPlus
[platform/framework/web/chromium-efl.git] / PRESUBMIT_test.py
1 #!/usr/bin/env python3
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.
5
6 import io
7 import os.path
8 import subprocess
9 import textwrap
10 import unittest
11
12 import PRESUBMIT
13
14 from PRESUBMIT_test_mocks import MockFile, MockAffectedFile
15 from PRESUBMIT_test_mocks import MockInputApi, MockOutputApi
16
17
18 _TEST_DATA_DIR = 'base/test/data/presubmit'
19
20
21 class VersionControlConflictsTest(unittest.TestCase):
22   def testTypicalConflict(self):
23     lines = ['<<<<<<< HEAD',
24              '  base::ScopedTempDir temp_dir_;',
25              '=======',
26              '  ScopedTempDir temp_dir_;',
27              '>>>>>>> master']
28     errors = PRESUBMIT._CheckForVersionControlConflictsInFile(
29         MockInputApi(), MockFile('some/path/foo_platform.cc', lines))
30     self.assertEqual(3, len(errors))
31     self.assertTrue('1' in errors[0])
32     self.assertTrue('3' in errors[1])
33     self.assertTrue('5' in errors[2])
34
35   def testIgnoresReadmes(self):
36     lines = ['A First Level Header',
37              '====================',
38              '',
39              'A Second Level Header',
40              '---------------------']
41     errors = PRESUBMIT._CheckForVersionControlConflictsInFile(
42         MockInputApi(), MockFile('some/polymer/README.md', lines))
43     self.assertEqual(0, len(errors))
44
45
46 class BadExtensionsTest(unittest.TestCase):
47   def testBadRejFile(self):
48     mock_input_api = MockInputApi()
49     mock_input_api.files = [
50       MockFile('some/path/foo.cc', ''),
51       MockFile('some/path/foo.cc.rej', ''),
52       MockFile('some/path2/bar.h.rej', ''),
53     ]
54
55     results = PRESUBMIT.CheckPatchFiles(mock_input_api, MockOutputApi())
56     self.assertEqual(1, len(results))
57     self.assertEqual(2, len(results[0].items))
58     self.assertTrue('foo.cc.rej' in results[0].items[0])
59     self.assertTrue('bar.h.rej' in results[0].items[1])
60
61   def testBadOrigFile(self):
62     mock_input_api = MockInputApi()
63     mock_input_api.files = [
64       MockFile('other/path/qux.h.orig', ''),
65       MockFile('other/path/qux.h', ''),
66       MockFile('other/path/qux.cc', ''),
67     ]
68
69     results = PRESUBMIT.CheckPatchFiles(mock_input_api, MockOutputApi())
70     self.assertEqual(1, len(results))
71     self.assertEqual(1, len(results[0].items))
72     self.assertTrue('qux.h.orig' in results[0].items[0])
73
74   def testGoodFiles(self):
75     mock_input_api = MockInputApi()
76     mock_input_api.files = [
77       MockFile('other/path/qux.h', ''),
78       MockFile('other/path/qux.cc', ''),
79     ]
80     results = PRESUBMIT.CheckPatchFiles(mock_input_api, MockOutputApi())
81     self.assertEqual(0, len(results))
82
83
84 class CheckForSuperfluousStlIncludesInHeadersTest(unittest.TestCase):
85   def testGoodFiles(self):
86     mock_input_api = MockInputApi()
87     mock_input_api.files = [
88       # The check is not smart enough to figure out which definitions correspond
89       # to which header.
90       MockFile('other/path/foo.h',
91                ['#include <string>',
92                 'std::vector']),
93       # The check is not smart enough to do IWYU.
94       MockFile('other/path/bar.h',
95                ['#include "base/check.h"',
96                 'std::vector']),
97       MockFile('other/path/qux.h',
98                ['#include "base/stl_util.h"',
99                 'foobar']),
100       MockFile('other/path/baz.h',
101                ['#include "set/vector.h"',
102                 'bazzab']),
103       # The check is only for header files.
104       MockFile('other/path/not_checked.cc',
105                ['#include <vector>',
106                 'bazbaz']),
107     ]
108     results = PRESUBMIT.CheckForSuperfluousStlIncludesInHeaders(
109         mock_input_api, MockOutputApi())
110     self.assertEqual(0, len(results))
111
112   def testBadFiles(self):
113     mock_input_api = MockInputApi()
114     mock_input_api.files = [
115       MockFile('other/path/foo.h',
116                ['#include <vector>',
117                 'vector']),
118       MockFile('other/path/bar.h',
119                ['#include <limits>',
120                 '#include <set>',
121                 'no_std_namespace']),
122     ]
123     results = PRESUBMIT.CheckForSuperfluousStlIncludesInHeaders(
124         mock_input_api, MockOutputApi())
125     self.assertEqual(1, len(results))
126     self.assertTrue('foo.h: Includes STL' in results[0].message)
127     self.assertTrue('bar.h: Includes STL' in results[0].message)
128
129
130 class CheckSingletonInHeadersTest(unittest.TestCase):
131   def testSingletonInArbitraryHeader(self):
132     diff_singleton_h = ['base::subtle::AtomicWord '
133                         'base::Singleton<Type, Traits, DifferentiatingType>::']
134     diff_foo_h = ['// base::Singleton<Foo> in comment.',
135                   'friend class base::Singleton<Foo>']
136     diff_foo2_h = ['  //Foo* bar = base::Singleton<Foo>::get();']
137     diff_bad_h = ['Foo* foo = base::Singleton<Foo>::get();']
138     mock_input_api = MockInputApi()
139     mock_input_api.files = [MockAffectedFile('base/memory/singleton.h',
140                                              diff_singleton_h),
141                             MockAffectedFile('foo.h', diff_foo_h),
142                             MockAffectedFile('foo2.h', diff_foo2_h),
143                             MockAffectedFile('bad.h', diff_bad_h)]
144     warnings = PRESUBMIT.CheckSingletonInHeaders(mock_input_api,
145                                                   MockOutputApi())
146     self.assertEqual(1, len(warnings))
147     self.assertEqual(1, len(warnings[0].items))
148     self.assertEqual('error', warnings[0].type)
149     self.assertTrue('Found base::Singleton<T>' in warnings[0].message)
150
151   def testSingletonInCC(self):
152     diff_cc = ['Foo* foo = base::Singleton<Foo>::get();']
153     mock_input_api = MockInputApi()
154     mock_input_api.files = [MockAffectedFile('some/path/foo.cc', diff_cc)]
155     warnings = PRESUBMIT.CheckSingletonInHeaders(mock_input_api,
156                                                   MockOutputApi())
157     self.assertEqual(0, len(warnings))
158
159
160 class DeprecatedOSMacroNamesTest(unittest.TestCase):
161   def testDeprecatedOSMacroNames(self):
162     lines = ['#if defined(OS_WIN)',
163              ' #elif defined(OS_WINDOW)',
164              ' # if defined(OS_MAC) || defined(OS_CHROME)']
165     errors = PRESUBMIT._CheckForDeprecatedOSMacrosInFile(
166         MockInputApi(), MockFile('some/path/foo_platform.cc', lines))
167     self.assertEqual(len(lines) + 1, len(errors))
168     self.assertTrue(':1: defined(OS_WIN) -> BUILDFLAG(IS_WIN)' in errors[0])
169
170
171 class InvalidIfDefinedMacroNamesTest(unittest.TestCase):
172   def testInvalidIfDefinedMacroNames(self):
173     lines = ['#if defined(TARGET_IPHONE_SIMULATOR)',
174              '#if !defined(TARGET_IPHONE_SIMULATOR)',
175              '#elif defined(TARGET_IPHONE_SIMULATOR)',
176              '#ifdef TARGET_IPHONE_SIMULATOR',
177              ' # ifdef TARGET_IPHONE_SIMULATOR',
178              '# if defined(VALID) || defined(TARGET_IPHONE_SIMULATOR)',
179              '# else  // defined(TARGET_IPHONE_SIMULATOR)',
180              '#endif  // defined(TARGET_IPHONE_SIMULATOR)']
181     errors = PRESUBMIT._CheckForInvalidIfDefinedMacrosInFile(
182         MockInputApi(), MockFile('some/path/source.mm', lines))
183     self.assertEqual(len(lines), len(errors))
184
185   def testValidIfDefinedMacroNames(self):
186     lines = ['#if defined(FOO)',
187              '#ifdef BAR',
188              '#if TARGET_IPHONE_SIMULATOR']
189     errors = PRESUBMIT._CheckForInvalidIfDefinedMacrosInFile(
190         MockInputApi(), MockFile('some/path/source.cc', lines))
191     self.assertEqual(0, len(errors))
192
193
194 class CheckNoUNIT_TESTInSourceFilesTest(unittest.TestCase):
195   def testUnitTestMacros(self):
196     lines = ['#if defined(UNIT_TEST)',
197              '#if defined UNIT_TEST',
198              '#if !defined(UNIT_TEST)',
199              '#elif defined(UNIT_TEST)',
200              '#ifdef UNIT_TEST',
201              ' # ifdef UNIT_TEST',
202              '#ifndef UNIT_TEST',
203              '# if defined(VALID) || defined(UNIT_TEST)',
204              '# if defined(UNIT_TEST) && defined(VALID)',
205              '# else  // defined(UNIT_TEST)',
206              '#endif  // defined(UNIT_TEST)']
207     errors = PRESUBMIT._CheckNoUNIT_TESTInSourceFiles(
208         MockInputApi(), MockFile('some/path/source.cc', lines))
209     self.assertEqual(len(lines), len(errors))
210
211   def testNotUnitTestMacros(self):
212     lines = ['// Comment about "#if defined(UNIT_TEST)"',
213              '/* Comment about #if defined(UNIT_TEST)" */',
214              '#ifndef UNIT_TEST_H',
215              '#define UNIT_TEST_H',
216              '#ifndef TEST_UNIT_TEST',
217              '#define TEST_UNIT_TEST',
218              '#if defined(_UNIT_TEST)',
219              '#if defined(UNIT_TEST_)',
220              '#ifdef _UNIT_TEST',
221              '#ifdef UNIT_TEST_',
222              '#ifndef _UNIT_TEST',
223              '#ifndef UNIT_TEST_']
224     errors = PRESUBMIT._CheckNoUNIT_TESTInSourceFiles(
225         MockInputApi(), MockFile('some/path/source.cc', lines))
226     self.assertEqual(0, len(errors))
227
228 class CheckAddedDepsHaveTestApprovalsTest(unittest.TestCase):
229
230   def calculate(self, old_include_rules, old_specific_include_rules,
231                 new_include_rules, new_specific_include_rules):
232     return PRESUBMIT._CalculateAddedDeps(
233         os.path, 'include_rules = %r\nspecific_include_rules = %r' % (
234             old_include_rules, old_specific_include_rules),
235         'include_rules = %r\nspecific_include_rules = %r' % (
236             new_include_rules, new_specific_include_rules))
237
238   def testCalculateAddedDeps(self):
239     old_include_rules = [
240         '+base',
241         '-chrome',
242         '+content',
243         '-grit',
244         '-grit/",',
245         '+jni/fooblat.h',
246         '!sandbox',
247     ]
248     old_specific_include_rules = {
249         'compositor\.*': {
250             '+cc',
251         },
252     }
253
254     new_include_rules = [
255         '-ash',
256         '+base',
257         '+chrome',
258         '+components',
259         '+content',
260         '+grit',
261         '+grit/generated_resources.h",',
262         '+grit/",',
263         '+jni/fooblat.h',
264         '+policy',
265         '+' + os.path.join('third_party', 'WebKit'),
266     ]
267     new_specific_include_rules = {
268         'compositor\.*': {
269             '+cc',
270         },
271         'widget\.*': {
272             '+gpu',
273         },
274     }
275
276     expected = set([
277         os.path.join('chrome', 'DEPS'),
278         os.path.join('gpu', 'DEPS'),
279         os.path.join('components', 'DEPS'),
280         os.path.join('policy', 'DEPS'),
281         os.path.join('third_party', 'WebKit', 'DEPS'),
282     ])
283     self.assertEqual(
284         expected,
285         self.calculate(old_include_rules, old_specific_include_rules,
286                        new_include_rules, new_specific_include_rules))
287
288   def testCalculateAddedDepsIgnoresPermutations(self):
289     old_include_rules = [
290         '+base',
291         '+chrome',
292     ]
293     new_include_rules = [
294         '+chrome',
295         '+base',
296     ]
297     self.assertEqual(set(),
298                      self.calculate(old_include_rules, {}, new_include_rules,
299                                     {}))
300
301
302 class JSONParsingTest(unittest.TestCase):
303   def testSuccess(self):
304     input_api = MockInputApi()
305     filename = 'valid_json.json'
306     contents = ['// This is a comment.',
307                 '{',
308                 '  "key1": ["value1", "value2"],',
309                 '  "key2": 3  // This is an inline comment.',
310                 '}'
311                 ]
312     input_api.files = [MockFile(filename, contents)]
313     self.assertEqual(None,
314                      PRESUBMIT._GetJSONParseError(input_api, filename))
315
316   def testFailure(self):
317     input_api = MockInputApi()
318     test_data = [
319       ('invalid_json_1.json',
320        ['{ x }'],
321        'Expecting property name'),
322       ('invalid_json_2.json',
323        ['// Hello world!',
324         '{ "hello": "world }'],
325        'Unterminated string starting at:'),
326       ('invalid_json_3.json',
327        ['{ "a": "b", "c": "d", }'],
328        'Expecting property name'),
329       ('invalid_json_4.json',
330        ['{ "a": "b" "c": "d" }'],
331        "Expecting ',' delimiter:"),
332     ]
333
334     input_api.files = [MockFile(filename, contents)
335                        for (filename, contents, _) in test_data]
336
337     for (filename, _, expected_error) in test_data:
338       actual_error = PRESUBMIT._GetJSONParseError(input_api, filename)
339       self.assertTrue(expected_error in str(actual_error),
340                       "'%s' not found in '%s'" % (expected_error, actual_error))
341
342   def testNoEatComments(self):
343     input_api = MockInputApi()
344     file_with_comments = 'file_with_comments.json'
345     contents_with_comments = ['// This is a comment.',
346                               '{',
347                               '  "key1": ["value1", "value2"],',
348                               '  "key2": 3  // This is an inline comment.',
349                               '}'
350                               ]
351     file_without_comments = 'file_without_comments.json'
352     contents_without_comments = ['{',
353                                  '  "key1": ["value1", "value2"],',
354                                  '  "key2": 3',
355                                  '}'
356                                  ]
357     input_api.files = [MockFile(file_with_comments, contents_with_comments),
358                        MockFile(file_without_comments,
359                                 contents_without_comments)]
360
361     self.assertNotEqual(None,
362                         str(PRESUBMIT._GetJSONParseError(input_api,
363                                                          file_with_comments,
364                                                          eat_comments=False)))
365     self.assertEqual(None,
366                      PRESUBMIT._GetJSONParseError(input_api,
367                                                   file_without_comments,
368                                                   eat_comments=False))
369
370
371 class IDLParsingTest(unittest.TestCase):
372   def testSuccess(self):
373     input_api = MockInputApi()
374     filename = 'valid_idl_basics.idl'
375     contents = ['// Tests a valid IDL file.',
376                 'namespace idl_basics {',
377                 '  enum EnumType {',
378                 '    name1,',
379                 '    name2',
380                 '  };',
381                 '',
382                 '  dictionary MyType1 {',
383                 '    DOMString a;',
384                 '  };',
385                 '',
386                 '  callback Callback1 = void();',
387                 '  callback Callback2 = void(long x);',
388                 '  callback Callback3 = void(MyType1 arg);',
389                 '  callback Callback4 = void(EnumType type);',
390                 '',
391                 '  interface Functions {',
392                 '    static void function1();',
393                 '    static void function2(long x);',
394                 '    static void function3(MyType1 arg);',
395                 '    static void function4(Callback1 cb);',
396                 '    static void function5(Callback2 cb);',
397                 '    static void function6(Callback3 cb);',
398                 '    static void function7(Callback4 cb);',
399                 '  };',
400                 '',
401                 '  interface Events {',
402                 '    static void onFoo1();',
403                 '    static void onFoo2(long x);',
404                 '    static void onFoo2(MyType1 arg);',
405                 '    static void onFoo3(EnumType type);',
406                 '  };',
407                 '};'
408                 ]
409     input_api.files = [MockFile(filename, contents)]
410     self.assertEqual(None,
411                      PRESUBMIT._GetIDLParseError(input_api, filename))
412
413   def testFailure(self):
414     input_api = MockInputApi()
415     test_data = [
416       ('invalid_idl_1.idl',
417        ['//',
418         'namespace test {',
419         '  dictionary {',
420         '    DOMString s;',
421         '  };',
422         '};'],
423        'Unexpected "{" after keyword "dictionary".\n'),
424       # TODO(yoz): Disabled because it causes the IDL parser to hang.
425       # See crbug.com/363830.
426       # ('invalid_idl_2.idl',
427       #  (['namespace test {',
428       #    '  dictionary MissingSemicolon {',
429       #    '    DOMString a',
430       #    '    DOMString b;',
431       #    '  };',
432       #    '};'],
433       #   'Unexpected symbol DOMString after symbol a.'),
434       ('invalid_idl_3.idl',
435        ['//',
436         'namespace test {',
437         '  enum MissingComma {',
438         '    name1',
439         '    name2',
440         '  };',
441         '};'],
442        'Unexpected symbol name2 after symbol name1.'),
443       ('invalid_idl_4.idl',
444        ['//',
445         'namespace test {',
446         '  enum TrailingComma {',
447         '    name1,',
448         '    name2,',
449         '  };',
450         '};'],
451        'Trailing comma in block.'),
452       ('invalid_idl_5.idl',
453        ['//',
454         'namespace test {',
455         '  callback Callback1 = void(;',
456         '};'],
457        'Unexpected ";" after "(".'),
458       ('invalid_idl_6.idl',
459        ['//',
460         'namespace test {',
461         '  callback Callback1 = void(long );',
462         '};'],
463        'Unexpected ")" after symbol long.'),
464       ('invalid_idl_7.idl',
465        ['//',
466         'namespace test {',
467         '  interace Events {',
468         '    static void onFoo1();',
469         '  };',
470         '};'],
471        'Unexpected symbol Events after symbol interace.'),
472       ('invalid_idl_8.idl',
473        ['//',
474         'namespace test {',
475         '  interface NotEvent {',
476         '    static void onFoo1();',
477         '  };',
478         '};'],
479        'Did not process Interface Interface(NotEvent)'),
480       ('invalid_idl_9.idl',
481        ['//',
482         'namespace test {',
483         '  interface {',
484         '    static void function1();',
485         '  };',
486         '};'],
487        'Interface missing name.'),
488     ]
489
490     input_api.files = [MockFile(filename, contents)
491                        for (filename, contents, _) in test_data]
492
493     for (filename, _, expected_error) in test_data:
494       actual_error = PRESUBMIT._GetIDLParseError(input_api, filename)
495       self.assertTrue(expected_error in str(actual_error),
496                       "'%s' not found in '%s'" % (expected_error, actual_error))
497
498
499 class UserMetricsActionTest(unittest.TestCase):
500   def testUserMetricsActionInActions(self):
501     input_api = MockInputApi()
502     file_with_user_action = 'file_with_user_action.cc'
503     contents_with_user_action = [
504       'base::UserMetricsAction("AboutChrome")'
505     ]
506
507     input_api.files = [MockFile(file_with_user_action,
508                                 contents_with_user_action)]
509
510     self.assertEqual(
511       [], PRESUBMIT.CheckUserActionUpdate(input_api, MockOutputApi()))
512
513   def testUserMetricsActionNotAddedToActions(self):
514     input_api = MockInputApi()
515     file_with_user_action = 'file_with_user_action.cc'
516     contents_with_user_action = [
517       'base::UserMetricsAction("NotInActionsXml")'
518     ]
519
520     input_api.files = [MockFile(file_with_user_action,
521                                 contents_with_user_action)]
522
523     output = PRESUBMIT.CheckUserActionUpdate(input_api, MockOutputApi())
524     self.assertEqual(
525       ('File %s line %d: %s is missing in '
526        'tools/metrics/actions/actions.xml. Please run '
527        'tools/metrics/actions/extract_actions.py to update.'
528        % (file_with_user_action, 1, 'NotInActionsXml')),
529       output[0].message)
530
531   def testUserMetricsActionInTestFile(self):
532     input_api = MockInputApi()
533     file_with_user_action = 'file_with_user_action_unittest.cc'
534     contents_with_user_action = [
535       'base::UserMetricsAction("NotInActionsXml")'
536     ]
537
538     input_api.files = [MockFile(file_with_user_action,
539                                 contents_with_user_action)]
540
541     self.assertEqual(
542       [], PRESUBMIT.CheckUserActionUpdate(input_api, MockOutputApi()))
543
544
545 class PydepsNeedsUpdatingTest(unittest.TestCase):
546   class MockPopen:
547     def __init__(self, stdout):
548       self.stdout = io.StringIO(stdout)
549
550     def wait(self):
551       return 0
552
553   class MockSubprocess:
554     CalledProcessError = subprocess.CalledProcessError
555     PIPE = 0
556
557     def __init__(self):
558       self._popen_func = None
559
560     def SetPopenCallback(self, func):
561       self._popen_func = func
562
563     def Popen(self, cmd, *args, **kwargs):
564       return PydepsNeedsUpdatingTest.MockPopen(self._popen_func(cmd))
565
566   def _MockParseGclientArgs(self, is_android=True):
567     return lambda: {'checkout_android': 'true' if is_android else 'false' }
568
569   def setUp(self):
570     mock_all_pydeps = ['A.pydeps', 'B.pydeps', 'D.pydeps']
571     self.old_ALL_PYDEPS_FILES = PRESUBMIT._ALL_PYDEPS_FILES
572     PRESUBMIT._ALL_PYDEPS_FILES = mock_all_pydeps
573     mock_android_pydeps = ['D.pydeps']
574     self.old_ANDROID_SPECIFIC_PYDEPS_FILES = (
575         PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES)
576     PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES = mock_android_pydeps
577     self.old_ParseGclientArgs = PRESUBMIT._ParseGclientArgs
578     PRESUBMIT._ParseGclientArgs = self._MockParseGclientArgs()
579     self.mock_input_api = MockInputApi()
580     self.mock_output_api = MockOutputApi()
581     self.mock_input_api.subprocess = PydepsNeedsUpdatingTest.MockSubprocess()
582     self.checker = PRESUBMIT.PydepsChecker(self.mock_input_api, mock_all_pydeps)
583     self.checker._file_cache = {
584         'A.pydeps': '# Generated by:\n# CMD --output A.pydeps A\nA.py\nC.py\n',
585         'B.pydeps': '# Generated by:\n# CMD --output B.pydeps B\nB.py\nC.py\n',
586         'D.pydeps': '# Generated by:\n# CMD --output D.pydeps D\nD.py\n',
587     }
588
589   def tearDown(self):
590     PRESUBMIT._ALL_PYDEPS_FILES = self.old_ALL_PYDEPS_FILES
591     PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES = (
592         self.old_ANDROID_SPECIFIC_PYDEPS_FILES)
593     PRESUBMIT._ParseGclientArgs = self.old_ParseGclientArgs
594
595   def _RunCheck(self):
596     return PRESUBMIT.CheckPydepsNeedsUpdating(self.mock_input_api,
597                                                self.mock_output_api,
598                                                checker_for_tests=self.checker)
599
600   def testAddedPydep(self):
601     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
602     if not self.mock_input_api.platform.startswith('linux'):
603       return []
604
605     self.mock_input_api.files = [
606       MockAffectedFile('new.pydeps', [], action='A'),
607     ]
608
609     self.mock_input_api.CreateMockFileInPath(
610         [x.LocalPath() for x in self.mock_input_api.AffectedFiles(
611             include_deletes=True)])
612     results = self._RunCheck()
613     self.assertEqual(1, len(results))
614     self.assertIn('PYDEPS_FILES', str(results[0]))
615
616   def testPydepNotInSrc(self):
617     self.mock_input_api.files = [
618       MockAffectedFile('new.pydeps', [], action='A'),
619     ]
620     self.mock_input_api.CreateMockFileInPath([])
621     results = self._RunCheck()
622     self.assertEqual(0, len(results))
623
624   def testRemovedPydep(self):
625     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
626     if not self.mock_input_api.platform.startswith('linux'):
627       return []
628
629     self.mock_input_api.files = [
630       MockAffectedFile(PRESUBMIT._ALL_PYDEPS_FILES[0], [], action='D'),
631     ]
632     self.mock_input_api.CreateMockFileInPath(
633         [x.LocalPath() for x in self.mock_input_api.AffectedFiles(
634             include_deletes=True)])
635     results = self._RunCheck()
636     self.assertEqual(1, len(results))
637     self.assertIn('PYDEPS_FILES', str(results[0]))
638
639   def testRandomPyIgnored(self):
640     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
641     if not self.mock_input_api.platform.startswith('linux'):
642       return []
643
644     self.mock_input_api.files = [
645       MockAffectedFile('random.py', []),
646     ]
647
648     results = self._RunCheck()
649     self.assertEqual(0, len(results), 'Unexpected results: %r' % results)
650
651   def testRelevantPyNoChange(self):
652     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
653     if not self.mock_input_api.platform.startswith('linux'):
654       return []
655
656     self.mock_input_api.files = [
657       MockAffectedFile('A.py', []),
658     ]
659
660     def popen_callback(cmd):
661       self.assertEqual('CMD --output A.pydeps A --output ""', cmd)
662       return self.checker._file_cache['A.pydeps']
663
664     self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
665
666     results = self._RunCheck()
667     self.assertEqual(0, len(results), 'Unexpected results: %r' % results)
668
669   def testRelevantPyOneChange(self):
670     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
671     if not self.mock_input_api.platform.startswith('linux'):
672       return []
673
674     self.mock_input_api.files = [
675       MockAffectedFile('A.py', []),
676     ]
677
678     def popen_callback(cmd):
679       self.assertEqual('CMD --output A.pydeps A --output ""', cmd)
680       return 'changed data'
681
682     self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
683
684     results = self._RunCheck()
685     self.assertEqual(1, len(results))
686     # Check that --output "" is not included.
687     self.assertNotIn('""', str(results[0]))
688     self.assertIn('File is stale', str(results[0]))
689
690   def testRelevantPyTwoChanges(self):
691     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
692     if not self.mock_input_api.platform.startswith('linux'):
693       return []
694
695     self.mock_input_api.files = [
696       MockAffectedFile('C.py', []),
697     ]
698
699     def popen_callback(cmd):
700       return 'changed data'
701
702     self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
703
704     results = self._RunCheck()
705     self.assertEqual(2, len(results))
706     self.assertIn('File is stale', str(results[0]))
707     self.assertIn('File is stale', str(results[1]))
708
709   def testRelevantAndroidPyInNonAndroidCheckout(self):
710     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
711     if not self.mock_input_api.platform.startswith('linux'):
712       return []
713
714     self.mock_input_api.files = [
715       MockAffectedFile('D.py', []),
716     ]
717
718     def popen_callback(cmd):
719       self.assertEqual('CMD --output D.pydeps D --output ""', cmd)
720       return 'changed data'
721
722     self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
723     PRESUBMIT._ParseGclientArgs = self._MockParseGclientArgs(is_android=False)
724
725     results = self._RunCheck()
726     self.assertEqual(1, len(results))
727     self.assertIn('Android', str(results[0]))
728     self.assertIn('D.pydeps', str(results[0]))
729
730   def testGnPathsAndMissingOutputFlag(self):
731     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
732     if not self.mock_input_api.platform.startswith('linux'):
733       return []
734
735     self.checker._file_cache = {
736         'A.pydeps': '# Generated by:\n# CMD --gn-paths A\n//A.py\n//C.py\n',
737         'B.pydeps': '# Generated by:\n# CMD --gn-paths B\n//B.py\n//C.py\n',
738         'D.pydeps': '# Generated by:\n# CMD --gn-paths D\n//D.py\n',
739     }
740
741     self.mock_input_api.files = [
742       MockAffectedFile('A.py', []),
743     ]
744
745     def popen_callback(cmd):
746       self.assertEqual('CMD --gn-paths A --output A.pydeps --output ""', cmd)
747       return 'changed data'
748
749     self.mock_input_api.subprocess.SetPopenCallback(popen_callback)
750
751     results = self._RunCheck()
752     self.assertEqual(1, len(results))
753     self.assertIn('File is stale', str(results[0]))
754
755
756 class IncludeGuardTest(unittest.TestCase):
757   def testIncludeGuardChecks(self):
758     mock_input_api = MockInputApi()
759     mock_output_api = MockOutputApi()
760     mock_input_api.files = [
761         MockAffectedFile('content/browser/thing/foo.h', [
762           '// Comment',
763           '#ifndef CONTENT_BROWSER_THING_FOO_H_',
764           '#define CONTENT_BROWSER_THING_FOO_H_',
765           'struct McBoatFace;',
766           '#endif  // CONTENT_BROWSER_THING_FOO_H_',
767         ]),
768         MockAffectedFile('content/browser/thing/bar.h', [
769           '#ifndef CONTENT_BROWSER_THING_BAR_H_',
770           '#define CONTENT_BROWSER_THING_BAR_H_',
771           'namespace content {',
772           '#endif  // CONTENT_BROWSER_THING_BAR_H_',
773           '}  // namespace content',
774         ]),
775         MockAffectedFile('content/browser/test1.h', [
776           'namespace content {',
777           '}  // namespace content',
778         ]),
779         MockAffectedFile('content\\browser\\win.h', [
780           '#ifndef CONTENT_BROWSER_WIN_H_',
781           '#define CONTENT_BROWSER_WIN_H_',
782           'struct McBoatFace;',
783           '#endif  // CONTENT_BROWSER_WIN_H_',
784         ]),
785         MockAffectedFile('content/browser/test2.h', [
786           '// Comment',
787           '#ifndef CONTENT_BROWSER_TEST2_H_',
788           'struct McBoatFace;',
789           '#endif  // CONTENT_BROWSER_TEST2_H_',
790         ]),
791         MockAffectedFile('content/browser/internal.h', [
792           '// Comment',
793           '#ifndef CONTENT_BROWSER_INTERNAL_H_',
794           '#define CONTENT_BROWSER_INTERNAL_H_',
795           '// Comment',
796           '#ifndef INTERNAL_CONTENT_BROWSER_INTERNAL_H_',
797           '#define INTERNAL_CONTENT_BROWSER_INTERNAL_H_',
798           'namespace internal {',
799           '}  // namespace internal',
800           '#endif  // INTERNAL_CONTENT_BROWSER_THING_BAR_H_',
801           'namespace content {',
802           '}  // namespace content',
803           '#endif  // CONTENT_BROWSER_THING_BAR_H_',
804         ]),
805         MockAffectedFile('content/browser/thing/foo.cc', [
806           '// This is a non-header.',
807         ]),
808         MockAffectedFile('content/browser/disabled.h', [
809           '// no-include-guard-because-multiply-included',
810           'struct McBoatFace;',
811         ]),
812         # New files don't allow misspelled include guards.
813         MockAffectedFile('content/browser/spleling.h', [
814           '#ifndef CONTENT_BROWSER_SPLLEING_H_',
815           '#define CONTENT_BROWSER_SPLLEING_H_',
816           'struct McBoatFace;',
817           '#endif  // CONTENT_BROWSER_SPLLEING_H_',
818         ]),
819         # New files don't allow + in include guards.
820         MockAffectedFile('content/browser/foo+bar.h', [
821           '#ifndef CONTENT_BROWSER_FOO+BAR_H_',
822           '#define CONTENT_BROWSER_FOO+BAR_H_',
823           'struct McBoatFace;',
824           '#endif  // CONTENT_BROWSER_FOO+BAR_H_',
825         ]),
826         # Old files allow misspelled include guards (for now).
827         MockAffectedFile('chrome/old.h', [
828           '// New contents',
829           '#ifndef CHROME_ODL_H_',
830           '#define CHROME_ODL_H_',
831           '#endif  // CHROME_ODL_H_',
832         ], [
833           '// Old contents',
834           '#ifndef CHROME_ODL_H_',
835           '#define CHROME_ODL_H_',
836           '#endif  // CHROME_ODL_H_',
837         ], action='M'),
838         # Using a Blink style include guard outside Blink is wrong.
839         MockAffectedFile('content/NotInBlink.h', [
840           '#ifndef NotInBlink_h',
841           '#define NotInBlink_h',
842           'struct McBoatFace;',
843           '#endif  // NotInBlink_h',
844         ]),
845         # Using a Blink style include guard in Blink is no longer ok.
846         MockAffectedFile('third_party/blink/InBlink.h', [
847           '#ifndef InBlink_h',
848           '#define InBlink_h',
849           'struct McBoatFace;',
850           '#endif  // InBlink_h',
851         ]),
852         # Using a bad include guard in Blink is not ok.
853         MockAffectedFile('third_party/blink/AlsoInBlink.h', [
854           '#ifndef WrongInBlink_h',
855           '#define WrongInBlink_h',
856           'struct McBoatFace;',
857           '#endif  // WrongInBlink_h',
858         ]),
859         # Using a bad include guard in Blink is not supposed to be accepted even
860         # if it's an old file. However the current presubmit has accepted this
861         # for a while.
862         MockAffectedFile('third_party/blink/StillInBlink.h', [
863           '// New contents',
864           '#ifndef AcceptedInBlink_h',
865           '#define AcceptedInBlink_h',
866           'struct McBoatFace;',
867           '#endif  // AcceptedInBlink_h',
868         ], [
869           '// Old contents',
870           '#ifndef AcceptedInBlink_h',
871           '#define AcceptedInBlink_h',
872           'struct McBoatFace;',
873           '#endif  // AcceptedInBlink_h',
874         ], action='M'),
875         # Using a non-Chromium include guard in third_party
876         # (outside blink) is accepted.
877         MockAffectedFile('third_party/foo/some_file.h', [
878           '#ifndef REQUIRED_RPCNDR_H_',
879           '#define REQUIRED_RPCNDR_H_',
880           'struct SomeFileFoo;',
881           '#endif  // REQUIRED_RPCNDR_H_',
882         ]),
883         # Not having proper include guard in *_message_generator.h
884         # for old IPC messages is allowed.
885         MockAffectedFile('content/common/content_message_generator.h', [
886           '#undef CONTENT_COMMON_FOO_MESSAGES_H_',
887           '#include "content/common/foo_messages.h"',
888           '#ifndef CONTENT_COMMON_FOO_MESSAGES_H_',
889           '#error "Failed to include content/common/foo_messages.h"',
890           '#endif',
891         ]),
892       ]
893     msgs = PRESUBMIT.CheckForIncludeGuards(
894         mock_input_api, mock_output_api)
895     expected_fail_count = 8
896     self.assertEqual(expected_fail_count, len(msgs),
897                      'Expected %d items, found %d: %s'
898                      % (expected_fail_count, len(msgs), msgs))
899     self.assertEqual(msgs[0].items, ['content/browser/thing/bar.h'])
900     self.assertEqual(msgs[0].message,
901                      'Include guard CONTENT_BROWSER_THING_BAR_H_ '
902                      'not covering the whole file')
903
904     self.assertIn('content/browser/test1.h', msgs[1].message)
905     self.assertIn('Recommended name: CONTENT_BROWSER_TEST1_H_',
906                      msgs[1].message)
907
908     self.assertEqual(msgs[2].items, ['content/browser/test2.h:3'])
909     self.assertEqual(msgs[2].message,
910                      'Missing "#define CONTENT_BROWSER_TEST2_H_" for '
911                      'include guard')
912
913     self.assertEqual(msgs[3].items, ['content/browser/spleling.h:1'])
914     self.assertEqual(msgs[3].message,
915                      'Header using the wrong include guard name '
916                      'CONTENT_BROWSER_SPLLEING_H_')
917
918     self.assertIn('content/browser/foo+bar.h', msgs[4].message)
919     self.assertIn('Recommended name: CONTENT_BROWSER_FOO_BAR_H_',
920                   msgs[4].message)
921
922     self.assertEqual(msgs[5].items, ['content/NotInBlink.h:1'])
923     self.assertEqual(msgs[5].message,
924                      'Header using the wrong include guard name '
925                      'NotInBlink_h')
926
927     self.assertEqual(msgs[6].items, ['third_party/blink/InBlink.h:1'])
928     self.assertEqual(msgs[6].message,
929                      'Header using the wrong include guard name '
930                      'InBlink_h')
931
932     self.assertEqual(msgs[7].items, ['third_party/blink/AlsoInBlink.h:1'])
933     self.assertEqual(msgs[7].message,
934                      'Header using the wrong include guard name '
935                      'WrongInBlink_h')
936
937 class AccessibilityRelnotesFieldTest(unittest.TestCase):
938   def testRelnotesPresent(self):
939     mock_input_api = MockInputApi()
940     mock_output_api = MockOutputApi()
941
942     mock_input_api.files = [MockAffectedFile('ui/accessibility/foo.bar', [''])]
943     mock_input_api.change.DescriptionText = lambda : 'Commit description'
944     mock_input_api.change.footers['AX-Relnotes'] = [
945         'Important user facing change']
946
947     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
948         mock_input_api, mock_output_api)
949     self.assertEqual(0, len(msgs),
950                      'Expected %d messages, found %d: %s'
951                      % (0, len(msgs), msgs))
952
953   def testRelnotesMissingFromAccessibilityChange(self):
954     mock_input_api = MockInputApi()
955     mock_output_api = MockOutputApi()
956
957     mock_input_api.files = [
958         MockAffectedFile('some/file', ['']),
959         MockAffectedFile('ui/accessibility/foo.bar', ['']),
960         MockAffectedFile('some/other/file', [''])
961     ]
962     mock_input_api.change.DescriptionText = lambda : 'Commit description'
963
964     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
965         mock_input_api, mock_output_api)
966     self.assertEqual(1, len(msgs),
967                      'Expected %d messages, found %d: %s'
968                      % (1, len(msgs), msgs))
969     self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message,
970                     'Missing AX-Relnotes field message not found in errors')
971
972   # The relnotes footer is not required for changes which do not touch any
973   # accessibility directories.
974   def testIgnoresNonAccessibilityCode(self):
975     mock_input_api = MockInputApi()
976     mock_output_api = MockOutputApi()
977
978     mock_input_api.files = [
979         MockAffectedFile('some/file', ['']),
980         MockAffectedFile('some/other/file', [''])
981     ]
982     mock_input_api.change.DescriptionText = lambda : 'Commit description'
983
984     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
985         mock_input_api, mock_output_api)
986     self.assertEqual(0, len(msgs),
987                      'Expected %d messages, found %d: %s'
988                      % (0, len(msgs), msgs))
989
990   # Test that our presubmit correctly raises an error for a set of known paths.
991   def testExpectedPaths(self):
992     filesToTest = [
993       "chrome/browser/accessibility/foo.py",
994       "chrome/browser/ash/arc/accessibility/foo.cc",
995       "chrome/browser/ui/views/accessibility/foo.h",
996       "chrome/browser/extensions/api/automation/foo.h",
997       "chrome/browser/extensions/api/automation_internal/foo.cc",
998       "chrome/renderer/extensions/accessibility_foo.h",
999       "chrome/tests/data/accessibility/foo.html",
1000       "content/browser/accessibility/foo.cc",
1001       "content/renderer/accessibility/foo.h",
1002       "content/tests/data/accessibility/foo.cc",
1003       "extensions/renderer/api/automation/foo.h",
1004       "ui/accessibility/foo/bar/baz.cc",
1005       "ui/views/accessibility/foo/bar/baz.h",
1006     ]
1007
1008     for testFile in filesToTest:
1009       mock_input_api = MockInputApi()
1010       mock_output_api = MockOutputApi()
1011
1012       mock_input_api.files = [
1013           MockAffectedFile(testFile, [''])
1014       ]
1015       mock_input_api.change.DescriptionText = lambda : 'Commit description'
1016
1017       msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
1018           mock_input_api, mock_output_api)
1019       self.assertEqual(1, len(msgs),
1020                        'Expected %d messages, found %d: %s, for file %s'
1021                        % (1, len(msgs), msgs, testFile))
1022       self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message,
1023                       ('Missing AX-Relnotes field message not found in errors '
1024                        ' for file %s' % (testFile)))
1025
1026   # Test that AX-Relnotes field can appear in the commit description (as long
1027   # as it appears at the beginning of a line).
1028   def testRelnotesInCommitDescription(self):
1029     mock_input_api = MockInputApi()
1030     mock_output_api = MockOutputApi()
1031
1032     mock_input_api.files = [
1033         MockAffectedFile('ui/accessibility/foo.bar', ['']),
1034     ]
1035     mock_input_api.change.DescriptionText = lambda : ('Description:\n' +
1036         'AX-Relnotes: solves all accessibility issues forever')
1037
1038     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
1039         mock_input_api, mock_output_api)
1040     self.assertEqual(0, len(msgs),
1041                      'Expected %d messages, found %d: %s'
1042                      % (0, len(msgs), msgs))
1043
1044   # Test that we don't match AX-Relnotes if it appears in the middle of a line.
1045   def testRelnotesMustAppearAtBeginningOfLine(self):
1046     mock_input_api = MockInputApi()
1047     mock_output_api = MockOutputApi()
1048
1049     mock_input_api.files = [
1050         MockAffectedFile('ui/accessibility/foo.bar', ['']),
1051     ]
1052     mock_input_api.change.DescriptionText = lambda : ('Description:\n' +
1053         'This change has no AX-Relnotes: we should print a warning')
1054
1055     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
1056         mock_input_api, mock_output_api)
1057     self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message,
1058                     'Missing AX-Relnotes field message not found in errors')
1059
1060   # Tests that the AX-Relnotes field can be lowercase and use a '=' in place
1061   # of a ':'.
1062   def testRelnotesLowercaseWithEqualSign(self):
1063     mock_input_api = MockInputApi()
1064     mock_output_api = MockOutputApi()
1065
1066     mock_input_api.files = [
1067         MockAffectedFile('ui/accessibility/foo.bar', ['']),
1068     ]
1069     mock_input_api.change.DescriptionText = lambda : ('Description:\n' +
1070         'ax-relnotes= this is a valid format for accessibility relnotes')
1071
1072     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
1073         mock_input_api, mock_output_api)
1074     self.assertEqual(0, len(msgs),
1075                      'Expected %d messages, found %d: %s'
1076                      % (0, len(msgs), msgs))
1077
1078 class AccessibilityEventsTestsAreIncludedForAndroidTest(unittest.TestCase):
1079   # Test that no warning is raised when the Android file is also modified.
1080   def testAndroidChangeIncluded(self):
1081     mock_input_api = MockInputApi()
1082
1083     mock_input_api.files = [
1084         MockAffectedFile(
1085           'content/test/data/accessibility/event/foo-expected-mac.txt',
1086           [''], action='A'),
1087         MockAffectedFile(
1088           'accessibility/WebContentsAccessibilityEventsTest.java',
1089           [''], action='M')
1090     ]
1091
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))
1097
1098   # Test that Android change is not required when no html file is added/removed.
1099   def testIgnoreNonHtmlFiles(self):
1100     mock_input_api = MockInputApi()
1101
1102     mock_input_api.files = [
1103         MockAffectedFile('content/test/data/accessibility/event/foo.txt',
1104           [''], action='A'),
1105         MockAffectedFile('content/test/data/accessibility/event/foo.cc',
1106           [''], action='A'),
1107         MockAffectedFile('content/test/data/accessibility/event/foo.h',
1108           [''], action='A'),
1109         MockAffectedFile('content/test/data/accessibility/event/foo.py',
1110           [''], action='A')
1111     ]
1112
1113     msgs = PRESUBMIT.CheckAccessibilityEventsTestsAreIncludedForAndroid(
1114         mock_input_api, MockOutputApi())
1115     self.assertEqual(0, len(msgs),
1116                      'Expected %d messages, found %d: %s'
1117                      % (0, len(msgs), msgs))
1118
1119   # Test that Android change is not required for unrelated html files.
1120   def testIgnoreNonRelatedHtmlFiles(self):
1121     mock_input_api = MockInputApi()
1122
1123     mock_input_api.files = [
1124         MockAffectedFile('content/test/data/accessibility/aria/foo.html',
1125           [''], action='A'),
1126         MockAffectedFile('content/test/data/accessibility/html/foo.html',
1127           [''], action='A'),
1128         MockAffectedFile('chrome/tests/data/accessibility/foo.html',
1129           [''], action='A')
1130     ]
1131
1132     msgs = PRESUBMIT.CheckAccessibilityEventsTestsAreIncludedForAndroid(
1133         mock_input_api, MockOutputApi())
1134     self.assertEqual(0, len(msgs),
1135                      'Expected %d messages, found %d: %s'
1136                      % (0, len(msgs), msgs))
1137
1138   # Test that only modifying an html file will not trigger the warning.
1139   def testIgnoreModifiedFiles(self):
1140     mock_input_api = MockInputApi()
1141
1142     mock_input_api.files = [
1143         MockAffectedFile(
1144           'content/test/data/accessibility/event/foo-expected-win.txt',
1145           [''], action='M')
1146     ]
1147
1148     msgs = PRESUBMIT.CheckAccessibilityEventsTestsAreIncludedForAndroid(
1149         mock_input_api, MockOutputApi())
1150     self.assertEqual(0, len(msgs),
1151                      'Expected %d messages, found %d: %s'
1152                      % (0, len(msgs), msgs))
1153
1154 class AccessibilityTreeTestsAreIncludedForAndroidTest(unittest.TestCase):
1155   # Test that no warning is raised when the Android file is also modified.
1156   def testAndroidChangeIncluded(self):
1157     mock_input_api = MockInputApi()
1158
1159     mock_input_api.files = [
1160         MockAffectedFile('content/test/data/accessibility/aria/foo.html',
1161           [''], action='A'),
1162         MockAffectedFile(
1163           'accessibility/WebContentsAccessibilityTreeTest.java',
1164           [''], action='M')
1165     ]
1166
1167     msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1168         mock_input_api, MockOutputApi())
1169     self.assertEqual(0, len(msgs),
1170                      'Expected %d messages, found %d: %s'
1171                      % (0, len(msgs), msgs))
1172
1173   # Test that no warning is raised when the Android file is also modified.
1174   def testAndroidChangeIncludedManyFiles(self):
1175     mock_input_api = MockInputApi()
1176
1177     mock_input_api.files = [
1178         MockAffectedFile('content/test/data/accessibility/accname/foo.html',
1179           [''], action='A'),
1180         MockAffectedFile('content/test/data/accessibility/aria/foo.html',
1181           [''], action='A'),
1182         MockAffectedFile('content/test/data/accessibility/css/foo.html',
1183           [''], action='A'),
1184         MockAffectedFile('content/test/data/accessibility/html/foo.html',
1185           [''], action='A'),
1186         MockAffectedFile(
1187           'accessibility/WebContentsAccessibilityTreeTest.java',
1188           [''], action='M')
1189     ]
1190
1191     msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1192         mock_input_api, MockOutputApi())
1193     self.assertEqual(0, len(msgs),
1194                      'Expected %d messages, found %d: %s'
1195                      % (0, len(msgs), msgs))
1196
1197   # Test that a warning is raised when the Android file is not modified.
1198   def testAndroidChangeMissing(self):
1199     mock_input_api = MockInputApi()
1200
1201     mock_input_api.files = [
1202         MockAffectedFile(
1203           'content/test/data/accessibility/aria/foo-expected-win.txt',
1204           [''], action='A'),
1205     ]
1206
1207     msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1208         mock_input_api, MockOutputApi())
1209     self.assertEqual(1, len(msgs),
1210                      'Expected %d messages, found %d: %s'
1211                      % (1, len(msgs), msgs))
1212
1213   # Test that Android change is not required when no platform expectations files are changed.
1214   def testAndroidChangNotMissing(self):
1215     mock_input_api = MockInputApi()
1216
1217     mock_input_api.files = [
1218         MockAffectedFile('content/test/data/accessibility/accname/foo.txt',
1219           [''], action='A'),
1220         MockAffectedFile(
1221           'content/test/data/accessibility/html/foo-expected-blink.txt',
1222           [''], action='A'),
1223         MockAffectedFile('content/test/data/accessibility/html/foo.html',
1224           [''], action='A'),
1225         MockAffectedFile('content/test/data/accessibility/aria/foo.cc',
1226           [''], action='A'),
1227         MockAffectedFile('content/test/data/accessibility/css/foo.h',
1228           [''], action='A'),
1229         MockAffectedFile('content/test/data/accessibility/tree/foo.py',
1230           [''], action='A')
1231     ]
1232
1233     msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1234         mock_input_api, MockOutputApi())
1235     self.assertEqual(0, len(msgs),
1236                      'Expected %d messages, found %d: %s'
1237                      % (0, len(msgs), msgs))
1238
1239   # Test that Android change is not required for unrelated html files.
1240   def testIgnoreNonRelatedHtmlFiles(self):
1241     mock_input_api = MockInputApi()
1242
1243     mock_input_api.files = [
1244         MockAffectedFile('content/test/data/accessibility/event/foo.html',
1245           [''], action='A'),
1246     ]
1247
1248     msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1249         mock_input_api, MockOutputApi())
1250     self.assertEqual(0, len(msgs),
1251                      'Expected %d messages, found %d: %s'
1252                      % (0, len(msgs), msgs))
1253
1254   # Test that only modifying an html file will not trigger the warning.
1255   def testIgnoreModifiedFiles(self):
1256     mock_input_api = MockInputApi()
1257
1258     mock_input_api.files = [
1259         MockAffectedFile('content/test/data/accessibility/aria/foo.html',
1260           [''], action='M')
1261     ]
1262
1263     msgs = PRESUBMIT.CheckAccessibilityTreeTestsAreIncludedForAndroid(
1264         mock_input_api, MockOutputApi())
1265     self.assertEqual(0, len(msgs),
1266                      'Expected %d messages, found %d: %s'
1267                      % (0, len(msgs), msgs))
1268
1269 class AndroidDeprecatedTestAnnotationTest(unittest.TestCase):
1270   def testCheckAndroidTestAnnotationUsage(self):
1271     mock_input_api = MockInputApi()
1272     mock_output_api = MockOutputApi()
1273
1274     mock_input_api.files = [
1275         MockAffectedFile('LalaLand.java', [
1276           'random stuff'
1277         ]),
1278         MockAffectedFile('CorrectUsage.java', [
1279           'import androidx.test.filters.LargeTest;',
1280           'import androidx.test.filters.MediumTest;',
1281           'import androidx.test.filters.SmallTest;',
1282         ]),
1283         MockAffectedFile('UsedDeprecatedLargeTestAnnotation.java', [
1284           'import android.test.suitebuilder.annotation.LargeTest;',
1285         ]),
1286         MockAffectedFile('UsedDeprecatedMediumTestAnnotation.java', [
1287           'import android.test.suitebuilder.annotation.MediumTest;',
1288         ]),
1289         MockAffectedFile('UsedDeprecatedSmallTestAnnotation.java', [
1290           'import android.test.suitebuilder.annotation.SmallTest;',
1291         ]),
1292         MockAffectedFile('UsedDeprecatedSmokeAnnotation.java', [
1293           'import android.test.suitebuilder.annotation.Smoke;',
1294         ])
1295     ]
1296     msgs = PRESUBMIT._CheckAndroidTestAnnotationUsage(
1297         mock_input_api, mock_output_api)
1298     self.assertEqual(1, len(msgs),
1299                      'Expected %d items, found %d: %s'
1300                      % (1, len(msgs), msgs))
1301     self.assertEqual(4, len(msgs[0].items),
1302                      'Expected %d items, found %d: %s'
1303                      % (4, len(msgs[0].items), msgs[0].items))
1304     self.assertTrue('UsedDeprecatedLargeTestAnnotation.java:1' in msgs[0].items,
1305                     'UsedDeprecatedLargeTestAnnotation not found in errors')
1306     self.assertTrue('UsedDeprecatedMediumTestAnnotation.java:1'
1307                     in msgs[0].items,
1308                     'UsedDeprecatedMediumTestAnnotation not found in errors')
1309     self.assertTrue('UsedDeprecatedSmallTestAnnotation.java:1' in msgs[0].items,
1310                     'UsedDeprecatedSmallTestAnnotation not found in errors')
1311     self.assertTrue('UsedDeprecatedSmokeAnnotation.java:1' in msgs[0].items,
1312                     'UsedDeprecatedSmokeAnnotation not found in errors')
1313
1314 class AndroidBannedImportTest(unittest.TestCase):
1315   def testCheckAndroidNoBannedImports(self):
1316     mock_input_api = MockInputApi()
1317     mock_output_api = MockOutputApi()
1318
1319     test_files = [
1320       MockAffectedFile('RandomStufff.java', [
1321         'random stuff'
1322       ]),
1323       MockAffectedFile('NoBannedImports.java', [
1324         'import androidx.test.filters.LargeTest;',
1325         'import androidx.test.filters.MediumTest;',
1326         'import androidx.test.filters.SmallTest;',
1327       ]),
1328       MockAffectedFile('BannedUri.java', [
1329         'import java.net.URI;',
1330       ]),
1331       MockAffectedFile('BannedTargetApi.java', [
1332         'import android.annotation.TargetApi;',
1333       ]),
1334       MockAffectedFile('BannedUiThreadTestRule.java', [
1335         'import androidx.test.rule.UiThreadTestRule;',
1336       ]),
1337       MockAffectedFile('BannedUiThreadTest.java', [
1338         'import androidx.test.annotation.UiThreadTest;',
1339       ]),
1340       MockAffectedFile('BannedActivityTestRule.java', [
1341         'import androidx.test.rule.ActivityTestRule;',
1342       ]),
1343       MockAffectedFile('BannedVectorDrawableCompat.java', [
1344         'import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;',
1345       ])
1346     ]
1347     msgs = []
1348     for file in test_files:
1349       mock_input_api.files = [file]
1350       msgs.append(PRESUBMIT._CheckAndroidNoBannedImports(
1351         mock_input_api, mock_output_api))
1352     self.assertEqual(0, len(msgs[0]))
1353     self.assertEqual(0, len(msgs[1]))
1354     self.assertTrue(msgs[2][0].message.startswith(textwrap.dedent("""\
1355       Banned imports were used.
1356           BannedUri.java:1:"""
1357     )))
1358     self.assertTrue(msgs[3][0].message.startswith(textwrap.dedent("""\
1359       Banned imports were used.
1360           BannedTargetApi.java:1:"""
1361     )))
1362     self.assertTrue(msgs[4][0].message.startswith(textwrap.dedent("""\
1363       Banned imports were used.
1364           BannedUiThreadTestRule.java:1:"""
1365     )))
1366     self.assertTrue(msgs[5][0].message.startswith(textwrap.dedent("""\
1367       Banned imports were used.
1368           BannedUiThreadTest.java:1:"""
1369     )))
1370     self.assertTrue(msgs[6][0].message.startswith(textwrap.dedent("""\
1371       Banned imports were used.
1372           BannedActivityTestRule.java:1:"""
1373     )))
1374     self.assertTrue(msgs[7][0].message.startswith(textwrap.dedent("""\
1375       Banned imports were used.
1376           BannedVectorDrawableCompat.java:1:"""
1377     )))
1378
1379 class CheckNoDownstreamDepsTest(unittest.TestCase):
1380   def testInvalidDepFromUpstream(self):
1381     mock_input_api = MockInputApi()
1382     mock_output_api = MockOutputApi()
1383
1384     mock_input_api.files = [
1385         MockAffectedFile('BUILD.gn', [
1386           'deps = [',
1387           '   "//clank/target:test",',
1388           ']'
1389         ]),
1390         MockAffectedFile('chrome/android/BUILD.gn', [
1391           'deps = [ "//clank/target:test" ]'
1392         ]),
1393         MockAffectedFile('chrome/chrome_java_deps.gni', [
1394           'java_deps = [',
1395           '   "//clank/target:test",',
1396           ']'
1397         ]),
1398     ]
1399     mock_input_api.change.RepositoryRoot = lambda: 'chromium/src'
1400     msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1401         mock_input_api, mock_output_api)
1402     self.assertEqual(1, len(msgs),
1403                      'Expected %d items, found %d: %s'
1404                      % (1, len(msgs), msgs))
1405     self.assertEqual(3, len(msgs[0].items),
1406                      'Expected %d items, found %d: %s'
1407                      % (3, len(msgs[0].items), msgs[0].items))
1408     self.assertTrue(any('BUILD.gn:2' in item for item in msgs[0].items),
1409                     'BUILD.gn not found in errors')
1410     self.assertTrue(
1411         any('chrome/android/BUILD.gn:1' in item for item in msgs[0].items),
1412         'chrome/android/BUILD.gn:1 not found in errors')
1413     self.assertTrue(
1414         any('chrome/chrome_java_deps.gni:2' in item for item in msgs[0].items),
1415         'chrome/chrome_java_deps.gni:2 not found in errors')
1416
1417   def testAllowsComments(self):
1418     mock_input_api = MockInputApi()
1419     mock_output_api = MockOutputApi()
1420
1421     mock_input_api.files = [
1422         MockAffectedFile('BUILD.gn', [
1423           '# real implementation in //clank/target:test',
1424         ]),
1425     ]
1426     mock_input_api.change.RepositoryRoot = lambda: 'chromium/src'
1427     msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1428         mock_input_api, mock_output_api)
1429     self.assertEqual(0, len(msgs),
1430                      'Expected %d items, found %d: %s'
1431                      % (0, len(msgs), msgs))
1432
1433   def testOnlyChecksBuildFiles(self):
1434     mock_input_api = MockInputApi()
1435     mock_output_api = MockOutputApi()
1436
1437     mock_input_api.files = [
1438         MockAffectedFile('README.md', [
1439           'DEPS = [ "//clank/target:test" ]'
1440         ]),
1441         MockAffectedFile('chrome/android/java/file.java', [
1442           '//clank/ only function'
1443         ]),
1444     ]
1445     mock_input_api.change.RepositoryRoot = lambda: 'chromium/src'
1446     msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1447         mock_input_api, mock_output_api)
1448     self.assertEqual(0, len(msgs),
1449                      'Expected %d items, found %d: %s'
1450                      % (0, len(msgs), msgs))
1451
1452   def testValidDepFromDownstream(self):
1453     mock_input_api = MockInputApi()
1454     mock_output_api = MockOutputApi()
1455
1456     mock_input_api.files = [
1457         MockAffectedFile('BUILD.gn', [
1458           'DEPS = [',
1459           '   "//clank/target:test",',
1460           ']'
1461         ]),
1462         MockAffectedFile('java/BUILD.gn', [
1463           'DEPS = [ "//clank/target:test" ]'
1464         ]),
1465     ]
1466     mock_input_api.change.RepositoryRoot = lambda: 'chromium/src/clank'
1467     msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1468         mock_input_api, mock_output_api)
1469     self.assertEqual(0, len(msgs),
1470                      'Expected %d items, found %d: %s'
1471                      % (0, len(msgs), msgs))
1472
1473 class AndroidDeprecatedJUnitFrameworkTest(unittest.TestCase):
1474   def testCheckAndroidTestJUnitFramework(self):
1475     mock_input_api = MockInputApi()
1476     mock_output_api = MockOutputApi()
1477
1478     mock_input_api.files = [
1479         MockAffectedFile('LalaLand.java', [
1480           'random stuff'
1481         ]),
1482         MockAffectedFile('CorrectUsage.java', [
1483           'import org.junit.ABC',
1484           'import org.junit.XYZ;',
1485         ]),
1486         MockAffectedFile('UsedDeprecatedJUnit.java', [
1487           'import junit.framework.*;',
1488         ]),
1489         MockAffectedFile('UsedDeprecatedJUnitAssert.java', [
1490           'import junit.framework.Assert;',
1491         ]),
1492     ]
1493     msgs = PRESUBMIT._CheckAndroidTestJUnitFrameworkImport(
1494         mock_input_api, mock_output_api)
1495     self.assertEqual(1, len(msgs),
1496                      'Expected %d items, found %d: %s'
1497                      % (1, len(msgs), msgs))
1498     self.assertEqual(2, len(msgs[0].items),
1499                      'Expected %d items, found %d: %s'
1500                      % (2, len(msgs[0].items), msgs[0].items))
1501     self.assertTrue('UsedDeprecatedJUnit.java:1' in msgs[0].items,
1502                     'UsedDeprecatedJUnit.java not found in errors')
1503     self.assertTrue('UsedDeprecatedJUnitAssert.java:1'
1504                     in msgs[0].items,
1505                     'UsedDeprecatedJUnitAssert not found in errors')
1506
1507
1508 class AndroidJUnitBaseClassTest(unittest.TestCase):
1509   def testCheckAndroidTestJUnitBaseClass(self):
1510     mock_input_api = MockInputApi()
1511     mock_output_api = MockOutputApi()
1512
1513     mock_input_api.files = [
1514         MockAffectedFile('LalaLand.java', [
1515           'random stuff'
1516         ]),
1517         MockAffectedFile('CorrectTest.java', [
1518           '@RunWith(ABC.class);'
1519           'public class CorrectTest {',
1520           '}',
1521         ]),
1522         MockAffectedFile('HistoricallyIncorrectTest.java', [
1523           'public class Test extends BaseCaseA {',
1524           '}',
1525         ], old_contents=[
1526           'public class Test extends BaseCaseB {',
1527           '}',
1528         ]),
1529         MockAffectedFile('CorrectTestWithInterface.java', [
1530           '@RunWith(ABC.class);'
1531           'public class CorrectTest implement Interface {',
1532           '}',
1533         ]),
1534         MockAffectedFile('IncorrectTest.java', [
1535           'public class IncorrectTest extends TestCase {',
1536           '}',
1537         ]),
1538         MockAffectedFile('IncorrectWithInterfaceTest.java', [
1539           'public class Test implements X extends BaseClass {',
1540           '}',
1541         ]),
1542         MockAffectedFile('IncorrectMultiLineTest.java', [
1543           'public class Test implements X, Y, Z',
1544           '        extends TestBase {',
1545           '}',
1546         ]),
1547     ]
1548     msgs = PRESUBMIT._CheckAndroidTestJUnitInheritance(
1549         mock_input_api, mock_output_api)
1550     self.assertEqual(1, len(msgs),
1551                      'Expected %d items, found %d: %s'
1552                      % (1, len(msgs), msgs))
1553     self.assertEqual(3, len(msgs[0].items),
1554                      'Expected %d items, found %d: %s'
1555                      % (3, len(msgs[0].items), msgs[0].items))
1556     self.assertTrue('IncorrectTest.java:1' in msgs[0].items,
1557                     'IncorrectTest not found in errors')
1558     self.assertTrue('IncorrectWithInterfaceTest.java:1'
1559                     in msgs[0].items,
1560                     'IncorrectWithInterfaceTest not found in errors')
1561     self.assertTrue('IncorrectMultiLineTest.java:2' in msgs[0].items,
1562                     'IncorrectMultiLineTest not found in errors')
1563
1564 class AndroidDebuggableBuildTest(unittest.TestCase):
1565
1566   def testCheckAndroidDebuggableBuild(self):
1567     mock_input_api = MockInputApi()
1568     mock_output_api = MockOutputApi()
1569
1570     mock_input_api.files = [
1571       MockAffectedFile('RandomStuff.java', [
1572         'random stuff'
1573       ]),
1574       MockAffectedFile('CorrectUsage.java', [
1575         'import org.chromium.base.BuildInfo;',
1576         'some random stuff',
1577         'boolean isOsDebuggable = BuildInfo.isDebugAndroid();',
1578       ]),
1579       MockAffectedFile('JustCheckUserdebugBuild.java', [
1580         'import android.os.Build;',
1581         'some random stuff',
1582         'boolean isOsDebuggable = Build.TYPE.equals("userdebug")',
1583       ]),
1584       MockAffectedFile('JustCheckEngineeringBuild.java', [
1585         'import android.os.Build;',
1586         'some random stuff',
1587         'boolean isOsDebuggable = "eng".equals(Build.TYPE)',
1588       ]),
1589       MockAffectedFile('UsedBuildType.java', [
1590         'import android.os.Build;',
1591         'some random stuff',
1592         'boolean isOsDebuggable = Build.TYPE.equals("userdebug")'
1593             '|| "eng".equals(Build.TYPE)',
1594       ]),
1595       MockAffectedFile('UsedExplicitBuildType.java', [
1596         'some random stuff',
1597         'boolean isOsDebuggable = android.os.Build.TYPE.equals("userdebug")'
1598             '|| "eng".equals(android.os.Build.TYPE)',
1599       ]),
1600     ]
1601
1602     msgs = PRESUBMIT._CheckAndroidDebuggableBuild(
1603         mock_input_api, mock_output_api)
1604     self.assertEqual(1, len(msgs),
1605                      'Expected %d items, found %d: %s'
1606                      % (1, len(msgs), msgs))
1607     self.assertEqual(4, len(msgs[0].items),
1608                      'Expected %d items, found %d: %s'
1609                      % (4, len(msgs[0].items), msgs[0].items))
1610     self.assertTrue('JustCheckUserdebugBuild.java:3' in msgs[0].items)
1611     self.assertTrue('JustCheckEngineeringBuild.java:3' in msgs[0].items)
1612     self.assertTrue('UsedBuildType.java:3' in msgs[0].items)
1613     self.assertTrue('UsedExplicitBuildType.java:2' in msgs[0].items)
1614
1615
1616 class LogUsageTest(unittest.TestCase):
1617
1618   def testCheckAndroidCrLogUsage(self):
1619     mock_input_api = MockInputApi()
1620     mock_output_api = MockOutputApi()
1621
1622     mock_input_api.files = [
1623       MockAffectedFile('RandomStuff.java', [
1624         'random stuff'
1625       ]),
1626       MockAffectedFile('HasAndroidLog.java', [
1627         'import android.util.Log;',
1628         'some random stuff',
1629         'Log.d("TAG", "foo");',
1630       ]),
1631       MockAffectedFile('HasExplicitUtilLog.java', [
1632         'some random stuff',
1633         'android.util.Log.d("TAG", "foo");',
1634       ]),
1635       MockAffectedFile('IsInBasePackage.java', [
1636         'package org.chromium.base;',
1637         'private static final String TAG = "cr_Foo";',
1638         'Log.d(TAG, "foo");',
1639       ]),
1640       MockAffectedFile('IsInBasePackageButImportsLog.java', [
1641         'package org.chromium.base;',
1642         'import android.util.Log;',
1643         'private static final String TAG = "cr_Foo";',
1644         'Log.d(TAG, "foo");',
1645       ]),
1646       MockAffectedFile('HasBothLog.java', [
1647         'import org.chromium.base.Log;',
1648         'some random stuff',
1649         'private static final String TAG = "cr_Foo";',
1650         'Log.d(TAG, "foo");',
1651         'android.util.Log.d("TAG", "foo");',
1652       ]),
1653       MockAffectedFile('HasCorrectTag.java', [
1654         'import org.chromium.base.Log;',
1655         'some random stuff',
1656         'private static final String TAG = "cr_Foo";',
1657         'Log.d(TAG, "foo");',
1658       ]),
1659       MockAffectedFile('HasOldTag.java', [
1660         'import org.chromium.base.Log;',
1661         'some random stuff',
1662         'private static final String TAG = "cr.Foo";',
1663         'Log.d(TAG, "foo");',
1664       ]),
1665       MockAffectedFile('HasDottedTag.java', [
1666         'import org.chromium.base.Log;',
1667         'some random stuff',
1668         'private static final String TAG = "cr_foo.bar";',
1669         'Log.d(TAG, "foo");',
1670       ]),
1671       MockAffectedFile('HasDottedTagPublic.java', [
1672         'import org.chromium.base.Log;',
1673         'some random stuff',
1674         'public static final String TAG = "cr_foo.bar";',
1675         'Log.d(TAG, "foo");',
1676       ]),
1677       MockAffectedFile('HasNoTagDecl.java', [
1678         'import org.chromium.base.Log;',
1679         'some random stuff',
1680         'Log.d(TAG, "foo");',
1681       ]),
1682       MockAffectedFile('HasIncorrectTagDecl.java', [
1683         'import org.chromium.base.Log;',
1684         'private static final String TAHG = "cr_Foo";',
1685         'some random stuff',
1686         'Log.d(TAG, "foo");',
1687       ]),
1688       MockAffectedFile('HasInlineTag.java', [
1689         'import org.chromium.base.Log;',
1690         'some random stuff',
1691         'private static final String TAG = "cr_Foo";',
1692         'Log.d("TAG", "foo");',
1693       ]),
1694       MockAffectedFile('HasInlineTagWithSpace.java', [
1695         'import org.chromium.base.Log;',
1696         'some random stuff',
1697         'private static final String TAG = "cr_Foo";',
1698         'Log.d("log message", "foo");',
1699       ]),
1700       MockAffectedFile('HasUnprefixedTag.java', [
1701         'import org.chromium.base.Log;',
1702         'some random stuff',
1703         'private static final String TAG = "rubbish";',
1704         'Log.d(TAG, "foo");',
1705       ]),
1706       MockAffectedFile('HasTooLongTag.java', [
1707         'import org.chromium.base.Log;',
1708         'some random stuff',
1709         'private static final String TAG = "21_characters_long___";',
1710         'Log.d(TAG, "foo");',
1711       ]),
1712       MockAffectedFile('HasTooLongTagWithNoLogCallsInDiff.java', [
1713         'import org.chromium.base.Log;',
1714         'some random stuff',
1715         'private static final String TAG = "21_characters_long___";',
1716       ]),
1717     ]
1718
1719     msgs = PRESUBMIT._CheckAndroidCrLogUsage(
1720         mock_input_api, mock_output_api)
1721
1722     self.assertEqual(5, len(msgs),
1723                      'Expected %d items, found %d: %s' % (5, len(msgs), msgs))
1724
1725     # Declaration format
1726     nb = len(msgs[0].items)
1727     self.assertEqual(2, nb,
1728                      'Expected %d items, found %d: %s' % (2, nb, msgs[0].items))
1729     self.assertTrue('HasNoTagDecl.java' in msgs[0].items)
1730     self.assertTrue('HasIncorrectTagDecl.java' in msgs[0].items)
1731
1732     # Tag length
1733     nb = len(msgs[1].items)
1734     self.assertEqual(2, nb,
1735                      'Expected %d items, found %d: %s' % (2, nb, msgs[1].items))
1736     self.assertTrue('HasTooLongTag.java' in msgs[1].items)
1737     self.assertTrue('HasTooLongTagWithNoLogCallsInDiff.java' in msgs[1].items)
1738
1739     # Tag must be a variable named TAG
1740     nb = len(msgs[2].items)
1741     self.assertEqual(3, nb,
1742                      'Expected %d items, found %d: %s' % (3, nb, msgs[2].items))
1743     self.assertTrue('HasBothLog.java:5' in msgs[2].items)
1744     self.assertTrue('HasInlineTag.java:4' in msgs[2].items)
1745     self.assertTrue('HasInlineTagWithSpace.java:4' in msgs[2].items)
1746
1747     # Util Log usage
1748     nb = len(msgs[3].items)
1749     self.assertEqual(3, nb,
1750                      'Expected %d items, found %d: %s' % (3, nb, msgs[3].items))
1751     self.assertTrue('HasAndroidLog.java:3' in msgs[3].items)
1752     self.assertTrue('HasExplicitUtilLog.java:2' in msgs[3].items)
1753     self.assertTrue('IsInBasePackageButImportsLog.java:4' in msgs[3].items)
1754
1755     # Tag must not contain
1756     nb = len(msgs[4].items)
1757     self.assertEqual(3, nb,
1758                      'Expected %d items, found %d: %s' % (2, nb, msgs[4].items))
1759     self.assertTrue('HasDottedTag.java' in msgs[4].items)
1760     self.assertTrue('HasDottedTagPublic.java' in msgs[4].items)
1761     self.assertTrue('HasOldTag.java' in msgs[4].items)
1762
1763
1764 class GoogleAnswerUrlFormatTest(unittest.TestCase):
1765
1766   def testCatchAnswerUrlId(self):
1767     input_api = MockInputApi()
1768     input_api.files = [
1769       MockFile('somewhere/file.cc',
1770                ['char* host = '
1771                 '  "https://support.google.com/chrome/answer/123456";']),
1772       MockFile('somewhere_else/file.cc',
1773                ['char* host = '
1774                 '  "https://support.google.com/chrome/a/answer/123456";']),
1775     ]
1776
1777     warnings = PRESUBMIT.CheckGoogleSupportAnswerUrlOnUpload(
1778       input_api, MockOutputApi())
1779     self.assertEqual(1, len(warnings))
1780     self.assertEqual(2, len(warnings[0].items))
1781
1782   def testAllowAnswerUrlParam(self):
1783     input_api = MockInputApi()
1784     input_api.files = [
1785       MockFile('somewhere/file.cc',
1786                ['char* host = '
1787                 '  "https://support.google.com/chrome/?p=cpn_crash_reports";']),
1788     ]
1789
1790     warnings = PRESUBMIT.CheckGoogleSupportAnswerUrlOnUpload(
1791       input_api, MockOutputApi())
1792     self.assertEqual(0, len(warnings))
1793
1794
1795 class HardcodedGoogleHostsTest(unittest.TestCase):
1796
1797   def testWarnOnAssignedLiterals(self):
1798     input_api = MockInputApi()
1799     input_api.files = [
1800       MockFile('content/file.cc',
1801                ['char* host = "https://www.google.com";']),
1802       MockFile('content/file.cc',
1803                ['char* host = "https://www.googleapis.com";']),
1804       MockFile('content/file.cc',
1805                ['char* host = "https://clients1.google.com";']),
1806     ]
1807
1808     warnings = PRESUBMIT.CheckHardcodedGoogleHostsInLowerLayers(
1809       input_api, MockOutputApi())
1810     self.assertEqual(1, len(warnings))
1811     self.assertEqual(3, len(warnings[0].items))
1812
1813   def testAllowInComment(self):
1814     input_api = MockInputApi()
1815     input_api.files = [
1816       MockFile('content/file.cc',
1817                ['char* host = "https://www.aol.com"; // google.com'])
1818     ]
1819
1820     warnings = PRESUBMIT.CheckHardcodedGoogleHostsInLowerLayers(
1821       input_api, MockOutputApi())
1822     self.assertEqual(0, len(warnings))
1823
1824
1825 class ChromeOsSyncedPrefRegistrationTest(unittest.TestCase):
1826
1827   def testWarnsOnChromeOsDirectories(self):
1828     files = [
1829       MockFile('ash/file.cc',
1830                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1831       MockFile('chrome/browser/chromeos/file.cc',
1832                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1833       MockFile('chromeos/file.cc',
1834                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1835       MockFile('components/arc/file.cc',
1836                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1837       MockFile('components/exo/file.cc',
1838                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1839     ]
1840     input_api = MockInputApi()
1841     for file in files:
1842       input_api.files = [file]
1843       warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1844         input_api, MockOutputApi())
1845       self.assertEqual(1, len(warnings))
1846
1847   def testDoesNotWarnOnSyncOsPref(self):
1848     input_api = MockInputApi()
1849     input_api.files = [
1850       MockFile('chromeos/file.cc',
1851                ['PrefRegistrySyncable::SYNCABLE_OS_PREF']),
1852     ]
1853     warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1854       input_api, MockOutputApi())
1855     self.assertEqual(0, len(warnings))
1856
1857   def testDoesNotWarnOnOtherDirectories(self):
1858     input_api = MockInputApi()
1859     input_api.files = [
1860       MockFile('chrome/browser/ui/file.cc',
1861                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1862       MockFile('components/sync/file.cc',
1863                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1864       MockFile('content/browser/file.cc',
1865                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1866       MockFile('a/notchromeos/file.cc',
1867                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1868     ]
1869     warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1870       input_api, MockOutputApi())
1871     self.assertEqual(0, len(warnings))
1872
1873   def testSeparateWarningForPriorityPrefs(self):
1874     input_api = MockInputApi()
1875     input_api.files = [
1876       MockFile('chromeos/file.cc',
1877                ['PrefRegistrySyncable::SYNCABLE_PREF',
1878                 'PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF']),
1879     ]
1880     warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1881       input_api, MockOutputApi())
1882     self.assertEqual(2, len(warnings))
1883
1884
1885 class ForwardDeclarationTest(unittest.TestCase):
1886   def testCheckHeadersOnlyOutsideThirdParty(self):
1887     mock_input_api = MockInputApi()
1888     mock_input_api.files = [
1889       MockAffectedFile('somewhere/file.cc', [
1890         'class DummyClass;'
1891       ]),
1892       MockAffectedFile('third_party/header.h', [
1893         'class DummyClass;'
1894       ])
1895     ]
1896     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1897                                                           MockOutputApi())
1898     self.assertEqual(0, len(warnings))
1899
1900   def testNoNestedDeclaration(self):
1901     mock_input_api = MockInputApi()
1902     mock_input_api.files = [
1903       MockAffectedFile('somewhere/header.h', [
1904         'class SomeClass {',
1905         ' protected:',
1906         '  class NotAMatch;',
1907         '};'
1908       ])
1909     ]
1910     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1911                                                           MockOutputApi())
1912     self.assertEqual(0, len(warnings))
1913
1914   def testSubStrings(self):
1915     mock_input_api = MockInputApi()
1916     mock_input_api.files = [
1917       MockAffectedFile('somewhere/header.h', [
1918         'class NotUsefulClass;',
1919         'struct SomeStruct;',
1920         'UsefulClass *p1;',
1921         'SomeStructPtr *p2;'
1922       ])
1923     ]
1924     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1925                                                           MockOutputApi())
1926     self.assertEqual(2, len(warnings))
1927
1928   def testUselessForwardDeclaration(self):
1929     mock_input_api = MockInputApi()
1930     mock_input_api.files = [
1931       MockAffectedFile('somewhere/header.h', [
1932         'class DummyClass;',
1933         'struct DummyStruct;',
1934         'class UsefulClass;',
1935         'std::unique_ptr<UsefulClass> p;'
1936       ])
1937     ]
1938     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1939                                                           MockOutputApi())
1940     self.assertEqual(2, len(warnings))
1941
1942   def testBlinkHeaders(self):
1943     mock_input_api = MockInputApi()
1944     mock_input_api.files = [
1945       MockAffectedFile('third_party/blink/header.h', [
1946         'class DummyClass;',
1947         'struct DummyStruct;',
1948       ]),
1949       MockAffectedFile('third_party\\blink\\header.h', [
1950         'class DummyClass;',
1951         'struct DummyStruct;',
1952       ])
1953     ]
1954     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1955                                                           MockOutputApi())
1956     self.assertEqual(4, len(warnings))
1957
1958
1959 class RelativeIncludesTest(unittest.TestCase):
1960   def testThirdPartyNotWebKitIgnored(self):
1961     mock_input_api = MockInputApi()
1962     mock_input_api.files = [
1963       MockAffectedFile('third_party/test.cpp', '#include "../header.h"'),
1964       MockAffectedFile('third_party/test/test.cpp', '#include "../header.h"'),
1965     ]
1966
1967     mock_output_api = MockOutputApi()
1968
1969     errors = PRESUBMIT.CheckForRelativeIncludes(
1970         mock_input_api, mock_output_api)
1971     self.assertEqual(0, len(errors))
1972
1973   def testNonCppFileIgnored(self):
1974     mock_input_api = MockInputApi()
1975     mock_input_api.files = [
1976       MockAffectedFile('test.py', '#include "../header.h"'),
1977     ]
1978
1979     mock_output_api = MockOutputApi()
1980
1981     errors = PRESUBMIT.CheckForRelativeIncludes(
1982         mock_input_api, mock_output_api)
1983     self.assertEqual(0, len(errors))
1984
1985   def testInnocuousChangesAllowed(self):
1986     mock_input_api = MockInputApi()
1987     mock_input_api.files = [
1988       MockAffectedFile('test.cpp', '#include "header.h"'),
1989       MockAffectedFile('test2.cpp', '../'),
1990     ]
1991
1992     mock_output_api = MockOutputApi()
1993
1994     errors = PRESUBMIT.CheckForRelativeIncludes(
1995         mock_input_api, mock_output_api)
1996     self.assertEqual(0, len(errors))
1997
1998   def testRelativeIncludeNonWebKitProducesError(self):
1999     mock_input_api = MockInputApi()
2000     mock_input_api.files = [
2001       MockAffectedFile('test.cpp', ['#include "../header.h"']),
2002     ]
2003
2004     mock_output_api = MockOutputApi()
2005
2006     errors = PRESUBMIT.CheckForRelativeIncludes(
2007         mock_input_api, mock_output_api)
2008     self.assertEqual(1, len(errors))
2009
2010   def testRelativeIncludeWebKitProducesError(self):
2011     mock_input_api = MockInputApi()
2012     mock_input_api.files = [
2013       MockAffectedFile('third_party/blink/test.cpp',
2014                        ['#include "../header.h']),
2015     ]
2016
2017     mock_output_api = MockOutputApi()
2018
2019     errors = PRESUBMIT.CheckForRelativeIncludes(
2020         mock_input_api, mock_output_api)
2021     self.assertEqual(1, len(errors))
2022
2023
2024 class CCIncludeTest(unittest.TestCase):
2025   def testThirdPartyNotBlinkIgnored(self):
2026     mock_input_api = MockInputApi()
2027     mock_input_api.files = [
2028       MockAffectedFile('third_party/test.cpp', '#include "file.cc"'),
2029     ]
2030
2031     mock_output_api = MockOutputApi()
2032
2033     errors = PRESUBMIT.CheckForCcIncludes(
2034         mock_input_api, mock_output_api)
2035     self.assertEqual(0, len(errors))
2036
2037   def testPythonFileIgnored(self):
2038     mock_input_api = MockInputApi()
2039     mock_input_api.files = [
2040       MockAffectedFile('test.py', '#include "file.cc"'),
2041     ]
2042
2043     mock_output_api = MockOutputApi()
2044
2045     errors = PRESUBMIT.CheckForCcIncludes(
2046         mock_input_api, mock_output_api)
2047     self.assertEqual(0, len(errors))
2048
2049   def testIncFilesAccepted(self):
2050     mock_input_api = MockInputApi()
2051     mock_input_api.files = [
2052       MockAffectedFile('test.py', '#include "file.inc"'),
2053     ]
2054
2055     mock_output_api = MockOutputApi()
2056
2057     errors = PRESUBMIT.CheckForCcIncludes(
2058         mock_input_api, mock_output_api)
2059     self.assertEqual(0, len(errors))
2060
2061   def testInnocuousChangesAllowed(self):
2062     mock_input_api = MockInputApi()
2063     mock_input_api.files = [
2064       MockAffectedFile('test.cpp', '#include "header.h"'),
2065       MockAffectedFile('test2.cpp', 'Something "file.cc"'),
2066     ]
2067
2068     mock_output_api = MockOutputApi()
2069
2070     errors = PRESUBMIT.CheckForCcIncludes(
2071         mock_input_api, mock_output_api)
2072     self.assertEqual(0, len(errors))
2073
2074   def testCcIncludeNonBlinkProducesError(self):
2075     mock_input_api = MockInputApi()
2076     mock_input_api.files = [
2077       MockAffectedFile('test.cpp', ['#include "file.cc"']),
2078     ]
2079
2080     mock_output_api = MockOutputApi()
2081
2082     errors = PRESUBMIT.CheckForCcIncludes(
2083         mock_input_api, mock_output_api)
2084     self.assertEqual(1, len(errors))
2085
2086   def testCppIncludeBlinkProducesError(self):
2087     mock_input_api = MockInputApi()
2088     mock_input_api.files = [
2089       MockAffectedFile('third_party/blink/test.cpp',
2090                        ['#include "foo/file.cpp"']),
2091     ]
2092
2093     mock_output_api = MockOutputApi()
2094
2095     errors = PRESUBMIT.CheckForCcIncludes(
2096         mock_input_api, mock_output_api)
2097     self.assertEqual(1, len(errors))
2098
2099
2100 class GnGlobForwardTest(unittest.TestCase):
2101   def testAddBareGlobs(self):
2102     mock_input_api = MockInputApi()
2103     mock_input_api.files = [
2104       MockAffectedFile('base/stuff.gni', [
2105           'forward_variables_from(invoker, "*")']),
2106       MockAffectedFile('base/BUILD.gn', [
2107           'forward_variables_from(invoker, "*")']),
2108     ]
2109     warnings = PRESUBMIT.CheckGnGlobForward(mock_input_api, MockOutputApi())
2110     self.assertEqual(1, len(warnings))
2111     msg = '\n'.join(warnings[0].items)
2112     self.assertIn('base/stuff.gni', msg)
2113     # Should not check .gn files. Local templates don't need to care about
2114     # visibility / testonly.
2115     self.assertNotIn('base/BUILD.gn', msg)
2116
2117   def testValidUses(self):
2118     mock_input_api = MockInputApi()
2119     mock_input_api.files = [
2120       MockAffectedFile('base/stuff.gni', [
2121           'forward_variables_from(invoker, "*", [])']),
2122       MockAffectedFile('base/stuff2.gni', [
2123           'forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)']),
2124       MockAffectedFile('base/stuff3.gni', [
2125           'forward_variables_from(invoker, [ "testonly" ])']),
2126     ]
2127     warnings = PRESUBMIT.CheckGnGlobForward(mock_input_api, MockOutputApi())
2128     self.assertEqual([], warnings)
2129
2130
2131 class GnRebasePathTest(unittest.TestCase):
2132   def testAddAbsolutePath(self):
2133     mock_input_api = MockInputApi()
2134     mock_input_api.files = [
2135         MockAffectedFile('base/BUILD.gn', ['rebase_path("$target_gen_dir", "//")']),
2136         MockAffectedFile('base/root/BUILD.gn', ['rebase_path("$target_gen_dir", "/")']),
2137         MockAffectedFile('base/variable/BUILD.gn', ['rebase_path(target_gen_dir, "/")']),
2138     ]
2139     warnings = PRESUBMIT.CheckGnRebasePath(mock_input_api, MockOutputApi())
2140     self.assertEqual(1, len(warnings))
2141     msg = '\n'.join(warnings[0].items)
2142     self.assertIn('base/BUILD.gn', msg)
2143     self.assertIn('base/root/BUILD.gn', msg)
2144     self.assertIn('base/variable/BUILD.gn', msg)
2145     self.assertEqual(3, len(warnings[0].items))
2146
2147   def testValidUses(self):
2148     mock_input_api = MockInputApi()
2149     mock_input_api.files = [
2150         MockAffectedFile('base/foo/BUILD.gn', ['rebase_path("$target_gen_dir", root_build_dir)']),
2151         MockAffectedFile('base/bar/BUILD.gn', ['rebase_path("$target_gen_dir", root_build_dir, "/")']),
2152         MockAffectedFile('base/baz/BUILD.gn', ['rebase_path(target_gen_dir, root_build_dir)']),
2153         MockAffectedFile('base/baz/BUILD.gn', ['rebase_path(target_gen_dir, "//some/arbitrary/path")']),
2154         MockAffectedFile('base/okay_slash/BUILD.gn', ['rebase_path(".", "//")']),
2155     ]
2156     warnings = PRESUBMIT.CheckGnRebasePath(mock_input_api, MockOutputApi())
2157     self.assertEqual([], warnings)
2158
2159
2160 class NewHeaderWithoutGnChangeTest(unittest.TestCase):
2161   def testAddHeaderWithoutGn(self):
2162     mock_input_api = MockInputApi()
2163     mock_input_api.files = [
2164       MockAffectedFile('base/stuff.h', ''),
2165     ]
2166     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2167         mock_input_api, MockOutputApi())
2168     self.assertEqual(1, len(warnings))
2169     self.assertTrue('base/stuff.h' in warnings[0].items)
2170
2171   def testModifyHeader(self):
2172     mock_input_api = MockInputApi()
2173     mock_input_api.files = [
2174       MockAffectedFile('base/stuff.h', '', action='M'),
2175     ]
2176     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2177         mock_input_api, MockOutputApi())
2178     self.assertEqual(0, len(warnings))
2179
2180   def testDeleteHeader(self):
2181     mock_input_api = MockInputApi()
2182     mock_input_api.files = [
2183       MockAffectedFile('base/stuff.h', '', action='D'),
2184     ]
2185     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2186         mock_input_api, MockOutputApi())
2187     self.assertEqual(0, len(warnings))
2188
2189   def testAddHeaderWithGn(self):
2190     mock_input_api = MockInputApi()
2191     mock_input_api.files = [
2192       MockAffectedFile('base/stuff.h', ''),
2193       MockAffectedFile('base/BUILD.gn', 'stuff.h'),
2194     ]
2195     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2196         mock_input_api, MockOutputApi())
2197     self.assertEqual(0, len(warnings))
2198
2199   def testAddHeaderWithGni(self):
2200     mock_input_api = MockInputApi()
2201     mock_input_api.files = [
2202       MockAffectedFile('base/stuff.h', ''),
2203       MockAffectedFile('base/files.gni', 'stuff.h'),
2204     ]
2205     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2206         mock_input_api, MockOutputApi())
2207     self.assertEqual(0, len(warnings))
2208
2209   def testAddHeaderWithOther(self):
2210     mock_input_api = MockInputApi()
2211     mock_input_api.files = [
2212       MockAffectedFile('base/stuff.h', ''),
2213       MockAffectedFile('base/stuff.cc', 'stuff.h'),
2214     ]
2215     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2216         mock_input_api, MockOutputApi())
2217     self.assertEqual(1, len(warnings))
2218
2219   def testAddHeaderWithWrongGn(self):
2220     mock_input_api = MockInputApi()
2221     mock_input_api.files = [
2222       MockAffectedFile('base/stuff.h', ''),
2223       MockAffectedFile('base/BUILD.gn', 'stuff_h'),
2224     ]
2225     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2226         mock_input_api, MockOutputApi())
2227     self.assertEqual(1, len(warnings))
2228
2229   def testAddHeadersWithGn(self):
2230     mock_input_api = MockInputApi()
2231     mock_input_api.files = [
2232       MockAffectedFile('base/stuff.h', ''),
2233       MockAffectedFile('base/another.h', ''),
2234       MockAffectedFile('base/BUILD.gn', 'another.h\nstuff.h'),
2235     ]
2236     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2237         mock_input_api, MockOutputApi())
2238     self.assertEqual(0, len(warnings))
2239
2240   def testAddHeadersWithWrongGn(self):
2241     mock_input_api = MockInputApi()
2242     mock_input_api.files = [
2243       MockAffectedFile('base/stuff.h', ''),
2244       MockAffectedFile('base/another.h', ''),
2245       MockAffectedFile('base/BUILD.gn', 'another_h\nstuff.h'),
2246     ]
2247     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2248         mock_input_api, MockOutputApi())
2249     self.assertEqual(1, len(warnings))
2250     self.assertFalse('base/stuff.h' in warnings[0].items)
2251     self.assertTrue('base/another.h' in warnings[0].items)
2252
2253   def testAddHeadersWithWrongGn2(self):
2254     mock_input_api = MockInputApi()
2255     mock_input_api.files = [
2256       MockAffectedFile('base/stuff.h', ''),
2257       MockAffectedFile('base/another.h', ''),
2258       MockAffectedFile('base/BUILD.gn', 'another_h\nstuff_h'),
2259     ]
2260     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
2261         mock_input_api, MockOutputApi())
2262     self.assertEqual(1, len(warnings))
2263     self.assertTrue('base/stuff.h' in warnings[0].items)
2264     self.assertTrue('base/another.h' in warnings[0].items)
2265
2266
2267 class CorrectProductNameInMessagesTest(unittest.TestCase):
2268   def testProductNameInDesc(self):
2269     mock_input_api = MockInputApi()
2270     mock_input_api.files = [
2271       MockAffectedFile('chrome/app/google_chrome_strings.grd', [
2272         '<message name="Foo" desc="Welcome to Chrome">',
2273         '  Welcome to Chrome!',
2274         '</message>',
2275       ]),
2276       MockAffectedFile('chrome/app/chromium_strings.grd', [
2277         '<message name="Bar" desc="Welcome to Chrome">',
2278         '  Welcome to Chromium!',
2279         '</message>',
2280       ]),
2281     ]
2282     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2283         mock_input_api, MockOutputApi())
2284     self.assertEqual(0, len(warnings))
2285
2286   def testChromeInChromium(self):
2287     mock_input_api = MockInputApi()
2288     mock_input_api.files = [
2289       MockAffectedFile('chrome/app/google_chrome_strings.grd', [
2290         '<message name="Foo" desc="Welcome to Chrome">',
2291         '  Welcome to Chrome!',
2292         '</message>',
2293       ]),
2294       MockAffectedFile('chrome/app/chromium_strings.grd', [
2295         '<message name="Bar" desc="Welcome to Chrome">',
2296         '  Welcome to Chrome!',
2297         '</message>',
2298       ]),
2299     ]
2300     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2301         mock_input_api, MockOutputApi())
2302     self.assertEqual(1, len(warnings))
2303     self.assertTrue('chrome/app/chromium_strings.grd' in warnings[0].items[0])
2304
2305   def testChromiumInChrome(self):
2306     mock_input_api = MockInputApi()
2307     mock_input_api.files = [
2308       MockAffectedFile('chrome/app/google_chrome_strings.grd', [
2309         '<message name="Foo" desc="Welcome to Chrome">',
2310         '  Welcome to Chromium!',
2311         '</message>',
2312       ]),
2313       MockAffectedFile('chrome/app/chromium_strings.grd', [
2314         '<message name="Bar" desc="Welcome to Chrome">',
2315         '  Welcome to Chromium!',
2316         '</message>',
2317       ]),
2318     ]
2319     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2320         mock_input_api, MockOutputApi())
2321     self.assertEqual(1, len(warnings))
2322     self.assertTrue(
2323         'chrome/app/google_chrome_strings.grd:2' in warnings[0].items[0])
2324
2325   def testChromeForTestingInChromium(self):
2326     mock_input_api = MockInputApi()
2327     mock_input_api.files = [
2328       MockAffectedFile('chrome/app/chromium_strings.grd', [
2329         '<message name="Bar" desc="Welcome to Chrome">',
2330         '  Welcome to Chrome for Testing!',
2331         '</message>',
2332       ]),
2333     ]
2334     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2335         mock_input_api, MockOutputApi())
2336     self.assertEqual(0, len(warnings))
2337
2338   def testChromeForTestingInChrome(self):
2339     mock_input_api = MockInputApi()
2340     mock_input_api.files = [
2341       MockAffectedFile('chrome/app/google_chrome_strings.grd', [
2342         '<message name="Bar" desc="Welcome to Chrome">',
2343         '  Welcome to Chrome for Testing!',
2344         '</message>',
2345       ]),
2346     ]
2347     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2348         mock_input_api, MockOutputApi())
2349     self.assertEqual(1, len(warnings))
2350     self.assertTrue(
2351         'chrome/app/google_chrome_strings.grd:2' in warnings[0].items[0])
2352
2353   def testMultipleInstances(self):
2354     mock_input_api = MockInputApi()
2355     mock_input_api.files = [
2356       MockAffectedFile('chrome/app/chromium_strings.grd', [
2357         '<message name="Bar" desc="Welcome to Chrome">',
2358         '  Welcome to Chrome!',
2359         '</message>',
2360         '<message name="Baz" desc="A correct message">',
2361         '  Chromium is the software you are using.',
2362         '</message>',
2363         '<message name="Bat" desc="An incorrect message">',
2364         '  Google Chrome is the software you are using.',
2365         '</message>',
2366       ]),
2367     ]
2368     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2369         mock_input_api, MockOutputApi())
2370     self.assertEqual(1, len(warnings))
2371     self.assertTrue(
2372         'chrome/app/chromium_strings.grd:2' in warnings[0].items[0])
2373     self.assertTrue(
2374         'chrome/app/chromium_strings.grd:8' in warnings[0].items[1])
2375
2376   def testMultipleWarnings(self):
2377     mock_input_api = MockInputApi()
2378     mock_input_api.files = [
2379       MockAffectedFile('chrome/app/chromium_strings.grd', [
2380         '<message name="Bar" desc="Welcome to Chrome">',
2381         '  Welcome to Chrome!',
2382         '</message>',
2383         '<message name="Baz" desc="A correct message">',
2384         '  Chromium is the software you are using.',
2385         '</message>',
2386         '<message name="Bat" desc="An incorrect message">',
2387         '  Google Chrome is the software you are using.',
2388         '</message>',
2389       ]),
2390       MockAffectedFile('components/components_google_chrome_strings.grd', [
2391         '<message name="Bar" desc="Welcome to Chrome">',
2392         '  Welcome to Chrome!',
2393         '</message>',
2394         '<message name="Baz" desc="A correct message">',
2395         '  Chromium is the software you are using.',
2396         '</message>',
2397         '<message name="Bat" desc="An incorrect message">',
2398         '  Google Chrome is the software you are using.',
2399         '</message>',
2400       ]),
2401     ]
2402     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2403         mock_input_api, MockOutputApi())
2404     self.assertEqual(2, len(warnings))
2405     self.assertTrue(
2406         'components/components_google_chrome_strings.grd:5'
2407              in warnings[0].items[0])
2408     self.assertTrue(
2409         'chrome/app/chromium_strings.grd:2' in warnings[1].items[0])
2410     self.assertTrue(
2411         'chrome/app/chromium_strings.grd:8' in warnings[1].items[1])
2412
2413
2414 class _SecurityOwnersTestCase(unittest.TestCase):
2415   def _createMockInputApi(self):
2416     mock_input_api = MockInputApi()
2417     def FakeRepositoryRoot():
2418       return mock_input_api.os_path.join('chromium', 'src')
2419     mock_input_api.change.RepositoryRoot = FakeRepositoryRoot
2420     self._injectFakeOwnersClient(
2421         mock_input_api,
2422         ['apple@chromium.org', 'orange@chromium.org'])
2423     return mock_input_api
2424
2425   def _setupFakeChange(self, input_api):
2426     class FakeGerrit(object):
2427       def IsOwnersOverrideApproved(self, issue):
2428         return False
2429
2430     input_api.change.issue = 123
2431     input_api.gerrit = FakeGerrit()
2432
2433   def _injectFakeOwnersClient(self, input_api, owners):
2434     class FakeOwnersClient(object):
2435       def ListOwners(self, f):
2436         return owners
2437
2438     input_api.owners_client = FakeOwnersClient()
2439
2440   def _injectFakeChangeOwnerAndReviewers(self, input_api, owner, reviewers):
2441     def MockOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
2442       return [owner, reviewers]
2443     input_api.canned_checks.GetCodereviewOwnerAndReviewers = \
2444         MockOwnerAndReviewers
2445
2446
2447 class IpcSecurityOwnerTest(_SecurityOwnersTestCase):
2448   _test_cases = [
2449       ('*_messages.cc', 'scary_messages.cc'),
2450       ('*_messages*.h', 'scary_messages.h'),
2451       ('*_messages*.h', 'scary_messages_android.h'),
2452       ('*_param_traits*.*', 'scary_param_traits.h'),
2453       ('*_param_traits*.*', 'scary_param_traits_win.h'),
2454       ('*.mojom', 'scary.mojom'),
2455       ('*_mojom_traits*.*', 'scary_mojom_traits.h'),
2456       ('*_mojom_traits*.*', 'scary_mojom_traits_mac.h'),
2457       ('*_type_converter*.*', 'scary_type_converter.h'),
2458       ('*_type_converter*.*', 'scary_type_converter_nacl.h'),
2459       ('*.aidl', 'scary.aidl'),
2460   ]
2461
2462   def testHasCorrectPerFileRulesAndSecurityReviewer(self):
2463     mock_input_api = self._createMockInputApi()
2464     new_owners_file_path = mock_input_api.os_path.join(
2465         'services', 'goat', 'public', 'OWNERS')
2466     new_owners_file = [
2467         'per-file *.mojom=set noparent',
2468         'per-file *.mojom=file://ipc/SECURITY_OWNERS'
2469     ]
2470     def FakeReadFile(filename):
2471       self.assertEqual(
2472           mock_input_api.os_path.join('chromium', 'src', new_owners_file_path),
2473           filename)
2474       return '\n'.join(new_owners_file)
2475     mock_input_api.ReadFile = FakeReadFile
2476     mock_input_api.files = [
2477       MockAffectedFile(
2478           new_owners_file_path, new_owners_file),
2479       MockAffectedFile(
2480           mock_input_api.os_path.join(
2481               'services', 'goat', 'public', 'goat.mojom'),
2482           ['// Scary contents.'])]
2483     self._setupFakeChange(mock_input_api)
2484     self._injectFakeChangeOwnerAndReviewers(
2485         mock_input_api, 'owner@chromium.org', ['orange@chromium.org'])
2486     mock_input_api.is_committing = True
2487     mock_input_api.dry_run = False
2488     mock_output_api = MockOutputApi()
2489     results = PRESUBMIT.CheckSecurityOwners(
2490         mock_input_api, mock_output_api)
2491     self.assertEqual(0, len(results))
2492
2493   def testMissingSecurityReviewerAtUpload(self):
2494     mock_input_api = self._createMockInputApi()
2495     new_owners_file_path = mock_input_api.os_path.join(
2496         'services', 'goat', 'public', 'OWNERS')
2497     new_owners_file = [
2498         'per-file *.mojom=set noparent',
2499         'per-file *.mojom=file://ipc/SECURITY_OWNERS'
2500     ]
2501     def FakeReadFile(filename):
2502       self.assertEqual(
2503           mock_input_api.os_path.join('chromium', 'src', new_owners_file_path),
2504           filename)
2505       return '\n'.join(new_owners_file)
2506     mock_input_api.ReadFile = FakeReadFile
2507     mock_input_api.files = [
2508       MockAffectedFile(
2509           new_owners_file_path, new_owners_file),
2510       MockAffectedFile(
2511           mock_input_api.os_path.join(
2512               'services', 'goat', 'public', 'goat.mojom'),
2513           ['// Scary contents.'])]
2514     self._setupFakeChange(mock_input_api)
2515     self._injectFakeChangeOwnerAndReviewers(
2516         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2517     mock_input_api.is_committing = False
2518     mock_input_api.dry_run = False
2519     mock_output_api = MockOutputApi()
2520     results = PRESUBMIT.CheckSecurityOwners(
2521         mock_input_api, mock_output_api)
2522     self.assertEqual(1, len(results))
2523     self.assertEqual('notify', results[0].type)
2524     self.assertEqual(
2525         'Review from an owner in ipc/SECURITY_OWNERS is required for the '
2526         'following newly-added files:', results[0].message)
2527
2528   def testMissingSecurityReviewerAtDryRunCommit(self):
2529     mock_input_api = self._createMockInputApi()
2530     new_owners_file_path = mock_input_api.os_path.join(
2531         'services', 'goat', 'public', 'OWNERS')
2532     new_owners_file = [
2533         'per-file *.mojom=set noparent',
2534         'per-file *.mojom=file://ipc/SECURITY_OWNERS'
2535     ]
2536     def FakeReadFile(filename):
2537       self.assertEqual(
2538           mock_input_api.os_path.join('chromium', 'src', new_owners_file_path),
2539           filename)
2540       return '\n'.join(new_owners_file)
2541     mock_input_api.ReadFile = FakeReadFile
2542     mock_input_api.files = [
2543       MockAffectedFile(
2544           new_owners_file_path, new_owners_file),
2545       MockAffectedFile(
2546           mock_input_api.os_path.join(
2547               'services', 'goat', 'public', 'goat.mojom'),
2548           ['// Scary contents.'])]
2549     self._setupFakeChange(mock_input_api)
2550     self._injectFakeChangeOwnerAndReviewers(
2551         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2552     mock_input_api.is_committing = True
2553     mock_input_api.dry_run = True
2554     mock_output_api = MockOutputApi()
2555     results = PRESUBMIT.CheckSecurityOwners(
2556         mock_input_api, mock_output_api)
2557     self.assertEqual(1, len(results))
2558     self.assertEqual('error', results[0].type)
2559     self.assertEqual(
2560         'Review from an owner in ipc/SECURITY_OWNERS is required for the '
2561         'following newly-added files:', results[0].message)
2562
2563   def testMissingSecurityApprovalAtRealCommit(self):
2564     mock_input_api = self._createMockInputApi()
2565     new_owners_file_path = mock_input_api.os_path.join(
2566         'services', 'goat', 'public', 'OWNERS')
2567     new_owners_file = [
2568         'per-file *.mojom=set noparent',
2569         'per-file *.mojom=file://ipc/SECURITY_OWNERS'
2570     ]
2571     def FakeReadFile(filename):
2572       self.assertEqual(
2573           mock_input_api.os_path.join('chromium', 'src', new_owners_file_path),
2574           filename)
2575       return '\n'.join(new_owners_file)
2576     mock_input_api.ReadFile = FakeReadFile
2577     mock_input_api.files = [
2578       MockAffectedFile(
2579           new_owners_file_path, new_owners_file),
2580       MockAffectedFile(
2581           mock_input_api.os_path.join(
2582               'services', 'goat', 'public', 'goat.mojom'),
2583           ['// Scary contents.'])]
2584     self._setupFakeChange(mock_input_api)
2585     self._injectFakeChangeOwnerAndReviewers(
2586         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2587     mock_input_api.is_committing = True
2588     mock_input_api.dry_run = False
2589     mock_output_api = MockOutputApi()
2590     results = PRESUBMIT.CheckSecurityOwners(
2591         mock_input_api, mock_output_api)
2592     self.assertEqual('error', results[0].type)
2593     self.assertEqual(
2594         'Review from an owner in ipc/SECURITY_OWNERS is required for the '
2595         'following newly-added files:', results[0].message)
2596
2597   def testIpcChangeNeedsSecurityOwner(self):
2598     for is_committing in [True, False]:
2599       for pattern, filename in self._test_cases:
2600         with self.subTest(
2601             line=f'is_committing={is_committing}, filename={filename}'):
2602           mock_input_api = self._createMockInputApi()
2603           mock_input_api.files = [
2604             MockAffectedFile(
2605                 mock_input_api.os_path.join(
2606                     'services', 'goat', 'public', filename),
2607                 ['// Scary contents.'])]
2608           self._setupFakeChange(mock_input_api)
2609           self._injectFakeChangeOwnerAndReviewers(
2610               mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2611           mock_input_api.is_committing = is_committing
2612           mock_input_api.dry_run = False
2613           mock_output_api = MockOutputApi()
2614           results = PRESUBMIT.CheckSecurityOwners(
2615               mock_input_api, mock_output_api)
2616           self.assertEqual(1, len(results))
2617           self.assertEqual('error', results[0].type)
2618           self.assertTrue(results[0].message.replace('\\', '/').startswith(
2619               'Found missing OWNERS lines for security-sensitive files. '
2620               'Please add the following lines to services/goat/public/OWNERS:'))
2621           self.assertEqual(['ipc-security-reviews@chromium.org'],
2622                            mock_output_api.more_cc)
2623
2624
2625   def testServiceManifestChangeNeedsSecurityOwner(self):
2626     mock_input_api = self._createMockInputApi()
2627     mock_input_api.files = [
2628       MockAffectedFile(
2629           mock_input_api.os_path.join(
2630               'services', 'goat', 'public', 'cpp', 'manifest.cc'),
2631               [
2632                 '#include "services/goat/public/cpp/manifest.h"',
2633                 'const service_manager::Manifest& GetManifest() {}',
2634               ])]
2635     self._setupFakeChange(mock_input_api)
2636     self._injectFakeChangeOwnerAndReviewers(
2637         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2638     mock_output_api = MockOutputApi()
2639     errors = PRESUBMIT.CheckSecurityOwners(
2640         mock_input_api, mock_output_api)
2641     self.assertEqual(1, len(errors))
2642     self.assertTrue(errors[0].message.replace('\\', '/').startswith(
2643         'Found missing OWNERS lines for security-sensitive files. '
2644         'Please add the following lines to services/goat/public/cpp/OWNERS:'))
2645     self.assertEqual(['ipc-security-reviews@chromium.org'], mock_output_api.more_cc)
2646
2647   def testNonServiceManifestSourceChangesDoNotRequireSecurityOwner(self):
2648     mock_input_api = self._createMockInputApi()
2649     self._injectFakeChangeOwnerAndReviewers(
2650         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2651     mock_input_api.files = [
2652       MockAffectedFile('some/non/service/thing/foo_manifest.cc',
2653                        [
2654                          'const char kNoEnforcement[] = "not a manifest!";',
2655                        ])]
2656     mock_output_api = MockOutputApi()
2657     errors = PRESUBMIT.CheckSecurityOwners(
2658         mock_input_api, mock_output_api)
2659     self.assertEqual([], errors)
2660     self.assertEqual([], mock_output_api.more_cc)
2661
2662
2663 class FuchsiaSecurityOwnerTest(_SecurityOwnersTestCase):
2664   def testFidlChangeNeedsSecurityOwner(self):
2665     mock_input_api = self._createMockInputApi()
2666     mock_input_api.files = [
2667       MockAffectedFile('potentially/scary/ipc.fidl',
2668                        [
2669                          'library test.fidl'
2670                        ])]
2671     self._setupFakeChange(mock_input_api)
2672     self._injectFakeChangeOwnerAndReviewers(
2673         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2674     mock_output_api = MockOutputApi()
2675     errors = PRESUBMIT.CheckSecurityOwners(
2676         mock_input_api, mock_output_api)
2677     self.assertEqual(1, len(errors))
2678     self.assertTrue(errors[0].message.replace('\\', '/').startswith(
2679         'Found missing OWNERS lines for security-sensitive files. '
2680         'Please add the following lines to potentially/scary/OWNERS:'))
2681
2682   def testComponentManifestV1ChangeNeedsSecurityOwner(self):
2683     mock_input_api = self._createMockInputApi()
2684     mock_input_api.files = [
2685       MockAffectedFile('potentially/scary/v2_manifest.cmx',
2686                        [
2687                          '{ "that is no": "manifest!" }'
2688                        ])]
2689     self._setupFakeChange(mock_input_api)
2690     self._injectFakeChangeOwnerAndReviewers(
2691         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2692     mock_output_api = MockOutputApi()
2693     errors = PRESUBMIT.CheckSecurityOwners(
2694         mock_input_api, mock_output_api)
2695     self.assertEqual(1, len(errors))
2696     self.assertTrue(errors[0].message.replace('\\', '/').startswith(
2697         'Found missing OWNERS lines for security-sensitive files. '
2698         'Please add the following lines to potentially/scary/OWNERS:'))
2699
2700   def testComponentManifestV2NeedsSecurityOwner(self):
2701     mock_input_api = self._createMockInputApi()
2702     mock_input_api.files = [
2703       MockAffectedFile('potentially/scary/v2_manifest.cml',
2704                        [
2705                          '{ "that is no": "manifest!" }'
2706                        ])]
2707     self._setupFakeChange(mock_input_api)
2708     self._injectFakeChangeOwnerAndReviewers(
2709         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2710     mock_output_api = MockOutputApi()
2711     errors = PRESUBMIT.CheckSecurityOwners(
2712         mock_input_api, mock_output_api)
2713     self.assertEqual(1, len(errors))
2714     self.assertTrue(errors[0].message.replace('\\', '/').startswith(
2715         'Found missing OWNERS lines for security-sensitive files. '
2716         'Please add the following lines to potentially/scary/OWNERS:'))
2717
2718   def testThirdPartyTestsDoNotRequireSecurityOwner(self):
2719     mock_input_api = MockInputApi()
2720     self._injectFakeChangeOwnerAndReviewers(
2721         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2722     mock_input_api.files = [
2723       MockAffectedFile('third_party/crashpad/test/tests.cmx',
2724                        [
2725                          'const char kNoEnforcement[] = "Security?!? Pah!";',
2726                        ])]
2727     mock_output_api = MockOutputApi()
2728     errors = PRESUBMIT.CheckSecurityOwners(
2729         mock_input_api, mock_output_api)
2730     self.assertEqual([], errors)
2731
2732   def testOtherFuchsiaChangesDoNotRequireSecurityOwner(self):
2733     mock_input_api = MockInputApi()
2734     self._injectFakeChangeOwnerAndReviewers(
2735         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2736     mock_input_api.files = [
2737       MockAffectedFile('some/non/service/thing/fuchsia_fidl_cml_cmx_magic.cc',
2738                        [
2739                          'const char kNoEnforcement[] = "Security?!? Pah!";',
2740                        ])]
2741     mock_output_api = MockOutputApi()
2742     errors = PRESUBMIT.CheckSecurityOwners(
2743         mock_input_api, mock_output_api)
2744     self.assertEqual([], errors)
2745
2746
2747 class SecurityChangeTest(_SecurityOwnersTestCase):
2748   def testDiffGetServiceSandboxType(self):
2749     mock_input_api = MockInputApi()
2750     mock_input_api.files = [
2751         MockAffectedFile(
2752           'services/goat/teleporter_host.cc',
2753           [
2754             'template <>',
2755             'inline content::SandboxType',
2756             'content::GetServiceSandboxType<chrome::mojom::GoatTeleporter>() {',
2757             '#if defined(OS_WIN)',
2758             '  return SandboxType::kGoaty;',
2759             '#else',
2760             '  return SandboxType::kNoSandbox;',
2761             '#endif  // !defined(OS_WIN)',
2762             '}'
2763           ]
2764         ),
2765     ]
2766     files_to_functions = PRESUBMIT._GetFilesUsingSecurityCriticalFunctions(
2767         mock_input_api)
2768     self.assertEqual({
2769         'services/goat/teleporter_host.cc': set([
2770             'content::GetServiceSandboxType<>()'
2771         ])},
2772         files_to_functions)
2773
2774   def testDiffRemovingLine(self):
2775     mock_input_api = MockInputApi()
2776     mock_file = MockAffectedFile('services/goat/teleporter_host.cc', '')
2777     mock_file._scm_diff = """--- old 2020-05-04 14:08:25.000000000 -0400
2778 +++ new 2020-05-04 14:08:32.000000000 -0400
2779 @@ -1,5 +1,4 @@
2780  template <>
2781  inline content::SandboxType
2782 -content::GetServiceSandboxType<chrome::mojom::GoatTeleporter>() {
2783  #if defined(OS_WIN)
2784    return SandboxType::kGoaty;
2785 """
2786     mock_input_api.files = [mock_file]
2787     files_to_functions = PRESUBMIT._GetFilesUsingSecurityCriticalFunctions(
2788         mock_input_api)
2789     self.assertEqual({
2790         'services/goat/teleporter_host.cc': set([
2791             'content::GetServiceSandboxType<>()'
2792         ])},
2793         files_to_functions)
2794
2795   def testChangeOwnersMissing(self):
2796     mock_input_api = self._createMockInputApi()
2797     self._setupFakeChange(mock_input_api)
2798     self._injectFakeChangeOwnerAndReviewers(
2799         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2800     mock_input_api.is_committing = False
2801     mock_input_api.files = [
2802         MockAffectedFile('file.cc', ['GetServiceSandboxType<Goat>(Sandbox)'])
2803     ]
2804     mock_output_api = MockOutputApi()
2805     result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2806     self.assertEqual(1, len(result))
2807     self.assertEqual(result[0].type, 'notify')
2808     self.assertEqual(result[0].message,
2809         'The following files change calls to security-sensitive functions\n' \
2810         'that need to be reviewed by ipc/SECURITY_OWNERS.\n'
2811         '  file.cc\n'
2812         '    content::GetServiceSandboxType<>()\n\n')
2813
2814   def testChangeOwnersMissingAtCommit(self):
2815     mock_input_api = self._createMockInputApi()
2816     self._setupFakeChange(mock_input_api)
2817     self._injectFakeChangeOwnerAndReviewers(
2818         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2819     mock_input_api.is_committing = True
2820     mock_input_api.dry_run = False
2821     mock_input_api.files = [
2822         MockAffectedFile('file.cc', ['GetServiceSandboxType<mojom::Goat>()'])
2823     ]
2824     mock_output_api = MockOutputApi()
2825     result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2826     self.assertEqual(1, len(result))
2827     self.assertEqual(result[0].type, 'error')
2828     self.assertEqual(result[0].message,
2829         'The following files change calls to security-sensitive functions\n' \
2830         'that need to be reviewed by ipc/SECURITY_OWNERS.\n'
2831         '  file.cc\n'
2832         '    content::GetServiceSandboxType<>()\n\n')
2833
2834   def testChangeOwnersPresent(self):
2835     mock_input_api = self._createMockInputApi()
2836     self._injectFakeChangeOwnerAndReviewers(
2837         mock_input_api, 'owner@chromium.org',
2838         ['apple@chromium.org', 'banana@chromium.org'])
2839     mock_input_api.files = [
2840         MockAffectedFile('file.cc', ['WithSandboxType(Sandbox)'])
2841     ]
2842     mock_output_api = MockOutputApi()
2843     result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2844     self.assertEqual(0, len(result))
2845
2846   def testChangeOwnerIsSecurityOwner(self):
2847     mock_input_api = self._createMockInputApi()
2848     self._setupFakeChange(mock_input_api)
2849     self._injectFakeChangeOwnerAndReviewers(
2850         mock_input_api, 'orange@chromium.org', ['pear@chromium.org'])
2851     mock_input_api.files = [
2852         MockAffectedFile('file.cc', ['GetServiceSandboxType<T>(Sandbox)'])
2853     ]
2854     mock_output_api = MockOutputApi()
2855     result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2856     self.assertEqual(1, len(result))
2857
2858
2859 class BannedTypeCheckTest(unittest.TestCase):
2860   def testBannedJsFunctions(self):
2861     input_api = MockInputApi()
2862     input_api.files = [
2863       MockFile('ash/webui/file.js',
2864                 ['chrome.send(something);']),
2865       MockFile('some/js/ok/file.js',
2866                 ['chrome.send(something);']),
2867     ]
2868
2869     results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2870
2871     self.assertEqual(1, len(results))
2872     self.assertTrue('ash/webui/file.js' in results[0].message)
2873     self.assertFalse('some/js/ok/file.js' in results[0].message)
2874
2875   def testBannedJavaFunctions(self):
2876     input_api = MockInputApi()
2877     input_api.files = [
2878       MockFile('some/java/problematic/diskread.java',
2879                ['StrictMode.allowThreadDiskReads();']),
2880       MockFile('some/java/problematic/diskwrite.java',
2881                ['StrictMode.allowThreadDiskWrites();']),
2882       MockFile('some/java/ok/diskwrite.java',
2883                ['StrictModeContext.allowDiskWrites();']),
2884       MockFile('some/java/problematic/waitidleforsync.java',
2885                ['instrumentation.waitForIdleSync();']),
2886       MockFile('some/java/problematic/registerreceiver.java',
2887                ['context.registerReceiver();']),
2888       MockFile('some/java/problematic/property.java',
2889                ['new Property<abc, Integer>;']),
2890       MockFile('some/java/problematic/requestlayout.java',
2891                ['requestLayout();']),
2892       MockFile('some/java/problematic/lastprofile.java',
2893                ['Profile.getLastUsedRegularProfile();']),
2894       MockFile('some/java/problematic/getdrawable1.java',
2895                ['ResourcesCompat.getDrawable();']),
2896       MockFile('some/java/problematic/getdrawable2.java',
2897                ['getResources().getDrawable();']),
2898     ]
2899
2900     errors = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2901     self.assertEqual(2, len(errors))
2902     self.assertTrue('some/java/problematic/diskread.java' in errors[0].message)
2903     self.assertTrue('some/java/problematic/diskwrite.java' in errors[0].message)
2904     self.assertFalse('some/java/ok/diskwrite.java' in errors[0].message)
2905     self.assertFalse('some/java/ok/diskwrite.java' in errors[1].message)
2906     self.assertTrue('some/java/problematic/waitidleforsync.java' in errors[0].message)
2907     self.assertTrue('some/java/problematic/registerreceiver.java' in errors[1].message)
2908     self.assertTrue('some/java/problematic/property.java' in errors[0].message)
2909     self.assertTrue('some/java/problematic/requestlayout.java' in errors[0].message)
2910     self.assertTrue('some/java/problematic/lastprofile.java' in errors[0].message)
2911     self.assertTrue('some/java/problematic/getdrawable1.java' in errors[0].message)
2912     self.assertTrue('some/java/problematic/getdrawable2.java' in errors[0].message)
2913
2914   def testBannedCppFunctions(self):
2915     input_api = MockInputApi()
2916     input_api.files = [
2917       MockFile('some/cpp/problematic/file.cc',
2918                ['using namespace std;']),
2919       MockFile('third_party/blink/problematic/file.cc',
2920                ['GetInterfaceProvider()']),
2921       MockFile('some/cpp/ok/file.cc',
2922                ['using std::string;']),
2923       MockFile('some/cpp/problematic/file2.cc',
2924                ['set_owned_by_client()']),
2925       MockFile('some/cpp/nocheck/file.cc',
2926                ['using namespace std;  // nocheck']),
2927       MockFile('some/cpp/comment/file.cc',
2928                ['  // A comment about `using namespace std;`']),
2929     ]
2930
2931     results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2932
2933      # warnings are results[0], errors are results[1]
2934     self.assertEqual(2, len(results))
2935     self.assertTrue('some/cpp/problematic/file.cc' in results[1].message)
2936     self.assertTrue(
2937         'third_party/blink/problematic/file.cc' in results[0].message)
2938     self.assertTrue('some/cpp/ok/file.cc' not in results[1].message)
2939     self.assertTrue('some/cpp/problematic/file2.cc' in results[0].message)
2940     self.assertFalse('some/cpp/nocheck/file.cc' in results[0].message)
2941     self.assertFalse('some/cpp/nocheck/file.cc' in results[1].message)
2942     self.assertFalse('some/cpp/comment/file.cc' in results[0].message)
2943     self.assertFalse('some/cpp/comment/file.cc' in results[1].message)
2944
2945   def testBannedCppRandomFunctions(self):
2946     banned_rngs = [
2947         'absl::BitGen',
2948         'absl::InsecureBitGen',
2949         'std::linear_congruential_engine',
2950         'std::mersenne_twister_engine',
2951         'std::subtract_with_carry_engine',
2952         'std::discard_block_engine',
2953         'std::independent_bits_engine',
2954         'std::shuffle_order_engine',
2955         'std::minstd_rand0',
2956         'std::minstd_rand',
2957         'std::mt19937',
2958         'std::mt19937_64',
2959         'std::ranlux24_base',
2960         'std::ranlux48_base',
2961         'std::ranlux24',
2962         'std::ranlux48',
2963         'std::knuth_b',
2964         'std::default_random_engine',
2965         'std::random_device',
2966     ]
2967     for banned_rng in banned_rngs:
2968       input_api = MockInputApi()
2969       input_api.files = [
2970         MockFile('some/cpp/problematic/file.cc',
2971                  [f'{banned_rng} engine;']),
2972         MockFile('third_party/blink/problematic/file.cc',
2973                  [f'{banned_rng} engine;']),
2974         MockFile('third_party/ok/file.cc',
2975                  [f'{banned_rng} engine;']),
2976       ]
2977       results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2978       self.assertEqual(1, len(results), banned_rng)
2979       self.assertTrue('some/cpp/problematic/file.cc' in results[0].message,
2980                       banned_rng)
2981       self.assertTrue(
2982           'third_party/blink/problematic/file.cc' in results[0].message,
2983           banned_rng)
2984       self.assertFalse(
2985           'third_party/ok/file.cc' in results[0].message, banned_rng)
2986
2987   def testBannedIosObjcFunctions(self):
2988     input_api = MockInputApi()
2989     input_api.files = [
2990       MockFile('some/ios/file.mm',
2991                ['TEST(SomeClassTest, SomeInteraction) {',
2992                 '}']),
2993       MockFile('some/mac/file.mm',
2994                ['TEST(SomeClassTest, SomeInteraction) {',
2995                 '}']),
2996       MockFile('another/ios_file.mm',
2997                ['class SomeTest : public testing::Test {};']),
2998       MockFile('some/ios/file_egtest.mm',
2999                ['- (void)testSomething { EXPECT_OCMOCK_VERIFY(aMock); }']),
3000       MockFile('some/ios/file_unittest.mm',
3001                ['TEST_F(SomeTest, TestThis) { EXPECT_OCMOCK_VERIFY(aMock); }']),
3002     ]
3003
3004     errors = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
3005     self.assertEqual(1, len(errors))
3006     self.assertTrue('some/ios/file.mm' in errors[0].message)
3007     self.assertTrue('another/ios_file.mm' in errors[0].message)
3008     self.assertTrue('some/mac/file.mm' not in errors[0].message)
3009     self.assertTrue('some/ios/file_egtest.mm' in errors[0].message)
3010     self.assertTrue('some/ios/file_unittest.mm' not in errors[0].message)
3011
3012   def testBannedMojoFunctions(self):
3013     input_api = MockInputApi()
3014     input_api.files = [
3015       MockFile('some/cpp/problematic/file2.cc',
3016                ['mojo::ConvertTo<>']),
3017       MockFile('third_party/blink/ok/file3.cc',
3018                ['mojo::ConvertTo<>']),
3019       MockFile('content/renderer/ok/file3.cc',
3020                ['mojo::ConvertTo<>']),
3021     ]
3022
3023     results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
3024
3025     # warnings are results[0], errors are results[1]
3026     self.assertEqual(1, len(results))
3027     self.assertTrue('some/cpp/problematic/file2.cc' in results[0].message)
3028     self.assertTrue('third_party/blink/ok/file3.cc' not in results[0].message)
3029     self.assertTrue('content/renderer/ok/file3.cc' not in results[0].message)
3030
3031   def testBannedMojomPatterns(self):
3032     input_api = MockInputApi()
3033     input_api.files = [
3034       MockFile('bad.mojom',
3035                ['struct Bad {',
3036                 '  handle<shared_buffer> buffer;',
3037                 '};']),
3038       MockFile('good.mojom',
3039                ['struct  Good {',
3040                 '  mojo_base.mojom.ReadOnlySharedMemoryRegion region1;',
3041                 '  mojo_base.mojom.WritableSharedMemoryRegion region2;',
3042                 '  mojo_base.mojom.UnsafeSharedMemoryRegion region3;',
3043                 '};']),
3044     ]
3045
3046     results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
3047
3048     # warnings are results[0], errors are results[1]
3049     self.assertEqual(1, len(results))
3050     self.assertTrue('bad.mojom' in results[0].message)
3051     self.assertTrue('good.mojom' not in results[0].message)
3052
3053 class NoProductionCodeUsingTestOnlyFunctionsTest(unittest.TestCase):
3054   def testTruePositives(self):
3055     mock_input_api = MockInputApi()
3056     mock_input_api.files = [
3057       MockFile('some/path/foo.cc', ['foo_for_testing();']),
3058       MockFile('some/path/foo.mm', ['FooForTesting();']),
3059       MockFile('some/path/foo.cxx', ['FooForTests();']),
3060       MockFile('some/path/foo.cpp', ['foo_for_test();']),
3061     ]
3062
3063     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctions(
3064         mock_input_api, MockOutputApi())
3065     self.assertEqual(1, len(results))
3066     self.assertEqual(4, len(results[0].items))
3067     self.assertTrue('foo.cc' in results[0].items[0])
3068     self.assertTrue('foo.mm' in results[0].items[1])
3069     self.assertTrue('foo.cxx' in results[0].items[2])
3070     self.assertTrue('foo.cpp' in results[0].items[3])
3071
3072   def testFalsePositives(self):
3073     mock_input_api = MockInputApi()
3074     mock_input_api.files = [
3075       MockFile('some/path/foo.h', ['foo_for_testing();']),
3076       MockFile('some/path/foo.mm', ['FooForTesting() {']),
3077       MockFile('some/path/foo.cc', ['::FooForTests();']),
3078       MockFile('some/path/foo.cpp', ['// foo_for_test();']),
3079       MockFile('some/path/foo.cxx', ['foo_for_test(); // IN-TEST']),
3080     ]
3081
3082     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctions(
3083         mock_input_api, MockOutputApi())
3084     self.assertEqual(0, len(results))
3085
3086   def testAllowedFiles(self):
3087     mock_input_api = MockInputApi()
3088     mock_input_api.files = [
3089       MockFile('path/foo_unittest.cc', ['foo_for_testing();']),
3090       MockFile('path/bar_unittest_mac.cc', ['foo_for_testing();']),
3091       MockFile('path/baz_unittests.cc', ['foo_for_testing();']),
3092     ]
3093
3094     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctions(
3095         mock_input_api, MockOutputApi())
3096     self.assertEqual(0, len(results))
3097
3098
3099 class NoProductionJavaCodeUsingTestOnlyFunctionsTest(unittest.TestCase):
3100   def testTruePositives(self):
3101     mock_input_api = MockInputApi()
3102     mock_input_api.files = [
3103       MockFile('dir/java/src/foo.java', ['FooForTesting();']),
3104       MockFile('dir/java/src/bar.java', ['FooForTests(x);']),
3105       MockFile('dir/java/src/baz.java', ['FooForTest(', 'y', ');']),
3106       MockFile('dir/java/src/mult.java', [
3107         'int x = SomethingLongHere()',
3108         '    * SomethingLongHereForTesting();'
3109       ])
3110     ]
3111
3112     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctionsJava(
3113         mock_input_api, MockOutputApi())
3114     self.assertEqual(1, len(results))
3115     self.assertEqual(4, len(results[0].items))
3116     self.assertTrue('foo.java' in results[0].items[0])
3117     self.assertTrue('bar.java' in results[0].items[1])
3118     self.assertTrue('baz.java' in results[0].items[2])
3119     self.assertTrue('mult.java' in results[0].items[3])
3120
3121   def testFalsePositives(self):
3122     mock_input_api = MockInputApi()
3123     mock_input_api.files = [
3124       MockFile('dir/java/src/foo.xml', ['FooForTesting();']),
3125       MockFile('dir/java/src/foo.java', ['FooForTests() {']),
3126       MockFile('dir/java/src/bar.java', ['// FooForTest();']),
3127       MockFile('dir/java/src/bar2.java', ['x = 1; // FooForTest();']),
3128       MockFile('dir/java/src/bar3.java', ['@VisibleForTesting']),
3129       MockFile('dir/java/src/bar4.java', ['@VisibleForTesting()']),
3130       MockFile('dir/java/src/bar5.java', [
3131         '@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)'
3132       ]),
3133       MockFile('dir/javatests/src/baz.java', ['FooForTest(', 'y', ');']),
3134       MockFile('dir/junit/src/baz.java', ['FooForTest(', 'y', ');']),
3135       MockFile('dir/junit/src/javadoc.java', [
3136         '/** Use FooForTest(); to obtain foo in tests.'
3137         ' */'
3138       ]),
3139       MockFile('dir/junit/src/javadoc2.java', [
3140         '/** ',
3141         ' * Use FooForTest(); to obtain foo in tests.'
3142         ' */'
3143       ]),
3144       MockFile('dir/java/src/bar6.java', ['FooForTesting(); // IN-TEST']),
3145     ]
3146
3147     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctionsJava(
3148         mock_input_api, MockOutputApi())
3149     self.assertEqual(0, len(results))
3150
3151
3152 class NewImagesWarningTest(unittest.TestCase):
3153   def testTruePositives(self):
3154     mock_input_api = MockInputApi()
3155     mock_input_api.files = [
3156       MockFile('dir/android/res/drawable/foo.png', []),
3157       MockFile('dir/android/res/drawable-v21/bar.svg', []),
3158       MockFile('dir/android/res/mipmap-v21-en/baz.webp', []),
3159       MockFile('dir/android/res_gshoe/drawable-mdpi/foobar.png', []),
3160     ]
3161
3162     results = PRESUBMIT._CheckNewImagesWarning(mock_input_api, MockOutputApi())
3163     self.assertEqual(1, len(results))
3164     self.assertEqual(4, len(results[0].items))
3165     self.assertTrue('foo.png' in results[0].items[0].LocalPath())
3166     self.assertTrue('bar.svg' in results[0].items[1].LocalPath())
3167     self.assertTrue('baz.webp' in results[0].items[2].LocalPath())
3168     self.assertTrue('foobar.png' in results[0].items[3].LocalPath())
3169
3170   def testFalsePositives(self):
3171     mock_input_api = MockInputApi()
3172     mock_input_api.files = [
3173       MockFile('dir/pngs/README.md', []),
3174       MockFile('java/test/res/drawable/foo.png', []),
3175       MockFile('third_party/blink/foo.png', []),
3176       MockFile('dir/third_party/libpng/src/foo.cc', ['foobar']),
3177       MockFile('dir/resources.webp/.gitignore', ['foo.png']),
3178     ]
3179
3180     results = PRESUBMIT._CheckNewImagesWarning(mock_input_api, MockOutputApi())
3181     self.assertEqual(0, len(results))
3182
3183 class ProductIconsTest(unittest.TestCase):
3184   def test(self):
3185     mock_input_api = MockInputApi()
3186     mock_input_api.files = [
3187       MockFile('components/vector_icons/google_jetpack.icon', []),
3188       MockFile('components/vector_icons/generic_jetpack.icon', []),
3189     ]
3190
3191     results = PRESUBMIT.CheckNoProductIconsAddedToPublicRepo(mock_input_api, MockOutputApi())
3192     self.assertEqual(1, len(results))
3193     self.assertEqual(1, len(results[0].items))
3194     self.assertTrue('google_jetpack.icon' in results[0].items[0])
3195
3196 class CheckUniquePtrTest(unittest.TestCase):
3197   def testTruePositivesNullptr(self):
3198     mock_input_api = MockInputApi()
3199     mock_input_api.files = [
3200       MockFile('dir/baz.cc', ['std::unique_ptr<T>()']),
3201       MockFile('dir/baz-p.cc', ['std::unique_ptr<T<P>>()']),
3202     ]
3203
3204     results = PRESUBMIT.CheckUniquePtrOnUpload(mock_input_api, MockOutputApi())
3205     self.assertEqual(1, len(results))
3206     self.assertTrue('nullptr' in results[0].message)
3207     self.assertEqual(2, len(results[0].items))
3208     self.assertTrue('baz.cc' in results[0].items[0])
3209     self.assertTrue('baz-p.cc' in results[0].items[1])
3210
3211   def testTruePositivesConstructor(self):
3212     mock_input_api = MockInputApi()
3213     mock_input_api.files = [
3214       MockFile('dir/foo.cc', ['return std::unique_ptr<T>(foo);']),
3215       MockFile('dir/bar.mm', ['bar = std::unique_ptr<T>(foo)']),
3216       MockFile('dir/mult.cc', [
3217         'return',
3218         '    std::unique_ptr<T>(barVeryVeryLongFooSoThatItWouldNotFitAbove);'
3219       ]),
3220       MockFile('dir/mult2.cc', [
3221         'barVeryVeryLongLongBaaaaaarSoThatTheLineLimitIsAlmostReached =',
3222         '    std::unique_ptr<T>(foo);'
3223       ]),
3224       MockFile('dir/mult3.cc', [
3225         'bar = std::unique_ptr<T>(',
3226         '    fooVeryVeryVeryLongStillGoingWellThisWillTakeAWhileFinallyThere);'
3227       ]),
3228       MockFile('dir/multi_arg.cc', [
3229           'auto p = std::unique_ptr<std::pair<T, D>>(new std::pair(T, D));']),
3230     ]
3231
3232     results = PRESUBMIT.CheckUniquePtrOnUpload(mock_input_api, MockOutputApi())
3233     self.assertEqual(1, len(results))
3234     self.assertTrue('std::make_unique' in results[0].message)
3235     self.assertEqual(6, len(results[0].items))
3236     self.assertTrue('foo.cc' in results[0].items[0])
3237     self.assertTrue('bar.mm' in results[0].items[1])
3238     self.assertTrue('mult.cc' in results[0].items[2])
3239     self.assertTrue('mult2.cc' in results[0].items[3])
3240     self.assertTrue('mult3.cc' in results[0].items[4])
3241     self.assertTrue('multi_arg.cc' in results[0].items[5])
3242
3243   def testFalsePositives(self):
3244     mock_input_api = MockInputApi()
3245     mock_input_api.files = [
3246       MockFile('dir/foo.cc', ['return std::unique_ptr<T[]>(foo);']),
3247       MockFile('dir/bar.mm', ['bar = std::unique_ptr<T[]>(foo)']),
3248       MockFile('dir/file.cc', ['std::unique_ptr<T> p = Foo();']),
3249       MockFile('dir/baz.cc', [
3250         'std::unique_ptr<T> result = std::make_unique<T>();'
3251       ]),
3252       MockFile('dir/baz2.cc', [
3253         'std::unique_ptr<T> result = std::make_unique<T>('
3254       ]),
3255       MockFile('dir/nested.cc', ['set<std::unique_ptr<T>>();']),
3256       MockFile('dir/nested2.cc', ['map<U, std::unique_ptr<T>>();']),
3257
3258       # Two-argument invocation of std::unique_ptr is exempt because there is
3259       # no equivalent using std::make_unique.
3260       MockFile('dir/multi_arg.cc', [
3261         'auto p = std::unique_ptr<T, D>(new T(), D());']),
3262     ]
3263
3264     results = PRESUBMIT.CheckUniquePtrOnUpload(mock_input_api, MockOutputApi())
3265     self.assertEqual(0, len(results))
3266
3267 class CheckNoDirectIncludesHeadersWhichRedefineStrCat(unittest.TestCase):
3268   def testBlocksDirectIncludes(self):
3269     mock_input_api = MockInputApi()
3270     mock_input_api.files = [
3271       MockFile('dir/foo_win.cc', ['#include "shlwapi.h"']),
3272       MockFile('dir/bar.h', ['#include <propvarutil.h>']),
3273       MockFile('dir/baz.h', ['#include <atlbase.h>']),
3274       MockFile('dir/jumbo.h', ['#include "sphelper.h"']),
3275     ]
3276     results = PRESUBMIT.CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
3277     self.assertEqual(1, len(results))
3278     self.assertEqual(4, len(results[0].items))
3279     self.assertTrue('StrCat' in results[0].message)
3280     self.assertTrue('foo_win.cc' in results[0].items[0])
3281     self.assertTrue('bar.h' in results[0].items[1])
3282     self.assertTrue('baz.h' in results[0].items[2])
3283     self.assertTrue('jumbo.h' in results[0].items[3])
3284
3285   def testAllowsToIncludeWrapper(self):
3286     mock_input_api = MockInputApi()
3287     mock_input_api.files = [
3288       MockFile('dir/baz_win.cc', ['#include "base/win/shlwapi.h"']),
3289       MockFile('dir/baz-win.h', ['#include "base/win/atl.h"']),
3290     ]
3291     results = PRESUBMIT.CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
3292     self.assertEqual(0, len(results))
3293
3294   def testAllowsToCreateWrapper(self):
3295     mock_input_api = MockInputApi()
3296     mock_input_api.files = [
3297       MockFile('base/win/shlwapi.h', [
3298         '#include <shlwapi.h>',
3299         '#include "base/win/windows_defines.inc"']),
3300     ]
3301     results = PRESUBMIT.CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
3302     self.assertEqual(0, len(results))
3303
3304   def testIgnoresNonImplAndHeaders(self):
3305     mock_input_api = MockInputApi()
3306     mock_input_api.files = [
3307       MockFile('dir/foo_win.txt', ['#include "shlwapi.h"']),
3308       MockFile('dir/bar.asm', ['#include <propvarutil.h>']),
3309     ]
3310     results = PRESUBMIT.CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
3311     self.assertEqual(0, len(results))
3312
3313
3314 class StringTest(unittest.TestCase):
3315   """Tests ICU syntax check and translation screenshots check."""
3316
3317   # An empty grd file.
3318   OLD_GRD_CONTENTS = """<?xml version="1.0" encoding="UTF-8"?>
3319            <grit latest_public_release="1" current_release="1">
3320              <release seq="1">
3321                <messages></messages>
3322              </release>
3323            </grit>
3324         """.splitlines()
3325   # A grd file with a single message.
3326   NEW_GRD_CONTENTS1 = """<?xml version="1.0" encoding="UTF-8"?>
3327            <grit latest_public_release="1" current_release="1">
3328              <release seq="1">
3329                <messages>
3330                  <message name="IDS_TEST1">
3331                    Test string 1
3332                  </message>
3333                  <message name="IDS_TEST_STRING_NON_TRANSLATEABLE1"
3334                      translateable="false">
3335                    Non translateable message 1, should be ignored
3336                  </message>
3337                  <message name="IDS_TEST_STRING_ACCESSIBILITY"
3338                      is_accessibility_with_no_ui="true">
3339                    Accessibility label 1, should be ignored
3340                  </message>
3341                </messages>
3342              </release>
3343            </grit>
3344         """.splitlines()
3345   # A grd file with two messages.
3346   NEW_GRD_CONTENTS2 = """<?xml version="1.0" encoding="UTF-8"?>
3347            <grit latest_public_release="1" current_release="1">
3348              <release seq="1">
3349                <messages>
3350                  <message name="IDS_TEST1">
3351                    Test string 1
3352                  </message>
3353                  <message name="IDS_TEST2">
3354                    Test string 2
3355                  </message>
3356                  <message name="IDS_TEST_STRING_NON_TRANSLATEABLE2"
3357                      translateable="false">
3358                    Non translateable message 2, should be ignored
3359                  </message>
3360                </messages>
3361              </release>
3362            </grit>
3363         """.splitlines()
3364   # A grd file with one ICU syntax message without syntax errors.
3365   NEW_GRD_CONTENTS_ICU_SYNTAX_OK1 = """<?xml version="1.0" encoding="UTF-8"?>
3366            <grit latest_public_release="1" current_release="1">
3367              <release seq="1">
3368                <messages>
3369                  <message name="IDS_TEST1">
3370                    {NUM, plural,
3371                     =1 {Test text for numeric one}
3372                     other {Test text for plural with {NUM} as number}}
3373                  </message>
3374                </messages>
3375              </release>
3376            </grit>
3377         """.splitlines()
3378   # A grd file with one ICU syntax message without syntax errors.
3379   NEW_GRD_CONTENTS_ICU_SYNTAX_OK2 = """<?xml version="1.0" encoding="UTF-8"?>
3380            <grit latest_public_release="1" current_release="1">
3381              <release seq="1">
3382                <messages>
3383                  <message name="IDS_TEST1">
3384                    {NUM, plural,
3385                     =1 {Different test text for numeric one}
3386                     other {Different test text for plural with {NUM} as number}}
3387                  </message>
3388                </messages>
3389              </release>
3390            </grit>
3391         """.splitlines()
3392   # A grd file with one ICU syntax message with syntax errors (misses a comma).
3393   NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR = """<?xml version="1.0" encoding="UTF-8"?>
3394            <grit latest_public_release="1" current_release="1">
3395              <release seq="1">
3396                <messages>
3397                  <message name="IDS_TEST1">
3398                    {NUM, plural
3399                     =1 {Test text for numeric one}
3400                     other {Test text for plural with {NUM} as number}}
3401                  </message>
3402                </messages>
3403              </release>
3404            </grit>
3405         """.splitlines()
3406
3407   OLD_GRDP_CONTENTS = (
3408     '<?xml version="1.0" encoding="utf-8"?>',
3409       '<grit-part>',
3410     '</grit-part>'
3411   )
3412
3413   NEW_GRDP_CONTENTS1 = (
3414     '<?xml version="1.0" encoding="utf-8"?>',
3415       '<grit-part>',
3416         '<message name="IDS_PART_TEST1">',
3417           'Part string 1',
3418         '</message>',
3419     '</grit-part>')
3420
3421   NEW_GRDP_CONTENTS2 = (
3422     '<?xml version="1.0" encoding="utf-8"?>',
3423       '<grit-part>',
3424         '<message name="IDS_PART_TEST1">',
3425           'Part string 1',
3426         '</message>',
3427         '<message name="IDS_PART_TEST2">',
3428           'Part string 2',
3429       '</message>',
3430     '</grit-part>')
3431
3432   NEW_GRDP_CONTENTS3 = (
3433     '<?xml version="1.0" encoding="utf-8"?>',
3434       '<grit-part>',
3435         '<message name="IDS_PART_TEST1" desc="Description with typo.">',
3436           'Part string 1',
3437         '</message>',
3438     '</grit-part>')
3439
3440   NEW_GRDP_CONTENTS4 = (
3441     '<?xml version="1.0" encoding="utf-8"?>',
3442       '<grit-part>',
3443         '<message name="IDS_PART_TEST1" desc="Description with typo fixed.">',
3444           'Part string 1',
3445         '</message>',
3446     '</grit-part>')
3447
3448   NEW_GRDP_CONTENTS5 = (
3449     '<?xml version="1.0" encoding="utf-8"?>',
3450       '<grit-part>',
3451         '<message name="IDS_PART_TEST1" meaning="Meaning with typo.">',
3452           'Part string 1',
3453         '</message>',
3454     '</grit-part>')
3455
3456   NEW_GRDP_CONTENTS6 = (
3457     '<?xml version="1.0" encoding="utf-8"?>',
3458       '<grit-part>',
3459         '<message name="IDS_PART_TEST1" meaning="Meaning with typo fixed.">',
3460           'Part string 1',
3461         '</message>',
3462     '</grit-part>')
3463
3464   # A grdp file with one ICU syntax message without syntax errors.
3465   NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1 = (
3466     '<?xml version="1.0" encoding="utf-8"?>',
3467       '<grit-part>',
3468         '<message name="IDS_PART_TEST1">',
3469            '{NUM, plural,',
3470             '=1 {Test text for numeric one}',
3471             'other {Test text for plural with {NUM} as number}}',
3472         '</message>',
3473     '</grit-part>')
3474   # A grdp file with one ICU syntax message without syntax errors.
3475   NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2 = (
3476     '<?xml version="1.0" encoding="utf-8"?>',
3477       '<grit-part>',
3478         '<message name="IDS_PART_TEST1">',
3479            '{NUM, plural,',
3480             '=1 {Different test text for numeric one}',
3481             'other {Different test text for plural with {NUM} as number}}',
3482         '</message>',
3483     '</grit-part>')
3484
3485   # A grdp file with one ICU syntax message with syntax errors (superfluent
3486   # whitespace).
3487   NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR = (
3488     '<?xml version="1.0" encoding="utf-8"?>',
3489       '<grit-part>',
3490         '<message name="IDS_PART_TEST1">',
3491            '{NUM, plural,',
3492             '= 1 {Test text for numeric one}',
3493             'other {Test text for plural with {NUM} as number}}',
3494         '</message>',
3495     '</grit-part>')
3496
3497   VALID_SHA1 = ('0000000000000000000000000000000000000000',)
3498   DO_NOT_UPLOAD_PNG_MESSAGE = ('Do not include actual screenshots in the '
3499                                'changelist. Run '
3500                                'tools/translate/upload_screenshots.py to '
3501                                'upload them instead:')
3502   ADD_SIGNATURES_MESSAGE = ('You are adding UI strings.\n'
3503                                  'To ensure the best translations, take '
3504                                  'screenshots of the relevant UI '
3505                                  '(https://g.co/chrome/translation) and add '
3506                                  'these files to your changelist:')
3507   REMOVE_SIGNATURES_MESSAGE = ('You removed strings associated with these '
3508                                'files. Remove:')
3509   ICU_SYNTAX_ERROR_MESSAGE = ('ICU syntax errors were found in the following '
3510                               'strings (problems or feedback? Contact '
3511                               'rainhard@chromium.org):')
3512   SHA1_FORMAT_MESSAGE = ('The following files do not seem to contain valid sha1 '
3513                          'hashes. Make sure they contain hashes created by '
3514                          'tools/translate/upload_screenshots.py:')
3515
3516   def makeInputApi(self, files):
3517     input_api = MockInputApi()
3518     input_api.files = files
3519     # Override os_path.exists because the presubmit uses the actual
3520     # os.path.exists.
3521     input_api.CreateMockFileInPath(
3522         [x.LocalPath() for x in input_api.AffectedFiles(include_deletes=True)])
3523     return input_api
3524
3525   """ CL modified and added messages, but didn't add any screenshots."""
3526   def testNoScreenshots(self):
3527     # No new strings (file contents same). Should not warn.
3528     input_api = self.makeInputApi([
3529       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS1,
3530                        self.NEW_GRD_CONTENTS1, action='M'),
3531       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS1,
3532                        self.NEW_GRDP_CONTENTS1, action='M')])
3533     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3534     self.assertEqual(0, len(warnings))
3535
3536     # Add two new strings. Should have two warnings.
3537     input_api = self.makeInputApi([
3538       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS2,
3539                        self.NEW_GRD_CONTENTS1, action='M'),
3540       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS2,
3541                        self.NEW_GRDP_CONTENTS1, action='M')])
3542     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3543     self.assertEqual(1, len(warnings))
3544     self.assertEqual(self.ADD_SIGNATURES_MESSAGE, warnings[0].message)
3545     self.assertEqual('error', warnings[0].type)
3546     self.assertEqual([
3547       os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3548       os.path.join('test_grd', 'IDS_TEST2.png.sha1')],
3549                      warnings[0].items)
3550
3551     # Add four new strings. Should have four warnings.
3552     input_api = self.makeInputApi([
3553       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS2,
3554                        self.OLD_GRD_CONTENTS, action='M'),
3555       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS2,
3556                        self.OLD_GRDP_CONTENTS, action='M')])
3557     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3558     self.assertEqual(1, len(warnings))
3559     self.assertEqual('error', warnings[0].type)
3560     self.assertEqual(self.ADD_SIGNATURES_MESSAGE, warnings[0].message)
3561     self.assertEqual([
3562         os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3563         os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3564         os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3565         os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3566     ], warnings[0].items)
3567
3568   def testModifiedMessageDescription(self):
3569     # CL modified a message description for a message that does not yet have a
3570     # screenshot. Should not warn.
3571     input_api = self.makeInputApi([
3572       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS3,
3573                        self.NEW_GRDP_CONTENTS4, action='M')])
3574     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3575     self.assertEqual(0, len(warnings))
3576
3577     # CL modified a message description for a message that already has a
3578     # screenshot. Should not warn.
3579     input_api = self.makeInputApi([
3580       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS3,
3581                        self.NEW_GRDP_CONTENTS4, action='M'),
3582       MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3583                self.VALID_SHA1, action='A')])
3584     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3585     self.assertEqual(0, len(warnings))
3586
3587   def testModifiedMessageMeaning(self):
3588     # CL modified a message meaning for a message that does not yet have a
3589     # screenshot. Should warn.
3590     input_api = self.makeInputApi([
3591       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS5,
3592                        self.NEW_GRDP_CONTENTS6, action='M')])
3593     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3594     self.assertEqual(1, len(warnings))
3595
3596     # CL modified a message meaning for a message that already has a
3597     # screenshot. Should not warn.
3598     input_api = self.makeInputApi([
3599       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS5,
3600                        self.NEW_GRDP_CONTENTS6, action='M'),
3601       MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3602                self.VALID_SHA1, action='A')])
3603     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3604     self.assertEqual(0, len(warnings))
3605
3606   def testModifiedIntroducedInvalidSha1(self):
3607     # CL modified a message and the sha1 file changed to invalid
3608     input_api = self.makeInputApi([
3609         MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS5,
3610                          self.NEW_GRDP_CONTENTS6, action='M'),
3611         MockAffectedFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3612                          ('some invalid sha1',), self.VALID_SHA1, action='M')])
3613     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3614     self.assertEqual(1, len(warnings))
3615
3616   def testPngAddedSha1NotAdded(self):
3617     # CL added one new message in a grd file and added the png file associated
3618     # with it, but did not add the corresponding sha1 file. This should warn
3619     # twice:
3620     # - Once for the added png file (because we don't want developers to upload
3621     #   actual images)
3622     # - Once for the missing .sha1 file
3623     input_api = self.makeInputApi([
3624         MockAffectedFile(
3625             'test.grd',
3626             self.NEW_GRD_CONTENTS1,
3627             self.OLD_GRD_CONTENTS,
3628             action='M'),
3629         MockAffectedFile(
3630             os.path.join('test_grd', 'IDS_TEST1.png'), 'binary', action='A')
3631     ])
3632     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3633     self.assertEqual(2, len(warnings))
3634     self.assertEqual('error', warnings[0].type)
3635     self.assertEqual(self.DO_NOT_UPLOAD_PNG_MESSAGE, warnings[0].message)
3636     self.assertEqual([os.path.join('test_grd', 'IDS_TEST1.png')],
3637                      warnings[0].items)
3638     self.assertEqual('error', warnings[1].type)
3639     self.assertEqual(self.ADD_SIGNATURES_MESSAGE, warnings[1].message)
3640     self.assertEqual([os.path.join('test_grd', 'IDS_TEST1.png.sha1')],
3641                      warnings[1].items)
3642
3643     # CL added two messages (one in grd, one in grdp) and added the png files
3644     # associated with the messages, but did not add the corresponding sha1
3645     # files. This should warn twice:
3646     # - Once for the added png files (because we don't want developers to upload
3647     #   actual images)
3648     # - Once for the missing .sha1 files
3649     input_api = self.makeInputApi([
3650         # Modified files:
3651         MockAffectedFile(
3652             'test.grd',
3653             self.NEW_GRD_CONTENTS1,
3654             self.OLD_GRD_CONTENTS,
3655             action='M'),
3656         MockAffectedFile(
3657             'part.grdp',
3658             self.NEW_GRDP_CONTENTS1,
3659             self.OLD_GRDP_CONTENTS,
3660             action='M'),
3661         # Added files:
3662         MockAffectedFile(
3663             os.path.join('test_grd', 'IDS_TEST1.png'), 'binary', action='A'),
3664         MockAffectedFile(
3665             os.path.join('part_grdp', 'IDS_PART_TEST1.png'), 'binary',
3666             action='A')
3667     ])
3668     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3669     self.assertEqual(2, len(warnings))
3670     self.assertEqual('error', warnings[0].type)
3671     self.assertEqual(self.DO_NOT_UPLOAD_PNG_MESSAGE, warnings[0].message)
3672     self.assertEqual([os.path.join('part_grdp', 'IDS_PART_TEST1.png'),
3673                       os.path.join('test_grd', 'IDS_TEST1.png')],
3674                      warnings[0].items)
3675     self.assertEqual('error', warnings[0].type)
3676     self.assertEqual(self.ADD_SIGNATURES_MESSAGE, warnings[1].message)
3677     self.assertEqual([os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3678                       os.path.join('test_grd', 'IDS_TEST1.png.sha1')],
3679                       warnings[1].items)
3680
3681
3682   def testScreenshotsWithSha1(self):
3683     # CL added four messages (two each in a grd and grdp) and their
3684     # corresponding .sha1 files. No warnings.
3685     input_api = self.makeInputApi([
3686         # Modified files:
3687         MockAffectedFile(
3688             'test.grd',
3689             self.NEW_GRD_CONTENTS2,
3690             self.OLD_GRD_CONTENTS,
3691             action='M'),
3692         MockAffectedFile(
3693             'part.grdp',
3694             self.NEW_GRDP_CONTENTS2,
3695             self.OLD_GRDP_CONTENTS,
3696             action='M'),
3697         # Added files:
3698         MockFile(
3699             os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3700             self.VALID_SHA1,
3701             action='A'),
3702         MockFile(
3703             os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3704             ('0000000000000000000000000000000000000000', ''),
3705             action='A'),
3706         MockFile(
3707             os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3708             self.VALID_SHA1,
3709             action='A'),
3710         MockFile(
3711             os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3712             self.VALID_SHA1,
3713             action='A'),
3714     ])
3715     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3716     self.assertEqual([], warnings)
3717
3718
3719   def testScreenshotsWithInvalidSha1(self):
3720     input_api = self.makeInputApi([
3721         # Modified files:
3722         MockAffectedFile(
3723             'test.grd',
3724             self.NEW_GRD_CONTENTS2,
3725             self.OLD_GRD_CONTENTS,
3726             action='M'),
3727         MockAffectedFile(
3728             'part.grdp',
3729             self.NEW_GRDP_CONTENTS2,
3730             self.OLD_GRDP_CONTENTS,
3731             action='M'),
3732         # Added files:
3733         MockFile(
3734             os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3735             self.VALID_SHA1,
3736             action='A'),
3737         MockFile(
3738             os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3739             ('‰PNG', 'test'),
3740             action='A'),
3741         MockFile(
3742             os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3743             self.VALID_SHA1,
3744             action='A'),
3745         MockFile(
3746             os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3747             self.VALID_SHA1,
3748             action='A'),
3749     ])
3750     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3751     self.assertEqual(1, len(warnings))
3752     self.assertEqual('error', warnings[0].type)
3753     self.assertEqual(self.SHA1_FORMAT_MESSAGE, warnings[0].message)
3754     self.assertEqual([os.path.join('test_grd', 'IDS_TEST2.png.sha1')],
3755                      warnings[0].items)
3756
3757
3758   def testScreenshotsRemovedWithSha1(self):
3759     # Replace new contents with old contents in grd and grp files, removing
3760     # IDS_TEST1, IDS_TEST2, IDS_PART_TEST1 and IDS_PART_TEST2.
3761     # Should warn to remove the sha1 files associated with these strings.
3762     input_api = self.makeInputApi([
3763         # Modified files:
3764         MockAffectedFile(
3765             'test.grd',
3766             self.OLD_GRD_CONTENTS, # new_contents
3767             self.NEW_GRD_CONTENTS2, # old_contents
3768             action='M'),
3769         MockAffectedFile(
3770             'part.grdp',
3771             self.OLD_GRDP_CONTENTS, # new_contents
3772             self.NEW_GRDP_CONTENTS2, # old_contents
3773             action='M'),
3774         # Unmodified files:
3775         MockFile(os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3776                  self.VALID_SHA1, ''),
3777         MockFile(os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3778                  self.VALID_SHA1, ''),
3779         MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3780                  self.VALID_SHA1, ''),
3781         MockFile(os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3782                  self.VALID_SHA1, '')
3783     ])
3784     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3785     self.assertEqual(1, len(warnings))
3786     self.assertEqual('error', warnings[0].type)
3787     self.assertEqual(self.REMOVE_SIGNATURES_MESSAGE, warnings[0].message)
3788     self.assertEqual([
3789         os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3790         os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3791         os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3792         os.path.join('test_grd', 'IDS_TEST2.png.sha1')
3793     ], warnings[0].items)
3794
3795     # Same as above, but this time one of the .sha1 files is also removed.
3796     input_api = self.makeInputApi([
3797         # Modified files:
3798         MockAffectedFile(
3799             'test.grd',
3800             self.OLD_GRD_CONTENTS, # new_contents
3801             self.NEW_GRD_CONTENTS2, # old_contents
3802             action='M'),
3803         MockAffectedFile(
3804             'part.grdp',
3805             self.OLD_GRDP_CONTENTS, # new_contents
3806             self.NEW_GRDP_CONTENTS2, # old_contents
3807             action='M'),
3808         # Unmodified files:
3809         MockFile(os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3810                  self.VALID_SHA1, ''),
3811         MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3812                  self.VALID_SHA1, ''),
3813         # Deleted files:
3814         MockAffectedFile(
3815             os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3816             '',
3817             'old_contents',
3818             action='D'),
3819         MockAffectedFile(
3820             os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3821             '',
3822             'old_contents',
3823             action='D')
3824     ])
3825     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3826     self.assertEqual(1, len(warnings))
3827     self.assertEqual('error', warnings[0].type)
3828     self.assertEqual(self.REMOVE_SIGNATURES_MESSAGE, warnings[0].message)
3829     self.assertEqual([os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3830                       os.path.join('test_grd', 'IDS_TEST1.png.sha1')
3831                      ], warnings[0].items)
3832
3833     # Remove all sha1 files. There should be no warnings.
3834     input_api = self.makeInputApi([
3835         # Modified files:
3836         MockAffectedFile(
3837             'test.grd',
3838             self.OLD_GRD_CONTENTS,
3839             self.NEW_GRD_CONTENTS2,
3840             action='M'),
3841         MockAffectedFile(
3842             'part.grdp',
3843             self.OLD_GRDP_CONTENTS,
3844             self.NEW_GRDP_CONTENTS2,
3845             action='M'),
3846         # Deleted files:
3847         MockFile(
3848             os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3849             self.VALID_SHA1,
3850             action='D'),
3851         MockFile(
3852             os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3853             self.VALID_SHA1,
3854             action='D'),
3855         MockFile(
3856             os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3857             self.VALID_SHA1,
3858             action='D'),
3859         MockFile(
3860             os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3861             self.VALID_SHA1,
3862             action='D')
3863     ])
3864     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3865     self.assertEqual([], warnings)
3866
3867   def testIcuSyntax(self):
3868     # Add valid ICU syntax string. Should not raise an error.
3869     input_api = self.makeInputApi([
3870       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK2,
3871                        self.NEW_GRD_CONTENTS1, action='M'),
3872       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2,
3873                        self.NEW_GRDP_CONTENTS1, action='M')])
3874     results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3875     # We expect no ICU syntax errors.
3876     icu_errors = [e for e in results
3877         if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3878     self.assertEqual(0, len(icu_errors))
3879
3880     # Valid changes in ICU syntax. Should not raise an error.
3881     input_api = self.makeInputApi([
3882       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK2,
3883                        self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK1, action='M'),
3884       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2,
3885                        self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1, action='M')])
3886     results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3887     # We expect no ICU syntax errors.
3888     icu_errors = [e for e in results
3889         if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3890     self.assertEqual(0, len(icu_errors))
3891
3892     # Add invalid ICU syntax strings. Should raise two errors.
3893     input_api = self.makeInputApi([
3894       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR,
3895                        self.NEW_GRD_CONTENTS1, action='M'),
3896       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR,
3897                        self.NEW_GRD_CONTENTS1, action='M')])
3898     results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3899     # We expect 2 ICU syntax errors.
3900     icu_errors = [e for e in results
3901         if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3902     self.assertEqual(1, len(icu_errors))
3903     self.assertEqual([
3904         'IDS_TEST1: This message looks like an ICU plural, but does not follow '
3905         'ICU syntax.',
3906         'IDS_PART_TEST1: Variant "= 1" is not valid for plural message'
3907       ], icu_errors[0].items)
3908
3909     # Change two strings to have ICU syntax errors. Should raise two errors.
3910     input_api = self.makeInputApi([
3911       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR,
3912                        self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK1, action='M'),
3913       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR,
3914                        self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1, action='M')])
3915     results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3916     # We expect 2 ICU syntax errors.
3917     icu_errors = [e for e in results
3918         if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3919     self.assertEqual(1, len(icu_errors))
3920     self.assertEqual([
3921         'IDS_TEST1: This message looks like an ICU plural, but does not follow '
3922         'ICU syntax.',
3923         'IDS_PART_TEST1: Variant "= 1" is not valid for plural message'
3924       ], icu_errors[0].items)
3925
3926
3927 class TranslationExpectationsTest(unittest.TestCase):
3928   ERROR_MESSAGE_FORMAT = (
3929     "Failed to get a list of translatable grd files. "
3930     "This happens when:\n"
3931     " - One of the modified grd or grdp files cannot be parsed or\n"
3932     " - %s is not updated.\n"
3933     "Stack:\n"
3934   )
3935   REPO_ROOT = os.path.join('tools', 'translation', 'testdata')
3936   # This lists all .grd files under REPO_ROOT.
3937   EXPECTATIONS = os.path.join(REPO_ROOT,
3938                               "translation_expectations.pyl")
3939   # This lists all .grd files under REPO_ROOT except unlisted.grd.
3940   EXPECTATIONS_WITHOUT_UNLISTED_FILE = os.path.join(
3941       REPO_ROOT, "translation_expectations_without_unlisted_file.pyl")
3942
3943   # Tests that the presubmit doesn't return when no grd or grdp files are
3944   # modified.
3945   def testExpectationsNoModifiedGrd(self):
3946     input_api = MockInputApi()
3947     input_api.files = [
3948         MockAffectedFile('not_used.txt', 'not used', 'not used', action='M')
3949     ]
3950     # Fake list of all grd files in the repo. This list is missing all grd/grdps
3951     # under tools/translation/testdata. This is OK because the presubmit won't
3952     # run in the first place since there are no modified grd/grps in input_api.
3953     grd_files = ['doesnt_exist_doesnt_matter.grd']
3954     warnings = PRESUBMIT.CheckTranslationExpectations(
3955         input_api, MockOutputApi(), self.REPO_ROOT, self.EXPECTATIONS,
3956         grd_files)
3957     self.assertEqual(0, len(warnings))
3958
3959
3960   # Tests that the list of files passed to the presubmit matches the list of
3961   # files in the expectations.
3962   def testExpectationsSuccess(self):
3963     # Mock input file list needs a grd or grdp file in order to run the
3964     # presubmit. The file itself doesn't matter.
3965     input_api = MockInputApi()
3966     input_api.files = [
3967         MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3968     ]
3969     # List of all grd files in the repo.
3970     grd_files = ['test.grd', 'unlisted.grd', 'not_translated.grd',
3971                  'internal.grd']
3972     warnings = PRESUBMIT.CheckTranslationExpectations(
3973         input_api, MockOutputApi(), self.REPO_ROOT, self.EXPECTATIONS,
3974         grd_files)
3975     self.assertEqual(0, len(warnings))
3976
3977   # Tests that the presubmit warns when a file is listed in expectations, but
3978   # does not actually exist.
3979   def testExpectationsMissingFile(self):
3980     # Mock input file list needs a grd or grdp file in order to run the
3981     # presubmit.
3982     input_api = MockInputApi()
3983     input_api.files = [
3984       MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3985     ]
3986     # unlisted.grd is listed under tools/translation/testdata but is not
3987     # included in translation expectations.
3988     grd_files = ['unlisted.grd', 'not_translated.grd', 'internal.grd']
3989     warnings = PRESUBMIT.CheckTranslationExpectations(
3990         input_api, MockOutputApi(), self.REPO_ROOT, self.EXPECTATIONS,
3991         grd_files)
3992     self.assertEqual(1, len(warnings))
3993     self.assertTrue(warnings[0].message.startswith(
3994         self.ERROR_MESSAGE_FORMAT % self.EXPECTATIONS))
3995     self.assertTrue(
3996         ("test.grd is listed in the translation expectations, "
3997          "but this grd file does not exist")
3998         in warnings[0].message)
3999
4000   # Tests that the presubmit warns when a file is not listed in expectations but
4001   # does actually exist.
4002   def testExpectationsUnlistedFile(self):
4003     # Mock input file list needs a grd or grdp file in order to run the
4004     # presubmit.
4005     input_api = MockInputApi()
4006     input_api.files = [
4007       MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
4008     ]
4009     # unlisted.grd is listed under tools/translation/testdata but is not
4010     # included in translation expectations.
4011     grd_files = ['test.grd', 'unlisted.grd', 'not_translated.grd',
4012                  'internal.grd']
4013     warnings = PRESUBMIT.CheckTranslationExpectations(
4014         input_api, MockOutputApi(), self.REPO_ROOT,
4015         self.EXPECTATIONS_WITHOUT_UNLISTED_FILE, grd_files)
4016     self.assertEqual(1, len(warnings))
4017     self.assertTrue(warnings[0].message.startswith(
4018         self.ERROR_MESSAGE_FORMAT % self.EXPECTATIONS_WITHOUT_UNLISTED_FILE))
4019     self.assertTrue(
4020         ("unlisted.grd appears to be translatable "
4021          "(because it contains <file> or <message> elements), "
4022          "but is not listed in the translation expectations.")
4023         in warnings[0].message)
4024
4025   # Tests that the presubmit warns twice:
4026   # - for a non-existing file listed in expectations
4027   # - for an existing file not listed in expectations
4028   def testMultipleWarnings(self):
4029     # Mock input file list needs a grd or grdp file in order to run the
4030     # presubmit.
4031     input_api = MockInputApi()
4032     input_api.files = [
4033       MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
4034     ]
4035     # unlisted.grd is listed under tools/translation/testdata but is not
4036     # included in translation expectations.
4037     # test.grd is not listed under tools/translation/testdata but is included
4038     # in translation expectations.
4039     grd_files = ['unlisted.grd', 'not_translated.grd', 'internal.grd']
4040     warnings = PRESUBMIT.CheckTranslationExpectations(
4041         input_api, MockOutputApi(), self.REPO_ROOT,
4042         self.EXPECTATIONS_WITHOUT_UNLISTED_FILE, grd_files)
4043     self.assertEqual(1, len(warnings))
4044     self.assertTrue(warnings[0].message.startswith(
4045         self.ERROR_MESSAGE_FORMAT % self.EXPECTATIONS_WITHOUT_UNLISTED_FILE))
4046     self.assertTrue(
4047         ("unlisted.grd appears to be translatable "
4048          "(because it contains <file> or <message> elements), "
4049          "but is not listed in the translation expectations.")
4050         in warnings[0].message)
4051     self.assertTrue(
4052         ("test.grd is listed in the translation expectations, "
4053          "but this grd file does not exist")
4054         in warnings[0].message)
4055
4056
4057 class DISABLETypoInTest(unittest.TestCase):
4058
4059   def testPositive(self):
4060     # Verify the typo "DISABLE_" instead of "DISABLED_" in various contexts
4061     # where the desire is to disable a test.
4062     tests = [
4063         # Disabled on one platform:
4064         '#if defined(OS_WIN)\n'
4065         '#define MAYBE_FoobarTest DISABLE_FoobarTest\n'
4066         '#else\n'
4067         '#define MAYBE_FoobarTest FoobarTest\n'
4068         '#endif\n',
4069         # Disabled on one platform spread cross lines:
4070         '#if defined(OS_WIN)\n'
4071         '#define MAYBE_FoobarTest \\\n'
4072         '    DISABLE_FoobarTest\n'
4073         '#else\n'
4074         '#define MAYBE_FoobarTest FoobarTest\n'
4075         '#endif\n',
4076         # Disabled on all platforms:
4077         '  TEST_F(FoobarTest, DISABLE_Foo)\n{\n}',
4078         # Disabled on all platforms but multiple lines
4079         '  TEST_F(FoobarTest,\n   DISABLE_foo){\n}\n',
4080     ]
4081
4082     for test in tests:
4083       mock_input_api = MockInputApi()
4084       mock_input_api.files = [
4085           MockFile('some/path/foo_unittest.cc', test.splitlines()),
4086       ]
4087
4088       results = PRESUBMIT.CheckNoDISABLETypoInTests(mock_input_api,
4089                                                      MockOutputApi())
4090       self.assertEqual(
4091           1,
4092           len(results),
4093           msg=('expected len(results) == 1 but got %d in test: %s' %
4094                (len(results), test)))
4095       self.assertTrue(
4096           'foo_unittest.cc' in results[0].message,
4097           msg=('expected foo_unittest.cc in message but got %s in test %s' %
4098                (results[0].message, test)))
4099
4100   def testIgnoreNotTestFiles(self):
4101     mock_input_api = MockInputApi()
4102     mock_input_api.files = [
4103         MockFile('some/path/foo.cc', 'TEST_F(FoobarTest, DISABLE_Foo)'),
4104     ]
4105
4106     results = PRESUBMIT.CheckNoDISABLETypoInTests(mock_input_api,
4107                                                    MockOutputApi())
4108     self.assertEqual(0, len(results))
4109
4110   def testIgnoreDeletedFiles(self):
4111     mock_input_api = MockInputApi()
4112     mock_input_api.files = [
4113         MockFile('some/path/foo.cc', 'TEST_F(FoobarTest, Foo)', action='D'),
4114     ]
4115
4116     results = PRESUBMIT.CheckNoDISABLETypoInTests(mock_input_api,
4117                                                    MockOutputApi())
4118     self.assertEqual(0, len(results))
4119
4120 class ForgettingMAYBEInTests(unittest.TestCase):
4121   def testPositive(self):
4122     test = (
4123         '#if defined(HAS_ENERGY)\n'
4124         '#define MAYBE_CastExplosion DISABLED_CastExplosion\n'
4125         '#else\n'
4126         '#define MAYBE_CastExplosion CastExplosion\n'
4127         '#endif\n'
4128         'TEST_F(ArchWizard, CastExplosion) {\n'
4129         '#if defined(ARCH_PRIEST_IN_PARTY)\n'
4130         '#define MAYBE_ArchPriest ArchPriest\n'
4131         '#else\n'
4132         '#define MAYBE_ArchPriest DISABLED_ArchPriest\n'
4133         '#endif\n'
4134         'TEST_F(ArchPriest, CastNaturesBounty) {\n'
4135         '#if !defined(CRUSADER_IN_PARTY)\n'
4136         '#define MAYBE_Crusader \\\n'
4137         '    DISABLED_Crusader \n'
4138         '#else\n'
4139         '#define MAYBE_Crusader \\\n'
4140         '    Crusader\n'
4141         '#endif\n'
4142         '  TEST_F(\n'
4143         '    Crusader,\n'
4144         '    CastTaunt) { }\n'
4145         '#if defined(LEARNED_BASIC_SKILLS)\n'
4146         '#define MAYBE_CastSteal \\\n'
4147         '    DISABLED_CastSteal \n'
4148         '#else\n'
4149         '#define MAYBE_CastSteal \\\n'
4150         '    CastSteal\n'
4151         '#endif\n'
4152         '  TEST_F(\n'
4153         '    ThiefClass,\n'
4154         '    CastSteal) { }\n'
4155     )
4156     mock_input_api = MockInputApi()
4157     mock_input_api.files = [
4158         MockFile('fantasyworld/classes_unittest.cc', test.splitlines()),
4159     ]
4160     results = PRESUBMIT.CheckForgettingMAYBEInTests(mock_input_api,
4161                                                     MockOutputApi())
4162     self.assertEqual(4, len(results))
4163     self.assertTrue('CastExplosion' in results[0].message)
4164     self.assertTrue('fantasyworld/classes_unittest.cc:2' in results[0].message)
4165     self.assertTrue('ArchPriest' in results[1].message)
4166     self.assertTrue('fantasyworld/classes_unittest.cc:8' in results[1].message)
4167     self.assertTrue('Crusader' in results[2].message)
4168     self.assertTrue('fantasyworld/classes_unittest.cc:14' in results[2].message)
4169     self.assertTrue('CastSteal' in results[3].message)
4170     self.assertTrue('fantasyworld/classes_unittest.cc:24' in results[3].message)
4171
4172   def testNegative(self):
4173     test = (
4174         '#if defined(HAS_ENERGY)\n'
4175         '#define MAYBE_CastExplosion DISABLED_CastExplosion\n'
4176         '#else\n'
4177         '#define MAYBE_CastExplosion CastExplosion\n'
4178         '#endif\n'
4179         'TEST_F(ArchWizard, MAYBE_CastExplosion) {\n'
4180         '#if defined(ARCH_PRIEST_IN_PARTY)\n'
4181         '#define MAYBE_ArchPriest ArchPriest\n'
4182         '#else\n'
4183         '#define MAYBE_ArchPriest DISABLED_ArchPriest\n'
4184         '#endif\n'
4185         'TEST_F(MAYBE_ArchPriest, CastNaturesBounty) {\n'
4186         '#if !defined(CRUSADER_IN_PARTY)\n'
4187         '#define MAYBE_Crusader \\\n'
4188         '    DISABLED_Crusader \n'
4189         '#else\n'
4190         '#define MAYBE_Crusader \\\n'
4191         '    Crusader\n'
4192         '#endif\n'
4193         '  TEST_F(\n'
4194         '    MAYBE_Crusader,\n'
4195         '    CastTaunt) { }\n'
4196         '#if defined(LEARNED_BASIC_SKILLS)\n'
4197         '#define MAYBE_CastSteal \\\n'
4198         '    DISABLED_CastSteal \n'
4199         '#else\n'
4200         '#define MAYBE_CastSteal \\\n'
4201         '    CastSteal\n'
4202         '#endif\n'
4203         '  TEST_F(\n'
4204         '    ThiefClass,\n'
4205         '    MAYBE_CastSteal) { }\n'
4206     )
4207
4208     mock_input_api = MockInputApi()
4209     mock_input_api.files = [
4210         MockFile('fantasyworld/classes_unittest.cc', test.splitlines()),
4211     ]
4212     results = PRESUBMIT.CheckForgettingMAYBEInTests(mock_input_api,
4213                                                     MockOutputApi())
4214     self.assertEqual(0, len(results))
4215
4216 class CheckFuzzTargetsTest(unittest.TestCase):
4217
4218   def _check(self, files):
4219     mock_input_api = MockInputApi()
4220     mock_input_api.files = []
4221     for fname, contents in files.items():
4222       mock_input_api.files.append(MockFile(fname, contents.splitlines()))
4223     return PRESUBMIT.CheckFuzzTargetsOnUpload(mock_input_api, MockOutputApi())
4224
4225   def testLibFuzzerSourcesIgnored(self):
4226     results = self._check({
4227         "third_party/lib/Fuzzer/FuzzerDriver.cpp": "LLVMFuzzerInitialize",
4228     })
4229     self.assertEqual(results, [])
4230
4231   def testNonCodeFilesIgnored(self):
4232     results = self._check({
4233         "README.md": "LLVMFuzzerInitialize",
4234     })
4235     self.assertEqual(results, [])
4236
4237   def testNoErrorHeaderPresent(self):
4238     results = self._check({
4239         "fuzzer.cc": (
4240             "#include \"testing/libfuzzer/libfuzzer_exports.h\"\n" +
4241             "LLVMFuzzerInitialize"
4242         )
4243     })
4244     self.assertEqual(results, [])
4245
4246   def testErrorMissingHeader(self):
4247     results = self._check({
4248         "fuzzer.cc": "LLVMFuzzerInitialize"
4249     })
4250     self.assertEqual(len(results), 1)
4251     self.assertEqual(results[0].items, ['fuzzer.cc'])
4252
4253
4254 class SetNoParentTest(unittest.TestCase):
4255   def testSetNoParentTopLevelAllowed(self):
4256     mock_input_api = MockInputApi()
4257     mock_input_api.files = [
4258       MockAffectedFile('goat/OWNERS',
4259                        [
4260                          'set noparent',
4261                          'jochen@chromium.org',
4262                        ])
4263     ]
4264     mock_output_api = MockOutputApi()
4265     errors = PRESUBMIT.CheckSetNoParent(mock_input_api, mock_output_api)
4266     self.assertEqual([], errors)
4267
4268   def testSetNoParentMissing(self):
4269     mock_input_api = MockInputApi()
4270     mock_input_api.files = [
4271       MockAffectedFile('services/goat/OWNERS',
4272                        [
4273                          'set noparent',
4274                          'jochen@chromium.org',
4275                          'per-file *.json=set noparent',
4276                          'per-file *.json=jochen@chromium.org',
4277                        ])
4278     ]
4279     mock_output_api = MockOutputApi()
4280     errors = PRESUBMIT.CheckSetNoParent(mock_input_api, mock_output_api)
4281     self.assertEqual(1, len(errors))
4282     self.assertTrue('goat/OWNERS:1' in errors[0].long_text)
4283     self.assertTrue('goat/OWNERS:3' in errors[0].long_text)
4284
4285   def testSetNoParentWithCorrectRule(self):
4286     mock_input_api = MockInputApi()
4287     mock_input_api.files = [
4288       MockAffectedFile('services/goat/OWNERS',
4289                        [
4290                          'set noparent',
4291                          'file://ipc/SECURITY_OWNERS',
4292                          'per-file *.json=set noparent',
4293                          'per-file *.json=file://ipc/SECURITY_OWNERS',
4294                        ])
4295     ]
4296     mock_output_api = MockOutputApi()
4297     errors = PRESUBMIT.CheckSetNoParent(mock_input_api, mock_output_api)
4298     self.assertEqual([], errors)
4299
4300
4301 class MojomStabilityCheckTest(unittest.TestCase):
4302   def runTestWithAffectedFiles(self, affected_files):
4303     mock_input_api = MockInputApi()
4304     mock_input_api.files = affected_files
4305     mock_output_api = MockOutputApi()
4306     return PRESUBMIT.CheckStableMojomChanges(
4307         mock_input_api, mock_output_api)
4308
4309   def testSafeChangePasses(self):
4310     errors = self.runTestWithAffectedFiles([
4311       MockAffectedFile('foo/foo.mojom',
4312                        ['[Stable] struct S { [MinVersion=1] int32 x; };'],
4313                        old_contents=['[Stable] struct S {};'])
4314     ])
4315     self.assertEqual([], errors)
4316
4317   def testBadChangeFails(self):
4318     errors = self.runTestWithAffectedFiles([
4319       MockAffectedFile('foo/foo.mojom',
4320                        ['[Stable] struct S { int32 x; };'],
4321                        old_contents=['[Stable] struct S {};'])
4322     ])
4323     self.assertEqual(1, len(errors))
4324     self.assertTrue('not backward-compatible' in errors[0].message)
4325
4326   def testDeletedFile(self):
4327     """Regression test for https://crbug.com/1091407."""
4328     errors = self.runTestWithAffectedFiles([
4329       MockAffectedFile('a.mojom', [], old_contents=['struct S {};'],
4330                        action='D'),
4331       MockAffectedFile('b.mojom',
4332                        ['struct S {}; struct T { S s; };'],
4333                        old_contents=['import "a.mojom"; struct T { S s; };'])
4334     ])
4335     self.assertEqual([], errors)
4336
4337 class CheckForUseOfChromeAppsDeprecationsTest(unittest.TestCase):
4338
4339   ERROR_MSG_PIECE = 'technologies which will soon be deprecated'
4340
4341   # Each positive test is also a naive negative test for the other cases.
4342
4343   def testWarningNMF(self):
4344     mock_input_api = MockInputApi()
4345     mock_input_api.files = [
4346         MockAffectedFile(
4347             'foo.NMF',
4348             ['"program"', '"Z":"content"', 'B'],
4349             ['"program"', 'B'],
4350             scm_diff='\n'.join([
4351                 '--- foo.NMF.old  2020-12-02 20:40:54.430676385 +0100',
4352                 '+++ foo.NMF.new  2020-12-02 20:41:02.086700197 +0100',
4353                 '@@ -1,2 +1,3 @@',
4354                 ' "program"',
4355                 '+"Z":"content"',
4356                 ' B']),
4357             action='M')
4358     ]
4359     mock_output_api = MockOutputApi()
4360     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4361                                                            mock_output_api)
4362     self.assertEqual(1, len(errors))
4363     self.assertTrue( self.ERROR_MSG_PIECE in errors[0].message)
4364     self.assertTrue( 'foo.NMF' in errors[0].message)
4365
4366   def testWarningManifest(self):
4367     mock_input_api = MockInputApi()
4368     mock_input_api.files = [
4369         MockAffectedFile(
4370             'manifest.json',
4371             ['"app":', '"Z":"content"', 'B'],
4372             ['"app":"', 'B'],
4373             scm_diff='\n'.join([
4374                 '--- manifest.json.old  2020-12-02 20:40:54.430676385 +0100',
4375                 '+++ manifest.json.new  2020-12-02 20:41:02.086700197 +0100',
4376                 '@@ -1,2 +1,3 @@',
4377                 ' "app"',
4378                 '+"Z":"content"',
4379                 ' B']),
4380             action='M')
4381     ]
4382     mock_output_api = MockOutputApi()
4383     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4384                                                            mock_output_api)
4385     self.assertEqual(1, len(errors))
4386     self.assertTrue( self.ERROR_MSG_PIECE in errors[0].message)
4387     self.assertTrue( 'manifest.json' in errors[0].message)
4388
4389   def testOKWarningManifestWithoutApp(self):
4390     mock_input_api = MockInputApi()
4391     mock_input_api.files = [
4392         MockAffectedFile(
4393             'manifest.json',
4394             ['"name":', '"Z":"content"', 'B'],
4395             ['"name":"', 'B'],
4396             scm_diff='\n'.join([
4397                 '--- manifest.json.old  2020-12-02 20:40:54.430676385 +0100',
4398                 '+++ manifest.json.new  2020-12-02 20:41:02.086700197 +0100',
4399                 '@@ -1,2 +1,3 @@',
4400                 ' "app"',
4401                 '+"Z":"content"',
4402                 ' B']),
4403             action='M')
4404     ]
4405     mock_output_api = MockOutputApi()
4406     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4407                                                            mock_output_api)
4408     self.assertEqual(0, len(errors))
4409
4410   def testWarningPPAPI(self):
4411     mock_input_api = MockInputApi()
4412     mock_input_api.files = [
4413         MockAffectedFile(
4414             'foo.hpp',
4415             ['A', '#include <ppapi.h>', 'B'],
4416             ['A', 'B'],
4417             scm_diff='\n'.join([
4418                 '--- foo.hpp.old  2020-12-02 20:40:54.430676385 +0100',
4419                 '+++ foo.hpp.new  2020-12-02 20:41:02.086700197 +0100',
4420                 '@@ -1,2 +1,3 @@',
4421                 ' A',
4422                 '+#include <ppapi.h>',
4423                 ' B']),
4424             action='M')
4425     ]
4426     mock_output_api = MockOutputApi()
4427     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4428                                                            mock_output_api)
4429     self.assertEqual(1, len(errors))
4430     self.assertTrue( self.ERROR_MSG_PIECE in errors[0].message)
4431     self.assertTrue( 'foo.hpp' in errors[0].message)
4432
4433   def testNoWarningPPAPI(self):
4434     mock_input_api = MockInputApi()
4435     mock_input_api.files = [
4436         MockAffectedFile(
4437             'foo.txt',
4438             ['A', 'Peppapig', 'B'],
4439             ['A', 'B'],
4440             scm_diff='\n'.join([
4441                 '--- foo.txt.old  2020-12-02 20:40:54.430676385 +0100',
4442                 '+++ foo.txt.new  2020-12-02 20:41:02.086700197 +0100',
4443                 '@@ -1,2 +1,3 @@',
4444                 ' A',
4445                 '+Peppapig',
4446                 ' B']),
4447             action='M')
4448     ]
4449     mock_output_api = MockOutputApi()
4450     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
4451                                                            mock_output_api)
4452     self.assertEqual(0, len(errors))
4453
4454 class CheckDeprecationOfPreferencesTest(unittest.TestCase):
4455   # Test that a warning is generated if a preference registration is removed
4456   # from a random file.
4457   def testWarning(self):
4458     mock_input_api = MockInputApi()
4459     mock_input_api.files = [
4460         MockAffectedFile(
4461             'foo.cc',
4462             ['A', 'B'],
4463             ['A', 'prefs->RegisterStringPref("foo", "default");', 'B'],
4464             scm_diff='\n'.join([
4465                 '--- foo.cc.old  2020-12-02 20:40:54.430676385 +0100',
4466                 '+++ foo.cc.new  2020-12-02 20:41:02.086700197 +0100',
4467                 '@@ -1,3 +1,2 @@',
4468                 ' A',
4469                 '-prefs->RegisterStringPref("foo", "default");',
4470                 ' B']),
4471             action='M')
4472     ]
4473     mock_output_api = MockOutputApi()
4474     errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
4475                                                      mock_output_api)
4476     self.assertEqual(1, len(errors))
4477     self.assertTrue(
4478         'Discovered possible removal of preference registrations' in
4479         errors[0].message)
4480
4481   # Test that a warning is inhibited if the preference registration was moved
4482   # to the deprecation functions in browser prefs.
4483   def testNoWarningForMigration(self):
4484     mock_input_api = MockInputApi()
4485     mock_input_api.files = [
4486         # RegisterStringPref was removed from foo.cc.
4487         MockAffectedFile(
4488             'foo.cc',
4489             ['A', 'B'],
4490             ['A', 'prefs->RegisterStringPref("foo", "default");', 'B'],
4491             scm_diff='\n'.join([
4492                 '--- foo.cc.old  2020-12-02 20:40:54.430676385 +0100',
4493                 '+++ foo.cc.new  2020-12-02 20:41:02.086700197 +0100',
4494                 '@@ -1,3 +1,2 @@',
4495                 ' A',
4496                 '-prefs->RegisterStringPref("foo", "default");',
4497                 ' B']),
4498             action='M'),
4499         # But the preference was properly migrated.
4500         MockAffectedFile(
4501             'chrome/browser/prefs/browser_prefs.cc',
4502             [
4503                  '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4504                  '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4505                  '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4506                  'prefs->RegisterStringPref("foo", "default");',
4507                  '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4508             ],
4509             [
4510                  '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4511                  '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4512                  '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4513                  '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4514             ],
4515             scm_diff='\n'.join([
4516                  '--- browser_prefs.cc.old 2020-12-02 20:51:40.812686731 +0100',
4517                  '+++ browser_prefs.cc.new 2020-12-02 20:52:02.936755539 +0100',
4518                  '@@ -2,3 +2,4 @@',
4519                  ' // END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4520                  ' // BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4521                  '+prefs->RegisterStringPref("foo", "default");',
4522                  ' // END_MIGRATE_OBSOLETE_PROFILE_PREFS']),
4523             action='M'),
4524     ]
4525     mock_output_api = MockOutputApi()
4526     errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
4527                                                      mock_output_api)
4528     self.assertEqual(0, len(errors))
4529
4530   # Test that a warning is NOT inhibited if the preference registration was
4531   # moved to a place outside of the migration functions in browser_prefs.cc
4532   def testWarningForImproperMigration(self):
4533     mock_input_api = MockInputApi()
4534     mock_input_api.files = [
4535         # RegisterStringPref was removed from foo.cc.
4536         MockAffectedFile(
4537             'foo.cc',
4538             ['A', 'B'],
4539             ['A', 'prefs->RegisterStringPref("foo", "default");', 'B'],
4540             scm_diff='\n'.join([
4541                 '--- foo.cc.old  2020-12-02 20:40:54.430676385 +0100',
4542                 '+++ foo.cc.new  2020-12-02 20:41:02.086700197 +0100',
4543                 '@@ -1,3 +1,2 @@',
4544                 ' A',
4545                 '-prefs->RegisterStringPref("foo", "default");',
4546                 ' B']),
4547             action='M'),
4548         # The registration call was moved to a place in browser_prefs.cc that
4549         # is outside the migration functions.
4550         MockAffectedFile(
4551             'chrome/browser/prefs/browser_prefs.cc',
4552             [
4553                  'prefs->RegisterStringPref("foo", "default");',
4554                  '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4555                  '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4556                  '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4557                  '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4558             ],
4559             [
4560                  '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4561                  '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4562                  '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4563                  '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4564             ],
4565             scm_diff='\n'.join([
4566                  '--- browser_prefs.cc.old 2020-12-02 20:51:40.812686731 +0100',
4567                  '+++ browser_prefs.cc.new 2020-12-02 20:52:02.936755539 +0100',
4568                  '@@ -1,2 +1,3 @@',
4569                  '+prefs->RegisterStringPref("foo", "default");',
4570                  ' // BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4571                  ' // END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS']),
4572             action='M'),
4573     ]
4574     mock_output_api = MockOutputApi()
4575     errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
4576                                                      mock_output_api)
4577     self.assertEqual(1, len(errors))
4578     self.assertTrue(
4579         'Discovered possible removal of preference registrations' in
4580         errors[0].message)
4581
4582   # Check that the presubmit fails if a marker line in browser_prefs.cc is
4583   # deleted.
4584   def testDeletedMarkerRaisesError(self):
4585     mock_input_api = MockInputApi()
4586     mock_input_api.files = [
4587         MockAffectedFile('chrome/browser/prefs/browser_prefs.cc',
4588                          [
4589                            '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4590                            '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
4591                            '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
4592                            # The following line is deleted for this test
4593                            # '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
4594                          ])
4595     ]
4596     mock_output_api = MockOutputApi()
4597     errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
4598                                                      mock_output_api)
4599     self.assertEqual(1, len(errors))
4600     self.assertEqual(
4601         'Broken .*MIGRATE_OBSOLETE_.*_PREFS markers in browser_prefs.cc.',
4602         errors[0].message)
4603
4604 class CheckCrosApiNeedBrowserTestTest(unittest.TestCase):
4605     def testWarning(self):
4606         mock_input_api = MockInputApi()
4607         mock_output_api = MockOutputApi()
4608         mock_input_api.files = [
4609             MockAffectedFile('chromeos/crosapi/mojom/example.mojom', [], action='A'),
4610         ]
4611         result = PRESUBMIT.CheckCrosApiNeedBrowserTest(mock_input_api, mock_output_api)
4612         self.assertEqual(1, len(result))
4613         self.assertEqual(result[0].type, 'warning')
4614
4615     def testNoWarningWithBrowserTest(self):
4616         mock_input_api = MockInputApi()
4617         mock_output_api = MockOutputApi()
4618         mock_input_api.files = [
4619             MockAffectedFile('chromeos/crosapi/mojom/example.mojom', [], action='A'),
4620             MockAffectedFile('chrome/example_browsertest.cc', [], action='A'),
4621         ]
4622         result = PRESUBMIT.CheckCrosApiNeedBrowserTest(mock_input_api, mock_output_api)
4623         self.assertEqual(0, len(result))
4624
4625     def testNoWarningModifyCrosapi(self):
4626         mock_input_api = MockInputApi()
4627         mock_output_api = MockOutputApi()
4628         mock_input_api.files = [
4629             MockAffectedFile('chromeos/crosapi/mojom/example.mojom', [], action='M'),
4630         ]
4631         result = PRESUBMIT.CheckCrosApiNeedBrowserTest(mock_input_api, mock_output_api)
4632         self.assertEqual(0, len(result))
4633
4634     def testNoWarningAddNonMojomFile(self):
4635         mock_input_api = MockInputApi()
4636         mock_output_api = MockOutputApi()
4637         mock_input_api.files = [
4638             MockAffectedFile('chromeos/crosapi/mojom/example.cc', [], action='A'),
4639         ]
4640         result = PRESUBMIT.CheckCrosApiNeedBrowserTest(mock_input_api, mock_output_api)
4641         self.assertEqual(0, len(result))
4642
4643     def testNoWarningNoneRelatedMojom(self):
4644         mock_input_api = MockInputApi()
4645         mock_output_api = MockOutputApi()
4646         mock_input_api.files = [
4647             MockAffectedFile('random/folder/example.mojom', [], action='A'),
4648         ]
4649         result = PRESUBMIT.CheckCrosApiNeedBrowserTest(mock_input_api, mock_output_api)
4650         self.assertEqual(0, len(result))
4651
4652
4653 class AssertAshOnlyCodeTest(unittest.TestCase):
4654     def testErrorsOnlyOnAshDirectories(self):
4655         files_in_ash = [
4656             MockFile('ash/BUILD.gn', []),
4657             MockFile('chrome/browser/ash/BUILD.gn', []),
4658         ]
4659         other_files = [
4660             MockFile('chrome/browser/BUILD.gn', []),
4661             MockFile('chrome/browser/BUILD.gn', ['assert(is_chromeos_ash)']),
4662         ]
4663         input_api = MockInputApi()
4664         input_api.files = files_in_ash
4665         errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4666         self.assertEqual(2, len(errors))
4667
4668         input_api.files = other_files
4669         errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4670         self.assertEqual(0, len(errors))
4671
4672     def testDoesNotErrorOnNonGNFiles(self):
4673         input_api = MockInputApi()
4674         input_api.files = [
4675             MockFile('ash/test.h', ['assert(is_chromeos_ash)']),
4676             MockFile('chrome/browser/ash/test.cc',
4677                      ['assert(is_chromeos_ash)']),
4678         ]
4679         errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4680         self.assertEqual(0, len(errors))
4681
4682     def testDeletedFile(self):
4683         input_api = MockInputApi()
4684         input_api.files = [
4685             MockFile('ash/BUILD.gn', []),
4686             MockFile('ash/foo/BUILD.gn', [], action='D'),
4687         ]
4688         errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4689         self.assertEqual(1, len(errors))
4690
4691     def testDoesNotErrorWithAssertion(self):
4692         input_api = MockInputApi()
4693         input_api.files = [
4694             MockFile('ash/BUILD.gn', ['assert(is_chromeos_ash)']),
4695             MockFile('chrome/browser/ash/BUILD.gn',
4696                      ['assert(is_chromeos_ash)']),
4697             MockFile('chrome/browser/ash/BUILD.gn',
4698                      ['assert(is_chromeos_ash, "test")']),
4699         ]
4700         errors = PRESUBMIT.CheckAssertAshOnlyCode(input_api, MockOutputApi())
4701         self.assertEqual(0, len(errors))
4702
4703
4704 class CheckRawPtrUsageTest(unittest.TestCase):
4705   def testAllowedCases(self):
4706     mock_input_api = MockInputApi()
4707     mock_input_api.files = [
4708         # Browser-side files are allowed.
4709         MockAffectedFile('test10/browser/foo.h', ['raw_ptr<int>']),
4710         MockAffectedFile('test11/browser/foo.cc', ['raw_ptr<int>']),
4711         MockAffectedFile('test12/blink/common/foo.cc', ['raw_ptr<int>']),
4712         MockAffectedFile('test13/blink/public/common/foo.cc', ['raw_ptr<int>']),
4713         MockAffectedFile('test14/blink/public/platform/foo.cc',
4714                          ['raw_ptr<int>']),
4715
4716         # Non-C++ files are allowed.
4717         MockAffectedFile('test20/renderer/foo.md', ['raw_ptr<int>']),
4718
4719         # Renderer code is generally allowed (except specifically
4720         # disallowed directories).
4721         MockAffectedFile('test30/renderer/foo.cc', ['raw_ptr<int>']),
4722     ]
4723     mock_output_api = MockOutputApi()
4724     errors = PRESUBMIT.CheckRawPtrUsage(mock_input_api, mock_output_api)
4725     self.assertFalse(errors)
4726
4727   def testDisallowedCases(self):
4728     mock_input_api = MockInputApi()
4729     mock_input_api.files = [
4730         MockAffectedFile('test1/third_party/blink/renderer/core/foo.h',
4731                          ['raw_ptr<int>']),
4732         MockAffectedFile(
4733             'test2/third_party/blink/renderer/platform/heap/foo.cc',
4734             ['raw_ptr<int>']),
4735         MockAffectedFile(
4736             'test3/third_party/blink/renderer/platform/wtf/foo.cc',
4737             ['raw_ptr<int>']),
4738         MockAffectedFile('test4/blink/public/web/foo.cc', ['raw_ptr<int>']),
4739     ]
4740     mock_output_api = MockOutputApi()
4741     errors = PRESUBMIT.CheckRawPtrUsage(mock_input_api, mock_output_api)
4742     self.assertEqual(len(mock_input_api.files), len(errors))
4743     for error in errors:
4744       self.assertTrue(
4745           'raw_ptr<T> should not be used in this renderer code' in
4746           error.message)
4747
4748 class CheckAdvancedMemorySafetyChecksUsageTest(unittest.TestCase):
4749   def testAllowedCases(self):
4750     mock_input_api = MockInputApi()
4751     mock_input_api.files = [
4752         # Non-C++ files are allowed.
4753         MockAffectedFile(
4754           'test20/renderer/foo.md', ['ADVANCED_MEMORY_SAFETY_CHECKS()']),
4755
4756         # Mentions in a comment are allowed.
4757         MockAffectedFile(
4758           'test30/renderer/foo.cc', ['//ADVANCED_MEMORY_SAFETY_CHECKS()']),
4759     ]
4760     mock_output_api = MockOutputApi()
4761     errors = PRESUBMIT.CheckAdvancedMemorySafetyChecksUsage(
4762       mock_input_api, mock_output_api)
4763     self.assertFalse(errors)
4764
4765   def testDisallowedCases(self):
4766     mock_input_api = MockInputApi()
4767     mock_input_api.files = [
4768         MockAffectedFile('test1/foo.h', ['ADVANCED_MEMORY_SAFETY_CHECKS()']),
4769         MockAffectedFile('test2/foo.cc', ['ADVANCED_MEMORY_SAFETY_CHECKS()']),
4770     ]
4771     mock_output_api = MockOutputApi()
4772     errors = PRESUBMIT.CheckAdvancedMemorySafetyChecksUsage(mock_input_api, mock_output_api)
4773     self.assertEqual(1, len(errors))
4774     self.assertTrue(
4775         'ADVANCED_MEMORY_SAFETY_CHECKS() macro is managed by' in
4776         errors[0].message)
4777
4778 class AssertPythonShebangTest(unittest.TestCase):
4779     def testError(self):
4780         input_api = MockInputApi()
4781         input_api.files = [
4782             MockFile('ash/test.py', ['#!/usr/bin/python']),
4783             MockFile('chrome/test.py', ['#!/usr/bin/python2']),
4784             MockFile('third_party/blink/test.py', ['#!/usr/bin/python3']),
4785             MockFile('empty.py', []),
4786         ]
4787         errors = PRESUBMIT.CheckPythonShebang(input_api, MockOutputApi())
4788         self.assertEqual(3, len(errors))
4789
4790     def testNonError(self):
4791         input_api = MockInputApi()
4792         input_api.files = [
4793             MockFile('chrome/browser/BUILD.gn', ['#!/usr/bin/python']),
4794             MockFile('third_party/blink/web_tests/external/test.py',
4795                      ['#!/usr/bin/python2']),
4796             MockFile('third_party/test/test.py', ['#!/usr/bin/python3']),
4797         ]
4798         errors = PRESUBMIT.CheckPythonShebang(input_api, MockOutputApi())
4799         self.assertEqual(0, len(errors))
4800
4801 class VerifyDcheckParentheses(unittest.TestCase):
4802   def testPermissibleUsage(self):
4803     input_api = MockInputApi()
4804     input_api.files = [
4805       MockFile('okay1.cc', ['DCHECK_IS_ON()']),
4806       MockFile('okay2.cc', ['#if DCHECK_IS_ON()']),
4807
4808       # Other constructs that aren't exactly `DCHECK_IS_ON()` do their
4809       # own thing at their own risk.
4810       MockFile('okay3.cc', ['PA_DCHECK_IS_ON']),
4811       MockFile('okay4.cc', ['#if PA_DCHECK_IS_ON']),
4812       MockFile('okay6.cc', ['BUILDFLAG(PA_DCHECK_IS_ON)']),
4813     ]
4814     errors = PRESUBMIT.CheckDCHECK_IS_ONHasBraces(input_api, MockOutputApi())
4815     self.assertEqual(0, len(errors))
4816
4817   def testMissingParentheses(self):
4818     input_api = MockInputApi()
4819     input_api.files = [
4820       MockFile('bad1.cc', ['DCHECK_IS_ON']),
4821       MockFile('bad2.cc', ['#if DCHECK_IS_ON']),
4822       MockFile('bad3.cc', ['DCHECK_IS_ON && foo']),
4823     ]
4824     errors = PRESUBMIT.CheckDCHECK_IS_ONHasBraces(input_api, MockOutputApi())
4825     self.assertEqual(3, len(errors))
4826     for error in errors:
4827       self.assertRegex(error.message, r'DCHECK_IS_ON().+parentheses')
4828
4829
4830 class CheckBatchAnnotation(unittest.TestCase):
4831   """Test the CheckBatchAnnotation presubmit check."""
4832
4833   def testTruePositives(self):
4834     """Examples of when there is no @Batch or @DoNotBatch is correctly flagged.
4835 """
4836     mock_input = MockInputApi()
4837     mock_input.files = [
4838         MockFile('path/OneTest.java', ['public class OneTest']),
4839         MockFile('path/TwoTest.java', ['public class TwoTest']),
4840         MockFile('path/ThreeTest.java',
4841                  ['@Batch(Batch.PER_CLASS)',
4842                   'import org.chromium.base.test.BaseRobolectricTestRunner;',
4843                   'public class Three {']),
4844         MockFile('path/FourTest.java',
4845                  ['@DoNotBatch(reason = "placeholder reason 1")',
4846                   'import org.chromium.base.test.BaseRobolectricTestRunner;',
4847                   'public class Four {']),
4848     ]
4849     errors = PRESUBMIT.CheckBatchAnnotation(mock_input, MockOutputApi())
4850     self.assertEqual(2, len(errors))
4851     self.assertEqual(2, len(errors[0].items))
4852     self.assertIn('OneTest.java', errors[0].items[0])
4853     self.assertIn('TwoTest.java', errors[0].items[1])
4854     self.assertEqual(2, len(errors[1].items))
4855     self.assertIn('ThreeTest.java', errors[1].items[0])
4856     self.assertIn('FourTest.java', errors[1].items[1])
4857
4858
4859   def testAnnotationsPresent(self):
4860     """Examples of when there is @Batch or @DoNotBatch is correctly flagged."""
4861     mock_input = MockInputApi()
4862     mock_input.files = [
4863         MockFile('path/OneTest.java',
4864                  ['@Batch(Batch.PER_CLASS)', 'public class One {']),
4865         MockFile('path/TwoTest.java',
4866                  ['@DoNotBatch(reason = "placeholder reasons.")', 'public class Two {'
4867                  ]),
4868         MockFile('path/ThreeTest.java',
4869                  ['@Batch(Batch.PER_CLASS)',
4870                   'public class Three extends BaseTestA {'],
4871                  ['@Batch(Batch.PER_CLASS)',
4872                   'public class Three extends BaseTestB {']),
4873         MockFile('path/FourTest.java',
4874                  ['@DoNotBatch(reason = "placeholder reason 1")',
4875                   'public class Four extends BaseTestA {'],
4876                  ['@DoNotBatch(reason = "placeholder reason 2")',
4877                   'public class Four extends BaseTestB {']),
4878         MockFile('path/FiveTest.java',
4879                  ['import androidx.test.uiautomator.UiDevice;',
4880                   'public class Five extends BaseTestA {'],
4881                  ['import androidx.test.uiautomator.UiDevice;',
4882                   'public class Five extends BaseTestB {']),
4883         MockFile('path/SixTest.java',
4884                  ['import org.chromium.base.test.BaseRobolectricTestRunner;',
4885                   'public class Six extends BaseTestA {'],
4886                  ['import org.chromium.base.test.BaseRobolectricTestRunner;',
4887                   'public class Six extends BaseTestB {']),
4888         MockFile('path/SevenTest.java',
4889                  ['import org.robolectric.annotation.Config;',
4890                   'public class Seven extends BaseTestA {'],
4891                  ['import org.robolectric.annotation.Config;',
4892                   'public class Seven extends BaseTestB {']),
4893         MockFile(
4894             'path/OtherClass.java',
4895             ['public class OtherClass {'],
4896         ),
4897         MockFile('path/PRESUBMIT.py',
4898                  ['@Batch(Batch.PER_CLASS)',
4899                   '@DoNotBatch(reason = "placeholder reason)']),
4900         MockFile('path/AnnotationTest.java',
4901           ['public @interface SomeAnnotation {'],),
4902     ]
4903     errors = PRESUBMIT.CheckBatchAnnotation(mock_input, MockOutputApi())
4904     self.assertEqual(0, len(errors))
4905
4906
4907 class CheckMockAnnotation(unittest.TestCase):
4908     """Test the CheckMockAnnotation presubmit check."""
4909
4910     def testTruePositives(self):
4911         """Examples of @Mock or @Spy being used and nothing should be flagged."""
4912         mock_input = MockInputApi()
4913         mock_input.files = [
4914             MockFile('path/OneTest.java', [
4915                 'import a.b.c.Bar;',
4916                 'import a.b.c.Foo;',
4917                 '@Mock public static Foo f = new Foo();',
4918                 'Mockito.mock(new Bar(a, b, c))'
4919             ]),
4920             MockFile('path/TwoTest.java', [
4921                 'package x.y.z;',
4922                 'import static org.mockito.Mockito.spy;',
4923                 '@Spy',
4924                 'public static FooBar<Baz> f;',
4925                 'a = spy(Baz.class)'
4926             ]),
4927         ]
4928         errors = PRESUBMIT.CheckMockAnnotation(mock_input, MockOutputApi())
4929         self.assertEqual(1, len(errors))
4930         self.assertEqual(2, len(errors[0].items))
4931         self.assertIn('a.b.c.Bar in path/OneTest.java', errors[0].items)
4932         self.assertIn('x.y.z.Baz in path/TwoTest.java', errors[0].items)
4933
4934     def testTrueNegatives(self):
4935         """Examples of when we should not be flagging mock() or spy() calls."""
4936         mock_input = MockInputApi()
4937         mock_input.files = [
4938             MockFile('path/OneTest.java', [
4939                 'package a.b.c;',
4940                 'import org.chromium.base.test.BaseRobolectricTestRunner;',
4941                 'Mockito.mock(Abc.class)'
4942             ]),
4943             MockFile('path/TwoTest.java', [
4944                 'package a.b.c;',
4945                 'import androidx.test.uiautomator.UiDevice;',
4946                 'Mockito.spy(new Def())'
4947             ]),
4948             MockFile('path/ThreeTest.java', [
4949                 'package a.b.c;',
4950                 'import static org.mockito.Mockito.spy;',
4951                 '@Spy',
4952                 'public static Foo f = new Abc();',
4953                 'a = spy(Foo.class)'
4954             ]),
4955             MockFile('path/FourTest.java', [
4956                 'package a.b.c;',
4957                 'import static org.mockito.Mockito.mock;',
4958                 '@Spy',
4959                 'public static Bar b = new Abc(a, b, c, d);',
4960                 ' mock(new Bar(a,b,c))'
4961             ]),
4962             MockFile('path/FiveTest.java', [
4963                 'package a.b.c;',
4964                 '@Mock',
4965                 'public static Baz<abc> b;',
4966                 'Mockito.mock(Baz.class)'
4967             ]),
4968             MockFile('path/SixTest.java', [
4969                 'package a.b.c;',
4970                 'import android.view.View;',
4971                 'import java.ArrayList;',
4972                 'Mockito.spy(new View())',
4973                 'Mockito.mock(ArrayList.class)'
4974             ]),
4975             MockFile('path/SevenTest.java', [
4976                 'package a.b.c;',
4977                 '@Mock private static Seven s;',
4978                 'Mockito.mock(Seven.class)'
4979             ]),
4980             MockFile('path/EightTest.java', [
4981                 'package a.b.c;',
4982                 '@Spy Eight e = new Eight2();',
4983                 'Mockito.py(new Eight())'
4984             ]),
4985         ]
4986         errors = PRESUBMIT.CheckMockAnnotation(mock_input, MockOutputApi())
4987         self.assertEqual(0, len(errors))
4988
4989
4990 class LayoutInTestsTest(unittest.TestCase):
4991   def testLayoutInTest(self):
4992     mock_input = MockInputApi()
4993     mock_input.files = [
4994         MockFile('path/to/foo_unittest.cc',
4995                  ['  foo->Layout();', '  bar.Layout();']),
4996     ]
4997     errors = PRESUBMIT.CheckNoLayoutCallsInTests(mock_input, MockOutputApi())
4998     self.assertNotEqual(0, len(errors))
4999
5000   def testNoTriggerOnLayoutOverride(self):
5001     mock_input = MockInputApi();
5002     mock_input.files = [
5003         MockFile('path/to/foo_unittest.cc',
5004                  ['class TestView: public views::View {',
5005                   ' public:',
5006                   '  void Layout(); override {',
5007                   '    views::View::Layout();',
5008                   '    // perform bespoke layout',
5009                   '  }',
5010                   '};'])
5011     ]
5012     errors = PRESUBMIT.CheckNoLayoutCallsInTests(mock_input, MockOutputApi())
5013     self.assertEqual(0, len(errors))
5014
5015 class AssertNoJsInIosTest(unittest.TestCase):
5016     def testErrorJs(self):
5017         input_api = MockInputApi()
5018         input_api.files = [
5019             MockFile('components/feature/ios/resources/script.js', []),
5020             MockFile('ios/chrome/feature/resources/script.js', []),
5021         ]
5022         results = PRESUBMIT.CheckNoJsInIos(input_api, MockOutputApi())
5023         self.assertEqual(1, len(results))
5024         self.assertEqual('error', results[0].type)
5025         self.assertEqual(2, len(results[0].items))
5026
5027     def testNonError(self):
5028         input_api = MockInputApi()
5029         input_api.files = [
5030             MockFile('chrome/resources/script.js', []),
5031             MockFile('components/feature/ios/resources/script.ts', []),
5032             MockFile('ios/chrome/feature/resources/script.ts', []),
5033             MockFile('ios/web/feature/resources/script.ts', []),
5034             MockFile('ios/third_party/script.js', []),
5035             MockFile('third_party/ios/script.js', []),
5036         ]
5037         results = PRESUBMIT.CheckNoJsInIos(input_api, MockOutputApi())
5038         self.assertEqual(0, len(results))
5039
5040     def testExistingFilesWarningOnly(self):
5041         input_api = MockInputApi()
5042         input_api.files = [
5043             MockFile('ios/chrome/feature/resources/script.js', [], action='M'),
5044             MockFile('ios/chrome/feature/resources/script2.js', [], action='D'),
5045         ]
5046         results = PRESUBMIT.CheckNoJsInIos(input_api, MockOutputApi())
5047         self.assertEqual(1, len(results))
5048         self.assertEqual('warning', results[0].type)
5049         self.assertEqual(1, len(results[0].items))
5050
5051     def testMovedScriptWarningOnly(self):
5052         input_api = MockInputApi()
5053         input_api.files = [
5054             MockFile('ios/chrome/feature/resources/script.js', [], action='D'),
5055             MockFile('ios/chrome/renamed_feature/resources/script.js', [], action='A'),
5056         ]
5057         results = PRESUBMIT.CheckNoJsInIos(input_api, MockOutputApi())
5058         self.assertEqual(1, len(results))
5059         self.assertEqual('warning', results[0].type)
5060         self.assertEqual(1, len(results[0].items))
5061
5062 class CheckNoAbbreviationInPngFileNameTest(unittest.TestCase):
5063   def testHasAbbreviation(self):
5064     """test png file names with abbreviation that fails the check"""
5065     input_api = MockInputApi()
5066     input_api.files = [
5067       MockFile('image_a.png', [], action='A'),
5068       MockFile('image_a_.png', [], action='A'),
5069       MockFile('image_a_name.png', [], action='A'),
5070       MockFile('chrome/ui/feature_name/resources/image_a.png', [], action='A'),
5071       MockFile('chrome/ui/feature_name/resources/image_a_.png', [], action='A'),
5072       MockFile('chrome/ui/feature_name/resources/image_a_name.png', [], action='A'),
5073     ]
5074     results = PRESUBMIT.CheckNoAbbreviationInPngFileName(input_api, MockOutputApi())
5075     self.assertEqual(1, len(results))
5076     self.assertEqual('error', results[0].type)
5077     self.assertEqual(len(input_api.files), len(results[0].items))
5078
5079   def testNoAbbreviation(self):
5080     """test png file names without abbreviation that passes the check"""
5081     input_api = MockInputApi()
5082     input_api.files = [
5083       MockFile('a.png', [], action='A'),
5084       MockFile('_a.png', [], action='A'),
5085       MockFile('image.png', [], action='A'),
5086       MockFile('image_ab_.png', [], action='A'),
5087       MockFile('image_ab_name.png', [], action='A'),
5088       # These paths used to fail because `feature_a_name` matched the regex by mistake.
5089       # They should pass now because the path components ahead of the file name are ignored in the check.
5090       MockFile('chrome/ui/feature_a_name/resources/a.png', [], action='A'),
5091       MockFile('chrome/ui/feature_a_name/resources/_a.png', [], action='A'),
5092       MockFile('chrome/ui/feature_a_name/resources/image.png', [], action='A'),
5093       MockFile('chrome/ui/feature_a_name/resources/image_ab_.png', [], action='A'),
5094       MockFile('chrome/ui/feature_a_name/resources/image_ab_name.png', [], action='A'),
5095     ]
5096     results = PRESUBMIT.CheckNoAbbreviationInPngFileName(input_api, MockOutputApi())
5097     self.assertEqual(0, len(results))
5098
5099 class CheckDanglingUntriagedTest(unittest.TestCase):
5100   def testError(self):
5101     """Test patch adding dangling pointers are reported"""
5102     mock_input_api = MockInputApi()
5103     mock_output_api = MockOutputApi()
5104
5105     mock_input_api.change.DescriptionText = lambda: "description"
5106     mock_input_api.files = [
5107       MockAffectedFile(
5108         local_path="foo/foo.cc",
5109         old_contents="raw_ptr<T>",
5110         new_contents="raw_ptr<T, DanglingUntriaged>",
5111       )
5112     ]
5113     msgs = PRESUBMIT.CheckDanglingUntriaged(mock_input_api, mock_output_api)
5114     self.assertEqual(len(msgs), 1)
5115     self.assertEqual(len(msgs[0].message), 10)
5116     self.assertEqual(
5117       msgs[0].message[0],
5118       "Unexpected new occurrences of `DanglingUntriaged` detected. Please",
5119     )
5120
5121 class CheckDanglingUntriagedTest(unittest.TestCase):
5122   def testError(self):
5123     """Test patch adding dangling pointers are reported"""
5124     mock_input_api = MockInputApi()
5125     mock_output_api = MockOutputApi()
5126
5127     mock_input_api.change.DescriptionText = lambda: "description"
5128     mock_input_api.files = [
5129       MockAffectedFile(
5130         local_path="foo/foo.cc",
5131         old_contents="raw_ptr<T>",
5132         new_contents="raw_ptr<T, DanglingUntriaged>",
5133       )
5134     ]
5135     msgs = PRESUBMIT.CheckDanglingUntriaged(mock_input_api,
5136                         mock_output_api)
5137     self.assertEqual(len(msgs), 1)
5138     self.assertEqual(len(msgs[0].message), 11)
5139     self.assertEqual(
5140       msgs[0].message[0],
5141       "Unexpected new occurrences of `DanglingUntriaged` detected. Please",
5142     )
5143
5144   def testNonCppFile(self):
5145     """Test patch adding dangling pointers are not reported in non C++ files"""
5146     mock_input_api = MockInputApi()
5147     mock_output_api = MockOutputApi()
5148
5149     mock_input_api.change.DescriptionText = lambda: "description"
5150     mock_input_api.files = [
5151       MockAffectedFile(
5152         local_path="foo/README.md",
5153         old_contents="",
5154         new_contents="The DanglingUntriaged annotation means",
5155       )
5156     ]
5157     msgs = PRESUBMIT.CheckDanglingUntriaged(mock_input_api,
5158                         mock_output_api)
5159     self.assertEqual(len(msgs), 0)
5160
5161   def testDeveloperAcknowledgeInCommitDescription(self):
5162     """Test patch adding dangling pointers, but acknowledged by the developers
5163     aren't reported"""
5164     mock_input_api = MockInputApi()
5165     mock_output_api = MockOutputApi()
5166
5167     mock_input_api.files = [
5168       MockAffectedFile(
5169         local_path="foo/foo.cc",
5170         old_contents="raw_ptr<T>",
5171         new_contents="raw_ptr<T, DanglingUntriaged>",
5172       )
5173     ]
5174     mock_input_api.change.DescriptionText = lambda: (
5175       "DanglingUntriaged-notes: Sorry about this!")
5176     msgs = PRESUBMIT.CheckDanglingUntriaged(mock_input_api,
5177                         mock_output_api)
5178     self.assertEqual(len(msgs), 0)
5179
5180   def testDeveloperAcknowledgeInCommitFooter(self):
5181     """Test patch adding dangling pointers, but acknowledged by the developers
5182     aren't reported"""
5183     mock_input_api = MockInputApi()
5184     mock_output_api = MockOutputApi()
5185
5186     mock_input_api.files = [
5187       MockAffectedFile(
5188         local_path="foo/foo.cc",
5189         old_contents="raw_ptr<T>",
5190         new_contents="raw_ptr<T, DanglingUntriaged>",
5191       )
5192     ]
5193     mock_input_api.change.DescriptionText = lambda: "description"
5194     mock_input_api.change.footers["DanglingUntriaged-notes"] = ["Sorry!"]
5195     msgs = PRESUBMIT.CheckDanglingUntriaged(mock_input_api,
5196                         mock_output_api)
5197     self.assertEqual(len(msgs), 0)
5198
5199   def testCongrats(self):
5200     """Test the presubmit congrats users removing dangling pointers"""
5201     mock_input_api = MockInputApi()
5202     mock_output_api = MockOutputApi()
5203
5204     mock_input_api.files = [
5205       MockAffectedFile(
5206         local_path="foo/foo.cc",
5207         old_contents="raw_ptr<T, DanglingUntriaged>",
5208         new_contents="raw_ptr<T>",
5209       )
5210     ]
5211     mock_input_api.change.DescriptionText = lambda: (
5212       "This patch fixes some DanglingUntriaged pointers!")
5213     msgs = PRESUBMIT.CheckDanglingUntriaged(mock_input_api,
5214                         mock_output_api)
5215     self.assertEqual(len(msgs), 1)
5216     self.assertTrue(
5217       "DanglingUntriaged pointers removed: 1" in msgs[0].message)
5218     self.assertTrue("Thank you!" in msgs[0].message)
5219
5220   def testRenameFile(self):
5221     """Patch that we do not warn about DanglingUntriaged when moving files"""
5222     mock_input_api = MockInputApi()
5223     mock_output_api = MockOutputApi()
5224
5225     mock_input_api.files = [
5226       MockAffectedFile(
5227         local_path="foo/foo.cc",
5228         old_contents="raw_ptr<T, DanglingUntriaged>",
5229         new_contents="",
5230         action="D",
5231       ),
5232       MockAffectedFile(
5233         local_path="foo/foo.cc",
5234         old_contents="",
5235         new_contents="raw_ptr<T, DanglingUntriaged>",
5236         action="A",
5237       ),
5238     ]
5239     mock_input_api.change.DescriptionText = lambda: (
5240       "This patch moves files")
5241     msgs = PRESUBMIT.CheckDanglingUntriaged(mock_input_api,
5242                         mock_output_api)
5243     self.assertEqual(len(msgs), 0)
5244
5245 class CheckInlineConstexprDefinitionsInHeadersTest(unittest.TestCase):
5246   def testNoInlineConstexprInHeaderFile(self):
5247     """Tests that non-inlined constexpr variables in headers fail the test."""
5248     input_api = MockInputApi()
5249     input_api.files = [
5250       MockAffectedFile('src/constants.h', ['constexpr int kVersion = 5;'])
5251     ]
5252     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5253     self.assertEqual(1, len(warnings))
5254
5255   def testNoInlineConstexprInHeaderFileInitializedFromFunction(self):
5256     """Tests that non-inlined constexpr header variables that are initialized from a function fail."""
5257     input_api = MockInputApi()
5258     input_api.files = [
5259       MockAffectedFile('src/constants.h', ['constexpr int kVersion = GetVersion();'])
5260     ]
5261     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5262     self.assertEqual(1, len(warnings))
5263
5264   def testNoInlineConstexprInHeaderFileInitializedWithExpression(self):
5265     """Tests that non-inlined constexpr header variables initialized with an expression fail."""
5266     input_api = MockInputApi()
5267     input_api.files = [
5268       MockAffectedFile('src/constants.h', ['constexpr int kVersion = (4 + 5)*3;'])
5269     ]
5270     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5271     self.assertEqual(1, len(warnings))
5272
5273   def testNoInlineConstexprInHeaderFileBraceInitialized(self):
5274     """Tests that non-inlined constexpr header variables that are brace-initialized fail."""
5275     input_api = MockInputApi()
5276     input_api.files = [
5277       MockAffectedFile('src/constants.h', ['constexpr int kVersion{5};'])
5278     ]
5279     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5280     self.assertEqual(1, len(warnings))
5281
5282   def testNoInlineConstexprInHeaderWithAttribute(self):
5283     """Tests that non-inlined constexpr header variables that have compiler attributes fail."""
5284     input_api = MockInputApi()
5285     input_api.files = [
5286       MockAffectedFile('src/constants.h', ['constexpr [[maybe_unused]] int kVersion{5};'])
5287     ]
5288     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5289     self.assertEqual(1, len(warnings))
5290
5291   def testInlineConstexprInHeaderWithAttribute(self):
5292     """Tests that inlined constexpr header variables that have compiler attributes pass."""
5293     input_api = MockInputApi()
5294     input_api.files = [
5295       MockAffectedFile('src/constants.h', ['inline constexpr [[maybe_unused]] int kVersion{5};']),
5296       MockAffectedFile('src/constants.h', ['constexpr inline [[maybe_unused]] int kVersion{5};']),
5297       MockAffectedFile('src/constants.h', ['inline constexpr [[maybe_unused]] inline int kVersion{5};'])
5298     ]
5299     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5300     self.assertEqual(0, len(warnings))
5301
5302   def testNoInlineConstexprInHeaderFileMultipleLines(self):
5303     """Tests that non-inlined constexpr header variable definitions spanning multiple lines fail."""
5304     input_api = MockInputApi()
5305     lines = ['constexpr char kLongName =',
5306              '    "This is a very long name of something.";'
5307              ]
5308     input_api.files = [MockAffectedFile('src/constants.h', lines)]
5309     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5310     self.assertEqual(1, len(warnings))
5311
5312   def testNoInlineConstexprInCCFile(self):
5313     """Tests that non-inlined constexpr variables in .cc files pass the test."""
5314     input_api = MockInputApi()
5315     input_api.files = [
5316       MockAffectedFile('src/implementation.cc', ['constexpr int kVersion = 5;'])
5317     ]
5318     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5319     self.assertEqual(0, len(warnings))
5320
5321   def testInlineConstexprInHeaderFile(self):
5322     """Tests that inlined constexpr variables in header files pass the test."""
5323     input_api = MockInputApi()
5324     input_api.files = [
5325       MockAffectedFile('src/constants.h', ['constexpr inline int kX = 5;']),
5326       MockAffectedFile('src/version.h', ['inline constexpr float kY = 5.0f;'])
5327     ]
5328     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5329     self.assertEqual(0, len(warnings))
5330
5331   def testConstexprStandaloneFunctionInHeaderFile(self):
5332     """Tests that non-inlined constexpr functions in headers pass the test."""
5333     input_api = MockInputApi()
5334     input_api.files = [
5335       MockAffectedFile('src/helpers.h', ['constexpr int GetVersion();'])
5336     ]
5337     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5338     self.assertEqual(0, len(warnings))
5339
5340   def testConstexprWithAbseilAttributeInHeader(self):
5341     """Tests that non-inlined constexpr variables with Abseil-type prefixes in headers fail."""
5342     input_api = MockInputApi()
5343     input_api.files = [
5344       MockAffectedFile('src/helpers.h', ['ABSL_FOOFOO constexpr int i = 5;'])
5345     ]
5346     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5347     self.assertEqual(1, len(warnings))
5348
5349   def testInlineConstexprWithAbseilAttributeInHeader(self):
5350     """Tests that inlined constexpr variables with Abseil-type prefixes in headers pass."""
5351     input_api = MockInputApi()
5352     input_api.files = [
5353       MockAffectedFile('src/helpers.h', ['constexpr ABSL_FOO inline int i = 5;'])
5354     ]
5355     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5356     self.assertEqual(0, len(warnings))
5357
5358   def testConstexprWithClangAttributeInHeader(self):
5359     """Tests that non-inlined constexpr variables with attributes with colons in headers fail."""
5360     input_api = MockInputApi()
5361     input_api.files = [
5362       MockAffectedFile('src/helpers.h', ['[[clang::someattribute]] constexpr int i = 5;'])
5363     ]
5364     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5365     self.assertEqual(1, len(warnings))
5366
5367   def testInlineConstexprWithClangAttributeInHeader(self):
5368     """Tests that inlined constexpr variables with attributes with colons in headers pass."""
5369     input_api = MockInputApi()
5370     input_api.files = [
5371       MockAffectedFile('src/helpers.h', ['constexpr [[clang::someattribute]] inline int i = 5;'])
5372     ]
5373     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5374     self.assertEqual(0, len(warnings))
5375
5376   def testNoExplicitInlineConstexprInsideClassInHeaderFile(self):
5377     """Tests that non-inlined constexpr class members pass the test."""
5378     input_api = MockInputApi()
5379     lines = ['class SomeClass {',
5380              ' public:',
5381              '  static constexpr kVersion = 5;',
5382              '};']
5383     input_api.files = [
5384       MockAffectedFile('src/class.h', lines)
5385     ]
5386     warnings = PRESUBMIT.CheckInlineConstexprDefinitionsInHeaders(input_api, MockOutputApi())
5387     self.assertEqual(0, len(warnings))
5388
5389 if __name__ == '__main__':
5390   unittest.main()