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