Upload upstream chromium 94.0.4606.31
[platform/framework/web/chromium-efl.git] / PRESUBMIT_test.py
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import os.path
7 import subprocess
8 import unittest
9
10 import PRESUBMIT
11
12 from PRESUBMIT_test_mocks import MockFile, MockAffectedFile
13 from PRESUBMIT_test_mocks import MockInputApi, MockOutputApi
14
15
16 _TEST_DATA_DIR = 'base/test/data/presubmit'
17
18
19 class VersionControlConflictsTest(unittest.TestCase):
20   def testTypicalConflict(self):
21     lines = ['<<<<<<< HEAD',
22              '  base::ScopedTempDir temp_dir_;',
23              '=======',
24              '  ScopedTempDir temp_dir_;',
25              '>>>>>>> master']
26     errors = PRESUBMIT._CheckForVersionControlConflictsInFile(
27         MockInputApi(), MockFile('some/path/foo_platform.cc', lines))
28     self.assertEqual(3, len(errors))
29     self.assertTrue('1' in errors[0])
30     self.assertTrue('3' in errors[1])
31     self.assertTrue('5' in errors[2])
32
33   def testIgnoresReadmes(self):
34     lines = ['A First Level Header',
35              '====================',
36              '',
37              'A Second Level Header',
38              '---------------------']
39     errors = PRESUBMIT._CheckForVersionControlConflictsInFile(
40         MockInputApi(), MockFile('some/polymer/README.md', lines))
41     self.assertEqual(0, len(errors))
42
43
44 class BadExtensionsTest(unittest.TestCase):
45   def testBadRejFile(self):
46     mock_input_api = MockInputApi()
47     mock_input_api.files = [
48       MockFile('some/path/foo.cc', ''),
49       MockFile('some/path/foo.cc.rej', ''),
50       MockFile('some/path2/bar.h.rej', ''),
51     ]
52
53     results = PRESUBMIT.CheckPatchFiles(mock_input_api, MockOutputApi())
54     self.assertEqual(1, len(results))
55     self.assertEqual(2, len(results[0].items))
56     self.assertTrue('foo.cc.rej' in results[0].items[0])
57     self.assertTrue('bar.h.rej' in results[0].items[1])
58
59   def testBadOrigFile(self):
60     mock_input_api = MockInputApi()
61     mock_input_api.files = [
62       MockFile('other/path/qux.h.orig', ''),
63       MockFile('other/path/qux.h', ''),
64       MockFile('other/path/qux.cc', ''),
65     ]
66
67     results = PRESUBMIT.CheckPatchFiles(mock_input_api, MockOutputApi())
68     self.assertEqual(1, len(results))
69     self.assertEqual(1, len(results[0].items))
70     self.assertTrue('qux.h.orig' in results[0].items[0])
71
72   def testGoodFiles(self):
73     mock_input_api = MockInputApi()
74     mock_input_api.files = [
75       MockFile('other/path/qux.h', ''),
76       MockFile('other/path/qux.cc', ''),
77     ]
78     results = PRESUBMIT.CheckPatchFiles(mock_input_api, MockOutputApi())
79     self.assertEqual(0, len(results))
80
81
82 class CheckForSuperfluousStlIncludesInHeadersTest(unittest.TestCase):
83   def testGoodFiles(self):
84     mock_input_api = MockInputApi()
85     mock_input_api.files = [
86       # The check is not smart enough to figure out which definitions correspond
87       # to which header.
88       MockFile('other/path/foo.h',
89                ['#include <string>',
90                 'std::vector']),
91       # The check is not smart enough to do IWYU.
92       MockFile('other/path/bar.h',
93                ['#include "base/check.h"',
94                 'std::vector']),
95       MockFile('other/path/qux.h',
96                ['#include "base/stl_util.h"',
97                 'foobar']),
98       MockFile('other/path/baz.h',
99                ['#include "set/vector.h"',
100                 'bazzab']),
101       # The check is only for header files.
102       MockFile('other/path/not_checked.cc',
103                ['#include <vector>',
104                 'bazbaz']),
105     ]
106     results = PRESUBMIT.CheckForSuperfluousStlIncludesInHeaders(
107         mock_input_api, MockOutputApi())
108     self.assertEqual(0, len(results))
109
110   def testBadFiles(self):
111     mock_input_api = MockInputApi()
112     mock_input_api.files = [
113       MockFile('other/path/foo.h',
114                ['#include <vector>',
115                 'vector']),
116       MockFile('other/path/bar.h',
117                ['#include <limits>',
118                 '#include <set>',
119                 'no_std_namespace']),
120     ]
121     results = PRESUBMIT.CheckForSuperfluousStlIncludesInHeaders(
122         mock_input_api, MockOutputApi())
123     self.assertEqual(1, len(results))
124     self.assertTrue('foo.h: Includes STL' in results[0].message)
125     self.assertTrue('bar.h: Includes STL' in results[0].message)
126
127
128 class CheckSingletonInHeadersTest(unittest.TestCase):
129   def testSingletonInArbitraryHeader(self):
130     diff_singleton_h = ['base::subtle::AtomicWord '
131                         'base::Singleton<Type, Traits, DifferentiatingType>::']
132     diff_foo_h = ['// base::Singleton<Foo> in comment.',
133                   'friend class base::Singleton<Foo>']
134     diff_foo2_h = ['  //Foo* bar = base::Singleton<Foo>::get();']
135     diff_bad_h = ['Foo* foo = base::Singleton<Foo>::get();']
136     mock_input_api = MockInputApi()
137     mock_input_api.files = [MockAffectedFile('base/memory/singleton.h',
138                                              diff_singleton_h),
139                             MockAffectedFile('foo.h', diff_foo_h),
140                             MockAffectedFile('foo2.h', diff_foo2_h),
141                             MockAffectedFile('bad.h', diff_bad_h)]
142     warnings = PRESUBMIT.CheckSingletonInHeaders(mock_input_api,
143                                                   MockOutputApi())
144     self.assertEqual(1, len(warnings))
145     self.assertEqual(1, len(warnings[0].items))
146     self.assertEqual('error', warnings[0].type)
147     self.assertTrue('Found base::Singleton<T>' in warnings[0].message)
148
149   def testSingletonInCC(self):
150     diff_cc = ['Foo* foo = base::Singleton<Foo>::get();']
151     mock_input_api = MockInputApi()
152     mock_input_api.files = [MockAffectedFile('some/path/foo.cc', diff_cc)]
153     warnings = PRESUBMIT.CheckSingletonInHeaders(mock_input_api,
154                                                   MockOutputApi())
155     self.assertEqual(0, len(warnings))
156
157
158 class InvalidOSMacroNamesTest(unittest.TestCase):
159   def testInvalidOSMacroNames(self):
160     lines = ['#if defined(OS_WINDOWS)',
161              ' #elif defined(OS_WINDOW)',
162              ' # if defined(OS_MAC) || defined(OS_CHROME)',
163              '# else  // defined(OS_MACOSX)',
164              '#endif  // defined(OS_MACOS)']
165     errors = PRESUBMIT._CheckForInvalidOSMacrosInFile(
166         MockInputApi(), MockFile('some/path/foo_platform.cc', lines))
167     self.assertEqual(len(lines), len(errors))
168     self.assertTrue(':1 OS_WINDOWS' in errors[0])
169     self.assertTrue('(did you mean OS_WIN?)' in errors[0])
170
171   def testValidOSMacroNames(self):
172     lines = ['#if defined(%s)' % m for m in PRESUBMIT._VALID_OS_MACROS]
173     errors = PRESUBMIT._CheckForInvalidOSMacrosInFile(
174         MockInputApi(), MockFile('some/path/foo_platform.cc', lines))
175     self.assertEqual(0, len(errors))
176
177
178 class InvalidIfDefinedMacroNamesTest(unittest.TestCase):
179   def testInvalidIfDefinedMacroNames(self):
180     lines = ['#if defined(TARGET_IPHONE_SIMULATOR)',
181              '#if !defined(TARGET_IPHONE_SIMULATOR)',
182              '#elif defined(TARGET_IPHONE_SIMULATOR)',
183              '#ifdef TARGET_IPHONE_SIMULATOR',
184              ' # ifdef TARGET_IPHONE_SIMULATOR',
185              '# if defined(VALID) || defined(TARGET_IPHONE_SIMULATOR)',
186              '# else  // defined(TARGET_IPHONE_SIMULATOR)',
187              '#endif  // defined(TARGET_IPHONE_SIMULATOR)']
188     errors = PRESUBMIT._CheckForInvalidIfDefinedMacrosInFile(
189         MockInputApi(), MockFile('some/path/source.mm', lines))
190     self.assertEqual(len(lines), len(errors))
191
192   def testValidIfDefinedMacroNames(self):
193     lines = ['#if defined(FOO)',
194              '#ifdef BAR']
195     errors = PRESUBMIT._CheckForInvalidIfDefinedMacrosInFile(
196         MockInputApi(), MockFile('some/path/source.cc', lines))
197     self.assertEqual(0, len(errors))
198
199
200 class CheckAddedDepsHaveTestApprovalsTest(unittest.TestCase):
201
202   def calculate(self, old_include_rules, old_specific_include_rules,
203                 new_include_rules, new_specific_include_rules):
204     return PRESUBMIT._CalculateAddedDeps(
205         os.path, 'include_rules = %r\nspecific_include_rules = %r' % (
206             old_include_rules, old_specific_include_rules),
207         'include_rules = %r\nspecific_include_rules = %r' % (
208             new_include_rules, new_specific_include_rules))
209
210   def testCalculateAddedDeps(self):
211     old_include_rules = [
212         '+base',
213         '-chrome',
214         '+content',
215         '-grit',
216         '-grit/",',
217         '+jni/fooblat.h',
218         '!sandbox',
219     ]
220     old_specific_include_rules = {
221         'compositor\.*': {
222             '+cc',
223         },
224     }
225
226     new_include_rules = [
227         '-ash',
228         '+base',
229         '+chrome',
230         '+components',
231         '+content',
232         '+grit',
233         '+grit/generated_resources.h",',
234         '+grit/",',
235         '+jni/fooblat.h',
236         '+policy',
237         '+' + os.path.join('third_party', 'WebKit'),
238     ]
239     new_specific_include_rules = {
240         'compositor\.*': {
241             '+cc',
242         },
243         'widget\.*': {
244             '+gpu',
245         },
246     }
247
248     expected = set([
249         os.path.join('chrome', 'DEPS'),
250         os.path.join('gpu', 'DEPS'),
251         os.path.join('components', 'DEPS'),
252         os.path.join('policy', 'DEPS'),
253         os.path.join('third_party', 'WebKit', 'DEPS'),
254     ])
255     self.assertEqual(
256         expected,
257         self.calculate(old_include_rules, old_specific_include_rules,
258                        new_include_rules, new_specific_include_rules))
259
260   def testCalculateAddedDepsIgnoresPermutations(self):
261     old_include_rules = [
262         '+base',
263         '+chrome',
264     ]
265     new_include_rules = [
266         '+chrome',
267         '+base',
268     ]
269     self.assertEqual(set(),
270                      self.calculate(old_include_rules, {}, new_include_rules,
271                                     {}))
272
273
274 class JSONParsingTest(unittest.TestCase):
275   def testSuccess(self):
276     input_api = MockInputApi()
277     filename = 'valid_json.json'
278     contents = ['// This is a comment.',
279                 '{',
280                 '  "key1": ["value1", "value2"],',
281                 '  "key2": 3  // This is an inline comment.',
282                 '}'
283                 ]
284     input_api.files = [MockFile(filename, contents)]
285     self.assertEqual(None,
286                      PRESUBMIT._GetJSONParseError(input_api, filename))
287
288   def testFailure(self):
289     input_api = MockInputApi()
290     test_data = [
291       ('invalid_json_1.json',
292        ['{ x }'],
293        'Expecting property name'),
294       ('invalid_json_2.json',
295        ['// Hello world!',
296         '{ "hello": "world }'],
297        'Unterminated string starting at:'),
298       ('invalid_json_3.json',
299        ['{ "a": "b", "c": "d", }'],
300        'Expecting property name'),
301       ('invalid_json_4.json',
302        ['{ "a": "b" "c": "d" }'],
303        "Expecting ',' delimiter:"),
304     ]
305
306     input_api.files = [MockFile(filename, contents)
307                        for (filename, contents, _) in test_data]
308
309     for (filename, _, expected_error) in test_data:
310       actual_error = PRESUBMIT._GetJSONParseError(input_api, filename)
311       self.assertTrue(expected_error in str(actual_error),
312                       "'%s' not found in '%s'" % (expected_error, actual_error))
313
314   def testNoEatComments(self):
315     input_api = MockInputApi()
316     file_with_comments = 'file_with_comments.json'
317     contents_with_comments = ['// This is a comment.',
318                               '{',
319                               '  "key1": ["value1", "value2"],',
320                               '  "key2": 3  // This is an inline comment.',
321                               '}'
322                               ]
323     file_without_comments = 'file_without_comments.json'
324     contents_without_comments = ['{',
325                                  '  "key1": ["value1", "value2"],',
326                                  '  "key2": 3',
327                                  '}'
328                                  ]
329     input_api.files = [MockFile(file_with_comments, contents_with_comments),
330                        MockFile(file_without_comments,
331                                 contents_without_comments)]
332
333     self.assertNotEqual(None,
334                         str(PRESUBMIT._GetJSONParseError(input_api,
335                                                          file_with_comments,
336                                                          eat_comments=False)))
337     self.assertEqual(None,
338                      PRESUBMIT._GetJSONParseError(input_api,
339                                                   file_without_comments,
340                                                   eat_comments=False))
341
342
343 class IDLParsingTest(unittest.TestCase):
344   def testSuccess(self):
345     input_api = MockInputApi()
346     filename = 'valid_idl_basics.idl'
347     contents = ['// Tests a valid IDL file.',
348                 'namespace idl_basics {',
349                 '  enum EnumType {',
350                 '    name1,',
351                 '    name2',
352                 '  };',
353                 '',
354                 '  dictionary MyType1 {',
355                 '    DOMString a;',
356                 '  };',
357                 '',
358                 '  callback Callback1 = void();',
359                 '  callback Callback2 = void(long x);',
360                 '  callback Callback3 = void(MyType1 arg);',
361                 '  callback Callback4 = void(EnumType type);',
362                 '',
363                 '  interface Functions {',
364                 '    static void function1();',
365                 '    static void function2(long x);',
366                 '    static void function3(MyType1 arg);',
367                 '    static void function4(Callback1 cb);',
368                 '    static void function5(Callback2 cb);',
369                 '    static void function6(Callback3 cb);',
370                 '    static void function7(Callback4 cb);',
371                 '  };',
372                 '',
373                 '  interface Events {',
374                 '    static void onFoo1();',
375                 '    static void onFoo2(long x);',
376                 '    static void onFoo2(MyType1 arg);',
377                 '    static void onFoo3(EnumType type);',
378                 '  };',
379                 '};'
380                 ]
381     input_api.files = [MockFile(filename, contents)]
382     self.assertEqual(None,
383                      PRESUBMIT._GetIDLParseError(input_api, filename))
384
385   def testFailure(self):
386     input_api = MockInputApi()
387     test_data = [
388       ('invalid_idl_1.idl',
389        ['//',
390         'namespace test {',
391         '  dictionary {',
392         '    DOMString s;',
393         '  };',
394         '};'],
395        'Unexpected "{" after keyword "dictionary".\n'),
396       # TODO(yoz): Disabled because it causes the IDL parser to hang.
397       # See crbug.com/363830.
398       # ('invalid_idl_2.idl',
399       #  (['namespace test {',
400       #    '  dictionary MissingSemicolon {',
401       #    '    DOMString a',
402       #    '    DOMString b;',
403       #    '  };',
404       #    '};'],
405       #   'Unexpected symbol DOMString after symbol a.'),
406       ('invalid_idl_3.idl',
407        ['//',
408         'namespace test {',
409         '  enum MissingComma {',
410         '    name1',
411         '    name2',
412         '  };',
413         '};'],
414        'Unexpected symbol name2 after symbol name1.'),
415       ('invalid_idl_4.idl',
416        ['//',
417         'namespace test {',
418         '  enum TrailingComma {',
419         '    name1,',
420         '    name2,',
421         '  };',
422         '};'],
423        'Trailing comma in block.'),
424       ('invalid_idl_5.idl',
425        ['//',
426         'namespace test {',
427         '  callback Callback1 = void(;',
428         '};'],
429        'Unexpected ";" after "(".'),
430       ('invalid_idl_6.idl',
431        ['//',
432         'namespace test {',
433         '  callback Callback1 = void(long );',
434         '};'],
435        'Unexpected ")" after symbol long.'),
436       ('invalid_idl_7.idl',
437        ['//',
438         'namespace test {',
439         '  interace Events {',
440         '    static void onFoo1();',
441         '  };',
442         '};'],
443        'Unexpected symbol Events after symbol interace.'),
444       ('invalid_idl_8.idl',
445        ['//',
446         'namespace test {',
447         '  interface NotEvent {',
448         '    static void onFoo1();',
449         '  };',
450         '};'],
451        'Did not process Interface Interface(NotEvent)'),
452       ('invalid_idl_9.idl',
453        ['//',
454         'namespace test {',
455         '  interface {',
456         '    static void function1();',
457         '  };',
458         '};'],
459        'Interface missing name.'),
460     ]
461
462     input_api.files = [MockFile(filename, contents)
463                        for (filename, contents, _) in test_data]
464
465     for (filename, _, expected_error) in test_data:
466       actual_error = PRESUBMIT._GetIDLParseError(input_api, filename)
467       self.assertTrue(expected_error in str(actual_error),
468                       "'%s' not found in '%s'" % (expected_error, actual_error))
469
470
471 class UserMetricsActionTest(unittest.TestCase):
472   def testUserMetricsActionInActions(self):
473     input_api = MockInputApi()
474     file_with_user_action = 'file_with_user_action.cc'
475     contents_with_user_action = [
476       'base::UserMetricsAction("AboutChrome")'
477     ]
478
479     input_api.files = [MockFile(file_with_user_action,
480                                 contents_with_user_action)]
481
482     self.assertEqual(
483       [], PRESUBMIT.CheckUserActionUpdate(input_api, MockOutputApi()))
484
485   def testUserMetricsActionNotAddedToActions(self):
486     input_api = MockInputApi()
487     file_with_user_action = 'file_with_user_action.cc'
488     contents_with_user_action = [
489       'base::UserMetricsAction("NotInActionsXml")'
490     ]
491
492     input_api.files = [MockFile(file_with_user_action,
493                                 contents_with_user_action)]
494
495     output = PRESUBMIT.CheckUserActionUpdate(input_api, MockOutputApi())
496     self.assertEqual(
497       ('File %s line %d: %s is missing in '
498        'tools/metrics/actions/actions.xml. Please run '
499        'tools/metrics/actions/extract_actions.py to update.'
500        % (file_with_user_action, 1, 'NotInActionsXml')),
501       output[0].message)
502
503   def testUserMetricsActionInTestFile(self):
504     input_api = MockInputApi()
505     file_with_user_action = 'file_with_user_action_unittest.cc'
506     contents_with_user_action = [
507       'base::UserMetricsAction("NotInActionsXml")'
508     ]
509
510     input_api.files = [MockFile(file_with_user_action,
511                                 contents_with_user_action)]
512
513     self.assertEqual(
514       [], PRESUBMIT.CheckUserActionUpdate(input_api, MockOutputApi()))
515
516
517 class PydepsNeedsUpdatingTest(unittest.TestCase):
518
519   class MockSubprocess(object):
520     CalledProcessError = subprocess.CalledProcessError
521
522   def _MockParseGclientArgs(self, is_android=True):
523     return lambda: {'checkout_android': 'true' if is_android else 'false' }
524
525   def setUp(self):
526     mock_all_pydeps = ['A.pydeps', 'B.pydeps', 'D.pydeps']
527     self.old_ALL_PYDEPS_FILES = PRESUBMIT._ALL_PYDEPS_FILES
528     PRESUBMIT._ALL_PYDEPS_FILES = mock_all_pydeps
529     mock_android_pydeps = ['D.pydeps']
530     self.old_ANDROID_SPECIFIC_PYDEPS_FILES = (
531         PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES)
532     PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES = mock_android_pydeps
533     self.old_ParseGclientArgs = PRESUBMIT._ParseGclientArgs
534     PRESUBMIT._ParseGclientArgs = self._MockParseGclientArgs()
535     self.mock_input_api = MockInputApi()
536     self.mock_output_api = MockOutputApi()
537     self.mock_input_api.subprocess = PydepsNeedsUpdatingTest.MockSubprocess()
538     self.checker = PRESUBMIT.PydepsChecker(self.mock_input_api, mock_all_pydeps)
539     self.checker._file_cache = {
540         'A.pydeps': '# Generated by:\n# CMD --output A.pydeps A\nA.py\nC.py\n',
541         'B.pydeps': '# Generated by:\n# CMD --output B.pydeps B\nB.py\nC.py\n',
542         'D.pydeps': '# Generated by:\n# CMD --output D.pydeps D\nD.py\n',
543     }
544
545   def tearDown(self):
546     PRESUBMIT._ALL_PYDEPS_FILES = self.old_ALL_PYDEPS_FILES
547     PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES = (
548         self.old_ANDROID_SPECIFIC_PYDEPS_FILES)
549     PRESUBMIT._ParseGclientArgs = self.old_ParseGclientArgs
550
551   def _RunCheck(self):
552     return PRESUBMIT.CheckPydepsNeedsUpdating(self.mock_input_api,
553                                                self.mock_output_api,
554                                                checker_for_tests=self.checker)
555
556   def testAddedPydep(self):
557     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
558     if self.mock_input_api.platform != 'linux2':
559       return []
560
561     self.mock_input_api.files = [
562       MockAffectedFile('new.pydeps', [], action='A'),
563     ]
564
565     self.mock_input_api.CreateMockFileInPath(
566         [x.LocalPath() for x in self.mock_input_api.AffectedFiles(
567             include_deletes=True)])
568     results = self._RunCheck()
569     self.assertEqual(1, len(results))
570     self.assertIn('PYDEPS_FILES', str(results[0]))
571
572   def testPydepNotInSrc(self):
573     self.mock_input_api.files = [
574       MockAffectedFile('new.pydeps', [], action='A'),
575     ]
576     self.mock_input_api.CreateMockFileInPath([])
577     results = self._RunCheck()
578     self.assertEqual(0, len(results))
579
580   def testRemovedPydep(self):
581     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
582     if self.mock_input_api.platform != 'linux2':
583       return []
584
585     self.mock_input_api.files = [
586       MockAffectedFile(PRESUBMIT._ALL_PYDEPS_FILES[0], [], action='D'),
587     ]
588     self.mock_input_api.CreateMockFileInPath(
589         [x.LocalPath() for x in self.mock_input_api.AffectedFiles(
590             include_deletes=True)])
591     results = self._RunCheck()
592     self.assertEqual(1, len(results))
593     self.assertIn('PYDEPS_FILES', str(results[0]))
594
595   def testRandomPyIgnored(self):
596     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
597     if self.mock_input_api.platform != 'linux2':
598       return []
599
600     self.mock_input_api.files = [
601       MockAffectedFile('random.py', []),
602     ]
603
604     results = self._RunCheck()
605     self.assertEqual(0, len(results), 'Unexpected results: %r' % results)
606
607   def testRelevantPyNoChange(self):
608     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
609     if self.mock_input_api.platform != 'linux2':
610       return []
611
612     self.mock_input_api.files = [
613       MockAffectedFile('A.py', []),
614     ]
615
616     def mock_check_output(cmd, shell=False, env=None):
617       self.assertEqual('CMD --output A.pydeps A --output ""', cmd)
618       return self.checker._file_cache['A.pydeps']
619
620     self.mock_input_api.subprocess.check_output = mock_check_output
621
622     results = self._RunCheck()
623     self.assertEqual(0, len(results), 'Unexpected results: %r' % results)
624
625   def testRelevantPyOneChange(self):
626     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
627     if self.mock_input_api.platform != 'linux2':
628       return []
629
630     self.mock_input_api.files = [
631       MockAffectedFile('A.py', []),
632     ]
633
634     def mock_check_output(cmd, shell=False, env=None):
635       self.assertEqual('CMD --output A.pydeps A --output ""', cmd)
636       return 'changed data'
637
638     self.mock_input_api.subprocess.check_output = mock_check_output
639
640     results = self._RunCheck()
641     self.assertEqual(1, len(results))
642     self.assertIn('File is stale', str(results[0]))
643
644   def testRelevantPyTwoChanges(self):
645     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
646     if self.mock_input_api.platform != 'linux2':
647       return []
648
649     self.mock_input_api.files = [
650       MockAffectedFile('C.py', []),
651     ]
652
653     def mock_check_output(cmd, shell=False, env=None):
654       return 'changed data'
655
656     self.mock_input_api.subprocess.check_output = mock_check_output
657
658     results = self._RunCheck()
659     self.assertEqual(2, len(results))
660     self.assertIn('File is stale', str(results[0]))
661     self.assertIn('File is stale', str(results[1]))
662
663   def testRelevantAndroidPyInNonAndroidCheckout(self):
664     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
665     if self.mock_input_api.platform != 'linux2':
666       return []
667
668     self.mock_input_api.files = [
669       MockAffectedFile('D.py', []),
670     ]
671
672     def mock_check_output(cmd, shell=False, env=None):
673       self.assertEqual('CMD --output D.pydeps D --output ""', cmd)
674       return 'changed data'
675
676     self.mock_input_api.subprocess.check_output = mock_check_output
677     PRESUBMIT._ParseGclientArgs = self._MockParseGclientArgs(is_android=False)
678
679     results = self._RunCheck()
680     self.assertEqual(1, len(results))
681     self.assertIn('Android', str(results[0]))
682     self.assertIn('D.pydeps', str(results[0]))
683
684   def testGnPathsAndMissingOutputFlag(self):
685     # PRESUBMIT.CheckPydepsNeedsUpdating is only implemented for Linux.
686     if self.mock_input_api.platform != 'linux2':
687       return []
688
689     self.checker._file_cache = {
690         'A.pydeps': '# Generated by:\n# CMD --gn-paths A\n//A.py\n//C.py\n',
691         'B.pydeps': '# Generated by:\n# CMD --gn-paths B\n//B.py\n//C.py\n',
692         'D.pydeps': '# Generated by:\n# CMD --gn-paths D\n//D.py\n',
693     }
694
695     self.mock_input_api.files = [
696       MockAffectedFile('A.py', []),
697     ]
698
699     def mock_check_output(cmd, shell=False, env=None):
700       self.assertEqual('CMD --gn-paths A --output A.pydeps --output ""', cmd)
701       return 'changed data'
702
703     self.mock_input_api.subprocess.check_output = mock_check_output
704
705     results = self._RunCheck()
706     self.assertEqual(1, len(results))
707     self.assertIn('File is stale', str(results[0]))
708
709
710 class IncludeGuardTest(unittest.TestCase):
711   def testIncludeGuardChecks(self):
712     mock_input_api = MockInputApi()
713     mock_output_api = MockOutputApi()
714     mock_input_api.files = [
715         MockAffectedFile('content/browser/thing/foo.h', [
716           '// Comment',
717           '#ifndef CONTENT_BROWSER_THING_FOO_H_',
718           '#define CONTENT_BROWSER_THING_FOO_H_',
719           'struct McBoatFace;',
720           '#endif  // CONTENT_BROWSER_THING_FOO_H_',
721         ]),
722         MockAffectedFile('content/browser/thing/bar.h', [
723           '#ifndef CONTENT_BROWSER_THING_BAR_H_',
724           '#define CONTENT_BROWSER_THING_BAR_H_',
725           'namespace content {',
726           '#endif  // CONTENT_BROWSER_THING_BAR_H_',
727           '}  // namespace content',
728         ]),
729         MockAffectedFile('content/browser/test1.h', [
730           'namespace content {',
731           '}  // namespace content',
732         ]),
733         MockAffectedFile('content\\browser\\win.h', [
734           '#ifndef CONTENT_BROWSER_WIN_H_',
735           '#define CONTENT_BROWSER_WIN_H_',
736           'struct McBoatFace;',
737           '#endif  // CONTENT_BROWSER_WIN_H_',
738         ]),
739         MockAffectedFile('content/browser/test2.h', [
740           '// Comment',
741           '#ifndef CONTENT_BROWSER_TEST2_H_',
742           'struct McBoatFace;',
743           '#endif  // CONTENT_BROWSER_TEST2_H_',
744         ]),
745         MockAffectedFile('content/browser/internal.h', [
746           '// Comment',
747           '#ifndef CONTENT_BROWSER_INTERNAL_H_',
748           '#define CONTENT_BROWSER_INTERNAL_H_',
749           '// Comment',
750           '#ifndef INTERNAL_CONTENT_BROWSER_INTERNAL_H_',
751           '#define INTERNAL_CONTENT_BROWSER_INTERNAL_H_',
752           'namespace internal {',
753           '}  // namespace internal',
754           '#endif  // INTERNAL_CONTENT_BROWSER_THING_BAR_H_',
755           'namespace content {',
756           '}  // namespace content',
757           '#endif  // CONTENT_BROWSER_THING_BAR_H_',
758         ]),
759         MockAffectedFile('content/browser/thing/foo.cc', [
760           '// This is a non-header.',
761         ]),
762         MockAffectedFile('content/browser/disabled.h', [
763           '// no-include-guard-because-multiply-included',
764           'struct McBoatFace;',
765         ]),
766         # New files don't allow misspelled include guards.
767         MockAffectedFile('content/browser/spleling.h', [
768           '#ifndef CONTENT_BROWSER_SPLLEING_H_',
769           '#define CONTENT_BROWSER_SPLLEING_H_',
770           'struct McBoatFace;',
771           '#endif  // CONTENT_BROWSER_SPLLEING_H_',
772         ]),
773         # New files don't allow + in include guards.
774         MockAffectedFile('content/browser/foo+bar.h', [
775           '#ifndef CONTENT_BROWSER_FOO+BAR_H_',
776           '#define CONTENT_BROWSER_FOO+BAR_H_',
777           'struct McBoatFace;',
778           '#endif  // CONTENT_BROWSER_FOO+BAR_H_',
779         ]),
780         # Old files allow misspelled include guards (for now).
781         MockAffectedFile('chrome/old.h', [
782           '// New contents',
783           '#ifndef CHROME_ODL_H_',
784           '#define CHROME_ODL_H_',
785           '#endif  // CHROME_ODL_H_',
786         ], [
787           '// Old contents',
788           '#ifndef CHROME_ODL_H_',
789           '#define CHROME_ODL_H_',
790           '#endif  // CHROME_ODL_H_',
791         ]),
792         # Using a Blink style include guard outside Blink is wrong.
793         MockAffectedFile('content/NotInBlink.h', [
794           '#ifndef NotInBlink_h',
795           '#define NotInBlink_h',
796           'struct McBoatFace;',
797           '#endif  // NotInBlink_h',
798         ]),
799         # Using a Blink style include guard in Blink is no longer ok.
800         MockAffectedFile('third_party/blink/InBlink.h', [
801           '#ifndef InBlink_h',
802           '#define InBlink_h',
803           'struct McBoatFace;',
804           '#endif  // InBlink_h',
805         ]),
806         # Using a bad include guard in Blink is not ok.
807         MockAffectedFile('third_party/blink/AlsoInBlink.h', [
808           '#ifndef WrongInBlink_h',
809           '#define WrongInBlink_h',
810           'struct McBoatFace;',
811           '#endif  // WrongInBlink_h',
812         ]),
813         # Using a bad include guard in Blink is not accepted even if
814         # it's an old file.
815         MockAffectedFile('third_party/blink/StillInBlink.h', [
816           '// New contents',
817           '#ifndef AcceptedInBlink_h',
818           '#define AcceptedInBlink_h',
819           'struct McBoatFace;',
820           '#endif  // AcceptedInBlink_h',
821         ], [
822           '// Old contents',
823           '#ifndef AcceptedInBlink_h',
824           '#define AcceptedInBlink_h',
825           'struct McBoatFace;',
826           '#endif  // AcceptedInBlink_h',
827         ]),
828         # Using a non-Chromium include guard in third_party
829         # (outside blink) is accepted.
830         MockAffectedFile('third_party/foo/some_file.h', [
831           '#ifndef REQUIRED_RPCNDR_H_',
832           '#define REQUIRED_RPCNDR_H_',
833           'struct SomeFileFoo;',
834           '#endif  // REQUIRED_RPCNDR_H_',
835         ]),
836         # Not having proper include guard in *_message_generator.h
837         # for old IPC messages is allowed.
838         MockAffectedFile('content/common/content_message_generator.h', [
839           '#undef CONTENT_COMMON_FOO_MESSAGES_H_',
840           '#include "content/common/foo_messages.h"',
841           '#ifndef CONTENT_COMMON_FOO_MESSAGES_H_',
842           '#error "Failed to include content/common/foo_messages.h"',
843           '#endif',
844         ]),
845       ]
846     msgs = PRESUBMIT.CheckForIncludeGuards(
847         mock_input_api, mock_output_api)
848     expected_fail_count = 8
849     self.assertEqual(expected_fail_count, len(msgs),
850                      'Expected %d items, found %d: %s'
851                      % (expected_fail_count, len(msgs), msgs))
852     self.assertEqual(msgs[0].items, ['content/browser/thing/bar.h'])
853     self.assertEqual(msgs[0].message,
854                      'Include guard CONTENT_BROWSER_THING_BAR_H_ '
855                      'not covering the whole file')
856
857     self.assertEqual(msgs[1].items, ['content/browser/test1.h'])
858     self.assertEqual(msgs[1].message,
859                      'Missing include guard CONTENT_BROWSER_TEST1_H_')
860
861     self.assertEqual(msgs[2].items, ['content/browser/test2.h:3'])
862     self.assertEqual(msgs[2].message,
863                      'Missing "#define CONTENT_BROWSER_TEST2_H_" for '
864                      'include guard')
865
866     self.assertEqual(msgs[3].items, ['content/browser/spleling.h:1'])
867     self.assertEqual(msgs[3].message,
868                      'Header using the wrong include guard name '
869                      'CONTENT_BROWSER_SPLLEING_H_')
870
871     self.assertEqual(msgs[4].items, ['content/browser/foo+bar.h'])
872     self.assertEqual(msgs[4].message,
873                      'Missing include guard CONTENT_BROWSER_FOO_BAR_H_')
874
875     self.assertEqual(msgs[5].items, ['content/NotInBlink.h:1'])
876     self.assertEqual(msgs[5].message,
877                      'Header using the wrong include guard name '
878                      'NotInBlink_h')
879
880     self.assertEqual(msgs[6].items, ['third_party/blink/InBlink.h:1'])
881     self.assertEqual(msgs[6].message,
882                      'Header using the wrong include guard name '
883                      'InBlink_h')
884
885     self.assertEqual(msgs[7].items, ['third_party/blink/AlsoInBlink.h:1'])
886     self.assertEqual(msgs[7].message,
887                      'Header using the wrong include guard name '
888                      'WrongInBlink_h')
889
890 class AccessibilityRelnotesFieldTest(unittest.TestCase):
891   def testRelnotesPresent(self):
892     mock_input_api = MockInputApi()
893     mock_output_api = MockOutputApi()
894
895     mock_input_api.files = [MockAffectedFile('ui/accessibility/foo.bar', [''])]
896     mock_input_api.change.DescriptionText = lambda : 'Commit description'
897     mock_input_api.change.footers['AX-Relnotes'] = [
898         'Important user facing change']
899
900     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
901         mock_input_api, mock_output_api)
902     self.assertEqual(0, len(msgs),
903                      'Expected %d messages, found %d: %s'
904                      % (0, len(msgs), msgs))
905
906   def testRelnotesMissingFromAccessibilityChange(self):
907     mock_input_api = MockInputApi()
908     mock_output_api = MockOutputApi()
909
910     mock_input_api.files = [
911         MockAffectedFile('some/file', ['']),
912         MockAffectedFile('ui/accessibility/foo.bar', ['']),
913         MockAffectedFile('some/other/file', [''])
914     ]
915     mock_input_api.change.DescriptionText = lambda : 'Commit description'
916
917     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
918         mock_input_api, mock_output_api)
919     self.assertEqual(1, len(msgs),
920                      'Expected %d messages, found %d: %s'
921                      % (1, len(msgs), msgs))
922     self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message,
923                     'Missing AX-Relnotes field message not found in errors')
924
925   # The relnotes footer is not required for changes which do not touch any
926   # accessibility directories.
927   def testIgnoresNonAccesssibilityCode(self):
928     mock_input_api = MockInputApi()
929     mock_output_api = MockOutputApi()
930
931     mock_input_api.files = [
932         MockAffectedFile('some/file', ['']),
933         MockAffectedFile('some/other/file', [''])
934     ]
935     mock_input_api.change.DescriptionText = lambda : 'Commit description'
936
937     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
938         mock_input_api, mock_output_api)
939     self.assertEqual(0, len(msgs),
940                      'Expected %d messages, found %d: %s'
941                      % (0, len(msgs), msgs))
942
943   # Test that our presubmit correctly raises an error for a set of known paths.
944   def testExpectedPaths(self):
945     filesToTest = [
946       "chrome/browser/accessibility/foo.py",
947       "chrome/browser/ash/arc/accessibility/foo.cc",
948       "chrome/browser/ui/views/accessibility/foo.h",
949       "chrome/browser/extensions/api/automation/foo.h",
950       "chrome/browser/extensions/api/automation_internal/foo.cc",
951       "chrome/renderer/extensions/accessibility_foo.h",
952       "chrome/tests/data/accessibility/foo.html",
953       "content/browser/accessibility/foo.cc",
954       "content/renderer/accessibility/foo.h",
955       "content/tests/data/accessibility/foo.cc",
956       "extensions/renderer/api/automation/foo.h",
957       "ui/accessibility/foo/bar/baz.cc",
958       "ui/views/accessibility/foo/bar/baz.h",
959     ]
960
961     for testFile in filesToTest:
962       mock_input_api = MockInputApi()
963       mock_output_api = MockOutputApi()
964
965       mock_input_api.files = [
966           MockAffectedFile(testFile, [''])
967       ]
968       mock_input_api.change.DescriptionText = lambda : 'Commit description'
969
970       msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
971           mock_input_api, mock_output_api)
972       self.assertEqual(1, len(msgs),
973                        'Expected %d messages, found %d: %s, for file %s'
974                        % (1, len(msgs), msgs, testFile))
975       self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message,
976                       ('Missing AX-Relnotes field message not found in errors '
977                        ' for file %s' % (testFile)))
978
979   # Test that AX-Relnotes field can appear in the commit description (as long
980   # as it appears at the beginning of a line).
981   def testRelnotesInCommitDescription(self):
982     mock_input_api = MockInputApi()
983     mock_output_api = MockOutputApi()
984
985     mock_input_api.files = [
986         MockAffectedFile('ui/accessibility/foo.bar', ['']),
987     ]
988     mock_input_api.change.DescriptionText = lambda : ('Description:\n' +
989         'AX-Relnotes: solves all accessibility issues forever')
990
991     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
992         mock_input_api, mock_output_api)
993     self.assertEqual(0, len(msgs),
994                      'Expected %d messages, found %d: %s'
995                      % (0, len(msgs), msgs))
996
997   # Test that we don't match AX-Relnotes if it appears in the middle of a line.
998   def testRelnotesMustAppearAtBeginningOfLine(self):
999     mock_input_api = MockInputApi()
1000     mock_output_api = MockOutputApi()
1001
1002     mock_input_api.files = [
1003         MockAffectedFile('ui/accessibility/foo.bar', ['']),
1004     ]
1005     mock_input_api.change.DescriptionText = lambda : ('Description:\n' +
1006         'This change has no AX-Relnotes: we should print a warning')
1007
1008     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
1009         mock_input_api, mock_output_api)
1010     self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message,
1011                     'Missing AX-Relnotes field message not found in errors')
1012
1013   # Tests that the AX-Relnotes field can be lowercase and use a '=' in place
1014   # of a ':'.
1015   def testRelnotesLowercaseWithEqualSign(self):
1016     mock_input_api = MockInputApi()
1017     mock_output_api = MockOutputApi()
1018
1019     mock_input_api.files = [
1020         MockAffectedFile('ui/accessibility/foo.bar', ['']),
1021     ]
1022     mock_input_api.change.DescriptionText = lambda : ('Description:\n' +
1023         'ax-relnotes= this is a valid format for accessibiliy relnotes')
1024
1025     msgs = PRESUBMIT.CheckAccessibilityRelnotesField(
1026         mock_input_api, mock_output_api)
1027     self.assertEqual(0, len(msgs),
1028                      'Expected %d messages, found %d: %s'
1029                      % (0, len(msgs), msgs))
1030
1031 class AndroidDeprecatedTestAnnotationTest(unittest.TestCase):
1032   def testCheckAndroidTestAnnotationUsage(self):
1033     mock_input_api = MockInputApi()
1034     mock_output_api = MockOutputApi()
1035
1036     mock_input_api.files = [
1037         MockAffectedFile('LalaLand.java', [
1038           'random stuff'
1039         ]),
1040         MockAffectedFile('CorrectUsage.java', [
1041           'import android.support.test.filters.LargeTest;',
1042           'import android.support.test.filters.MediumTest;',
1043           'import android.support.test.filters.SmallTest;',
1044         ]),
1045         MockAffectedFile('UsedDeprecatedLargeTestAnnotation.java', [
1046           'import android.test.suitebuilder.annotation.LargeTest;',
1047         ]),
1048         MockAffectedFile('UsedDeprecatedMediumTestAnnotation.java', [
1049           'import android.test.suitebuilder.annotation.MediumTest;',
1050         ]),
1051         MockAffectedFile('UsedDeprecatedSmallTestAnnotation.java', [
1052           'import android.test.suitebuilder.annotation.SmallTest;',
1053         ]),
1054         MockAffectedFile('UsedDeprecatedSmokeAnnotation.java', [
1055           'import android.test.suitebuilder.annotation.Smoke;',
1056         ])
1057     ]
1058     msgs = PRESUBMIT._CheckAndroidTestAnnotationUsage(
1059         mock_input_api, mock_output_api)
1060     self.assertEqual(1, len(msgs),
1061                      'Expected %d items, found %d: %s'
1062                      % (1, len(msgs), msgs))
1063     self.assertEqual(4, len(msgs[0].items),
1064                      'Expected %d items, found %d: %s'
1065                      % (4, len(msgs[0].items), msgs[0].items))
1066     self.assertTrue('UsedDeprecatedLargeTestAnnotation.java:1' in msgs[0].items,
1067                     'UsedDeprecatedLargeTestAnnotation not found in errors')
1068     self.assertTrue('UsedDeprecatedMediumTestAnnotation.java:1'
1069                     in msgs[0].items,
1070                     'UsedDeprecatedMediumTestAnnotation not found in errors')
1071     self.assertTrue('UsedDeprecatedSmallTestAnnotation.java:1' in msgs[0].items,
1072                     'UsedDeprecatedSmallTestAnnotation not found in errors')
1073     self.assertTrue('UsedDeprecatedSmokeAnnotation.java:1' in msgs[0].items,
1074                     'UsedDeprecatedSmokeAnnotation not found in errors')
1075
1076
1077 class CheckNoDownstreamDepsTest(unittest.TestCase):
1078   def testInvalidDepFromUpstream(self):
1079     mock_input_api = MockInputApi()
1080     mock_output_api = MockOutputApi()
1081
1082     mock_input_api.files = [
1083         MockAffectedFile('BUILD.gn', [
1084           'deps = [',
1085           '   "//clank/target:test",',
1086           ']'
1087         ]),
1088         MockAffectedFile('chrome/android/BUILD.gn', [
1089           'deps = [ "//clank/target:test" ]'
1090         ]),
1091         MockAffectedFile('chrome/chrome_java_deps.gni', [
1092           'java_deps = [',
1093           '   "//clank/target:test",',
1094           ']'
1095         ]),
1096     ]
1097     mock_input_api.change.RepositoryRoot = lambda: 'chromium/src'
1098     msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1099         mock_input_api, mock_output_api)
1100     self.assertEqual(1, len(msgs),
1101                      'Expected %d items, found %d: %s'
1102                      % (1, len(msgs), msgs))
1103     self.assertEqual(3, len(msgs[0].items),
1104                      'Expected %d items, found %d: %s'
1105                      % (3, len(msgs[0].items), msgs[0].items))
1106     self.assertTrue(any('BUILD.gn:2' in item for item in msgs[0].items),
1107                     'BUILD.gn not found in errors')
1108     self.assertTrue(
1109         any('chrome/android/BUILD.gn:1' in item for item in msgs[0].items),
1110         'chrome/android/BUILD.gn:1 not found in errors')
1111     self.assertTrue(
1112         any('chrome/chrome_java_deps.gni:2' in item for item in msgs[0].items),
1113         'chrome/chrome_java_deps.gni:2 not found in errors')
1114
1115   def testAllowsComments(self):
1116     mock_input_api = MockInputApi()
1117     mock_output_api = MockOutputApi()
1118
1119     mock_input_api.files = [
1120         MockAffectedFile('BUILD.gn', [
1121           '# real implementation in //clank/target:test',
1122         ]),
1123     ]
1124     mock_input_api.change.RepositoryRoot = lambda: 'chromium/src'
1125     msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1126         mock_input_api, mock_output_api)
1127     self.assertEqual(0, len(msgs),
1128                      'Expected %d items, found %d: %s'
1129                      % (0, len(msgs), msgs))
1130
1131   def testOnlyChecksBuildFiles(self):
1132     mock_input_api = MockInputApi()
1133     mock_output_api = MockOutputApi()
1134
1135     mock_input_api.files = [
1136         MockAffectedFile('README.md', [
1137           'DEPS = [ "//clank/target:test" ]'
1138         ]),
1139         MockAffectedFile('chrome/android/java/file.java', [
1140           '//clank/ only function'
1141         ]),
1142     ]
1143     mock_input_api.change.RepositoryRoot = lambda: 'chromium/src'
1144     msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1145         mock_input_api, mock_output_api)
1146     self.assertEqual(0, len(msgs),
1147                      'Expected %d items, found %d: %s'
1148                      % (0, len(msgs), msgs))
1149
1150   def testValidDepFromDownstream(self):
1151     mock_input_api = MockInputApi()
1152     mock_output_api = MockOutputApi()
1153
1154     mock_input_api.files = [
1155         MockAffectedFile('BUILD.gn', [
1156           'DEPS = [',
1157           '   "//clank/target:test",',
1158           ']'
1159         ]),
1160         MockAffectedFile('java/BUILD.gn', [
1161           'DEPS = [ "//clank/target:test" ]'
1162         ]),
1163     ]
1164     mock_input_api.change.RepositoryRoot = lambda: 'chromium/src/clank'
1165     msgs = PRESUBMIT.CheckNoUpstreamDepsOnClank(
1166         mock_input_api, mock_output_api)
1167     self.assertEqual(0, len(msgs),
1168                      'Expected %d items, found %d: %s'
1169                      % (0, len(msgs), msgs))
1170
1171 class AndroidDeprecatedJUnitFrameworkTest(unittest.TestCase):
1172   def testCheckAndroidTestJUnitFramework(self):
1173     mock_input_api = MockInputApi()
1174     mock_output_api = MockOutputApi()
1175
1176     mock_input_api.files = [
1177         MockAffectedFile('LalaLand.java', [
1178           'random stuff'
1179         ]),
1180         MockAffectedFile('CorrectUsage.java', [
1181           'import org.junit.ABC',
1182           'import org.junit.XYZ;',
1183         ]),
1184         MockAffectedFile('UsedDeprecatedJUnit.java', [
1185           'import junit.framework.*;',
1186         ]),
1187         MockAffectedFile('UsedDeprecatedJUnitAssert.java', [
1188           'import junit.framework.Assert;',
1189         ]),
1190     ]
1191     msgs = PRESUBMIT._CheckAndroidTestJUnitFrameworkImport(
1192         mock_input_api, mock_output_api)
1193     self.assertEqual(1, len(msgs),
1194                      'Expected %d items, found %d: %s'
1195                      % (1, len(msgs), msgs))
1196     self.assertEqual(2, len(msgs[0].items),
1197                      'Expected %d items, found %d: %s'
1198                      % (2, len(msgs[0].items), msgs[0].items))
1199     self.assertTrue('UsedDeprecatedJUnit.java:1' in msgs[0].items,
1200                     'UsedDeprecatedJUnit.java not found in errors')
1201     self.assertTrue('UsedDeprecatedJUnitAssert.java:1'
1202                     in msgs[0].items,
1203                     'UsedDeprecatedJUnitAssert not found in errors')
1204
1205
1206 class AndroidJUnitBaseClassTest(unittest.TestCase):
1207   def testCheckAndroidTestJUnitBaseClass(self):
1208     mock_input_api = MockInputApi()
1209     mock_output_api = MockOutputApi()
1210
1211     mock_input_api.files = [
1212         MockAffectedFile('LalaLand.java', [
1213           'random stuff'
1214         ]),
1215         MockAffectedFile('CorrectTest.java', [
1216           '@RunWith(ABC.class);'
1217           'public class CorrectTest {',
1218           '}',
1219         ]),
1220         MockAffectedFile('HistoricallyIncorrectTest.java', [
1221           'public class Test extends BaseCaseA {',
1222           '}',
1223         ], old_contents=[
1224           'public class Test extends BaseCaseB {',
1225           '}',
1226         ]),
1227         MockAffectedFile('CorrectTestWithInterface.java', [
1228           '@RunWith(ABC.class);'
1229           'public class CorrectTest implement Interface {',
1230           '}',
1231         ]),
1232         MockAffectedFile('IncorrectTest.java', [
1233           'public class IncorrectTest extends TestCase {',
1234           '}',
1235         ]),
1236         MockAffectedFile('IncorrectWithInterfaceTest.java', [
1237           'public class Test implements X extends BaseClass {',
1238           '}',
1239         ]),
1240         MockAffectedFile('IncorrectMultiLineTest.java', [
1241           'public class Test implements X, Y, Z',
1242           '        extends TestBase {',
1243           '}',
1244         ]),
1245     ]
1246     msgs = PRESUBMIT._CheckAndroidTestJUnitInheritance(
1247         mock_input_api, mock_output_api)
1248     self.assertEqual(1, len(msgs),
1249                      'Expected %d items, found %d: %s'
1250                      % (1, len(msgs), msgs))
1251     self.assertEqual(3, len(msgs[0].items),
1252                      'Expected %d items, found %d: %s'
1253                      % (3, len(msgs[0].items), msgs[0].items))
1254     self.assertTrue('IncorrectTest.java:1' in msgs[0].items,
1255                     'IncorrectTest not found in errors')
1256     self.assertTrue('IncorrectWithInterfaceTest.java:1'
1257                     in msgs[0].items,
1258                     'IncorrectWithInterfaceTest not found in errors')
1259     self.assertTrue('IncorrectMultiLineTest.java:2' in msgs[0].items,
1260                     'IncorrectMultiLineTest not found in errors')
1261
1262 class AndroidDebuggableBuildTest(unittest.TestCase):
1263
1264   def testCheckAndroidDebuggableBuild(self):
1265     mock_input_api = MockInputApi()
1266     mock_output_api = MockOutputApi()
1267
1268     mock_input_api.files = [
1269       MockAffectedFile('RandomStuff.java', [
1270         'random stuff'
1271       ]),
1272       MockAffectedFile('CorrectUsage.java', [
1273         'import org.chromium.base.BuildInfo;',
1274         'some random stuff',
1275         'boolean isOsDebuggable = BuildInfo.isDebugAndroid();',
1276       ]),
1277       MockAffectedFile('JustCheckUserdebugBuild.java', [
1278         'import android.os.Build;',
1279         'some random stuff',
1280         'boolean isOsDebuggable = Build.TYPE.equals("userdebug")',
1281       ]),
1282       MockAffectedFile('JustCheckEngineeringBuild.java', [
1283         'import android.os.Build;',
1284         'some random stuff',
1285         'boolean isOsDebuggable = "eng".equals(Build.TYPE)',
1286       ]),
1287       MockAffectedFile('UsedBuildType.java', [
1288         'import android.os.Build;',
1289         'some random stuff',
1290         'boolean isOsDebuggable = Build.TYPE.equals("userdebug")'
1291             '|| "eng".equals(Build.TYPE)',
1292       ]),
1293       MockAffectedFile('UsedExplicitBuildType.java', [
1294         'some random stuff',
1295         'boolean isOsDebuggable = android.os.Build.TYPE.equals("userdebug")'
1296             '|| "eng".equals(android.os.Build.TYPE)',
1297       ]),
1298     ]
1299
1300     msgs = PRESUBMIT._CheckAndroidDebuggableBuild(
1301         mock_input_api, mock_output_api)
1302     self.assertEqual(1, len(msgs),
1303                      'Expected %d items, found %d: %s'
1304                      % (1, len(msgs), msgs))
1305     self.assertEqual(4, len(msgs[0].items),
1306                      'Expected %d items, found %d: %s'
1307                      % (4, len(msgs[0].items), msgs[0].items))
1308     self.assertTrue('JustCheckUserdebugBuild.java:3' in msgs[0].items)
1309     self.assertTrue('JustCheckEngineeringBuild.java:3' in msgs[0].items)
1310     self.assertTrue('UsedBuildType.java:3' in msgs[0].items)
1311     self.assertTrue('UsedExplicitBuildType.java:2' in msgs[0].items)
1312
1313
1314 class LogUsageTest(unittest.TestCase):
1315
1316   def testCheckAndroidCrLogUsage(self):
1317     mock_input_api = MockInputApi()
1318     mock_output_api = MockOutputApi()
1319
1320     mock_input_api.files = [
1321       MockAffectedFile('RandomStuff.java', [
1322         'random stuff'
1323       ]),
1324       MockAffectedFile('HasAndroidLog.java', [
1325         'import android.util.Log;',
1326         'some random stuff',
1327         'Log.d("TAG", "foo");',
1328       ]),
1329       MockAffectedFile('HasExplicitUtilLog.java', [
1330         'some random stuff',
1331         'android.util.Log.d("TAG", "foo");',
1332       ]),
1333       MockAffectedFile('IsInBasePackage.java', [
1334         'package org.chromium.base;',
1335         'private static final String TAG = "cr_Foo";',
1336         'Log.d(TAG, "foo");',
1337       ]),
1338       MockAffectedFile('IsInBasePackageButImportsLog.java', [
1339         'package org.chromium.base;',
1340         'import android.util.Log;',
1341         'private static final String TAG = "cr_Foo";',
1342         'Log.d(TAG, "foo");',
1343       ]),
1344       MockAffectedFile('HasBothLog.java', [
1345         'import org.chromium.base.Log;',
1346         'some random stuff',
1347         'private static final String TAG = "cr_Foo";',
1348         'Log.d(TAG, "foo");',
1349         'android.util.Log.d("TAG", "foo");',
1350       ]),
1351       MockAffectedFile('HasCorrectTag.java', [
1352         'import org.chromium.base.Log;',
1353         'some random stuff',
1354         'private static final String TAG = "cr_Foo";',
1355         'Log.d(TAG, "foo");',
1356       ]),
1357       MockAffectedFile('HasOldTag.java', [
1358         'import org.chromium.base.Log;',
1359         'some random stuff',
1360         'private static final String TAG = "cr.Foo";',
1361         'Log.d(TAG, "foo");',
1362       ]),
1363       MockAffectedFile('HasDottedTag.java', [
1364         'import org.chromium.base.Log;',
1365         'some random stuff',
1366         'private static final String TAG = "cr_foo.bar";',
1367         'Log.d(TAG, "foo");',
1368       ]),
1369       MockAffectedFile('HasDottedTagPublic.java', [
1370         'import org.chromium.base.Log;',
1371         'some random stuff',
1372         'public static final String TAG = "cr_foo.bar";',
1373         'Log.d(TAG, "foo");',
1374       ]),
1375       MockAffectedFile('HasNoTagDecl.java', [
1376         'import org.chromium.base.Log;',
1377         'some random stuff',
1378         'Log.d(TAG, "foo");',
1379       ]),
1380       MockAffectedFile('HasIncorrectTagDecl.java', [
1381         'import org.chromium.base.Log;',
1382         'private static final String TAHG = "cr_Foo";',
1383         'some random stuff',
1384         'Log.d(TAG, "foo");',
1385       ]),
1386       MockAffectedFile('HasInlineTag.java', [
1387         'import org.chromium.base.Log;',
1388         'some random stuff',
1389         'private static final String TAG = "cr_Foo";',
1390         'Log.d("TAG", "foo");',
1391       ]),
1392       MockAffectedFile('HasInlineTagWithSpace.java', [
1393         'import org.chromium.base.Log;',
1394         'some random stuff',
1395         'private static final String TAG = "cr_Foo";',
1396         'Log.d("log message", "foo");',
1397       ]),
1398       MockAffectedFile('HasUnprefixedTag.java', [
1399         'import org.chromium.base.Log;',
1400         'some random stuff',
1401         'private static final String TAG = "rubbish";',
1402         'Log.d(TAG, "foo");',
1403       ]),
1404       MockAffectedFile('HasTooLongTag.java', [
1405         'import org.chromium.base.Log;',
1406         'some random stuff',
1407         'private static final String TAG = "21_charachers_long___";',
1408         'Log.d(TAG, "foo");',
1409       ]),
1410       MockAffectedFile('HasTooLongTagWithNoLogCallsInDiff.java', [
1411         'import org.chromium.base.Log;',
1412         'some random stuff',
1413         'private static final String TAG = "21_charachers_long___";',
1414       ]),
1415     ]
1416
1417     msgs = PRESUBMIT._CheckAndroidCrLogUsage(
1418         mock_input_api, mock_output_api)
1419
1420     self.assertEqual(5, len(msgs),
1421                      'Expected %d items, found %d: %s' % (5, len(msgs), msgs))
1422
1423     # Declaration format
1424     nb = len(msgs[0].items)
1425     self.assertEqual(2, nb,
1426                      'Expected %d items, found %d: %s' % (2, nb, msgs[0].items))
1427     self.assertTrue('HasNoTagDecl.java' in msgs[0].items)
1428     self.assertTrue('HasIncorrectTagDecl.java' in msgs[0].items)
1429
1430     # Tag length
1431     nb = len(msgs[1].items)
1432     self.assertEqual(2, nb,
1433                      'Expected %d items, found %d: %s' % (2, nb, msgs[1].items))
1434     self.assertTrue('HasTooLongTag.java' in msgs[1].items)
1435     self.assertTrue('HasTooLongTagWithNoLogCallsInDiff.java' in msgs[1].items)
1436
1437     # Tag must be a variable named TAG
1438     nb = len(msgs[2].items)
1439     self.assertEqual(3, nb,
1440                      'Expected %d items, found %d: %s' % (3, nb, msgs[2].items))
1441     self.assertTrue('HasBothLog.java:5' in msgs[2].items)
1442     self.assertTrue('HasInlineTag.java:4' in msgs[2].items)
1443     self.assertTrue('HasInlineTagWithSpace.java:4' in msgs[2].items)
1444
1445     # Util Log usage
1446     nb = len(msgs[3].items)
1447     self.assertEqual(3, nb,
1448                      'Expected %d items, found %d: %s' % (3, nb, msgs[3].items))
1449     self.assertTrue('HasAndroidLog.java:3' in msgs[3].items)
1450     self.assertTrue('HasExplicitUtilLog.java:2' in msgs[3].items)
1451     self.assertTrue('IsInBasePackageButImportsLog.java:4' in msgs[3].items)
1452
1453     # Tag must not contain
1454     nb = len(msgs[4].items)
1455     self.assertEqual(3, nb,
1456                      'Expected %d items, found %d: %s' % (2, nb, msgs[4].items))
1457     self.assertTrue('HasDottedTag.java' in msgs[4].items)
1458     self.assertTrue('HasDottedTagPublic.java' in msgs[4].items)
1459     self.assertTrue('HasOldTag.java' in msgs[4].items)
1460
1461
1462 class GoogleAnswerUrlFormatTest(unittest.TestCase):
1463
1464   def testCatchAnswerUrlId(self):
1465     input_api = MockInputApi()
1466     input_api.files = [
1467       MockFile('somewhere/file.cc',
1468                ['char* host = '
1469                 '  "https://support.google.com/chrome/answer/123456";']),
1470       MockFile('somewhere_else/file.cc',
1471                ['char* host = '
1472                 '  "https://support.google.com/chrome/a/answer/123456";']),
1473     ]
1474
1475     warnings = PRESUBMIT.CheckGoogleSupportAnswerUrlOnUpload(
1476       input_api, MockOutputApi())
1477     self.assertEqual(1, len(warnings))
1478     self.assertEqual(2, len(warnings[0].items))
1479
1480   def testAllowAnswerUrlParam(self):
1481     input_api = MockInputApi()
1482     input_api.files = [
1483       MockFile('somewhere/file.cc',
1484                ['char* host = '
1485                 '  "https://support.google.com/chrome/?p=cpn_crash_reports";']),
1486     ]
1487
1488     warnings = PRESUBMIT.CheckGoogleSupportAnswerUrlOnUpload(
1489       input_api, MockOutputApi())
1490     self.assertEqual(0, len(warnings))
1491
1492
1493 class HardcodedGoogleHostsTest(unittest.TestCase):
1494
1495   def testWarnOnAssignedLiterals(self):
1496     input_api = MockInputApi()
1497     input_api.files = [
1498       MockFile('content/file.cc',
1499                ['char* host = "https://www.google.com";']),
1500       MockFile('content/file.cc',
1501                ['char* host = "https://www.googleapis.com";']),
1502       MockFile('content/file.cc',
1503                ['char* host = "https://clients1.google.com";']),
1504     ]
1505
1506     warnings = PRESUBMIT.CheckHardcodedGoogleHostsInLowerLayers(
1507       input_api, MockOutputApi())
1508     self.assertEqual(1, len(warnings))
1509     self.assertEqual(3, len(warnings[0].items))
1510
1511   def testAllowInComment(self):
1512     input_api = MockInputApi()
1513     input_api.files = [
1514       MockFile('content/file.cc',
1515                ['char* host = "https://www.aol.com"; // google.com'])
1516     ]
1517
1518     warnings = PRESUBMIT.CheckHardcodedGoogleHostsInLowerLayers(
1519       input_api, MockOutputApi())
1520     self.assertEqual(0, len(warnings))
1521
1522
1523 class ChromeOsSyncedPrefRegistrationTest(unittest.TestCase):
1524
1525   def testWarnsOnChromeOsDirectories(self):
1526     input_api = MockInputApi()
1527     input_api.files = [
1528       MockFile('ash/file.cc',
1529                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1530       MockFile('chrome/browser/chromeos/file.cc',
1531                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1532       MockFile('chromeos/file.cc',
1533                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1534       MockFile('components/arc/file.cc',
1535                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1536       MockFile('components/exo/file.cc',
1537                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1538     ]
1539     warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1540       input_api, MockOutputApi())
1541     self.assertEqual(1, len(warnings))
1542
1543   def testDoesNotWarnOnSyncOsPref(self):
1544     input_api = MockInputApi()
1545     input_api.files = [
1546       MockFile('chromeos/file.cc',
1547                ['PrefRegistrySyncable::SYNCABLE_OS_PREF']),
1548     ]
1549     warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1550       input_api, MockOutputApi())
1551     self.assertEqual(0, len(warnings))
1552
1553   def testDoesNotWarnOnCrossPlatformDirectories(self):
1554     input_api = MockInputApi()
1555     input_api.files = [
1556       MockFile('chrome/browser/ui/file.cc',
1557                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1558       MockFile('components/sync/file.cc',
1559                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1560       MockFile('content/browser/file.cc',
1561                ['PrefRegistrySyncable::SYNCABLE_PREF']),
1562     ]
1563     warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1564       input_api, MockOutputApi())
1565     self.assertEqual(0, len(warnings))
1566
1567   def testSeparateWarningForPriorityPrefs(self):
1568     input_api = MockInputApi()
1569     input_api.files = [
1570       MockFile('chromeos/file.cc',
1571                ['PrefRegistrySyncable::SYNCABLE_PREF',
1572                 'PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF']),
1573     ]
1574     warnings = PRESUBMIT.CheckChromeOsSyncedPrefRegistration(
1575       input_api, MockOutputApi())
1576     self.assertEqual(2, len(warnings))
1577
1578
1579 class ForwardDeclarationTest(unittest.TestCase):
1580   def testCheckHeadersOnlyOutsideThirdParty(self):
1581     mock_input_api = MockInputApi()
1582     mock_input_api.files = [
1583       MockAffectedFile('somewhere/file.cc', [
1584         'class DummyClass;'
1585       ]),
1586       MockAffectedFile('third_party/header.h', [
1587         'class DummyClass;'
1588       ])
1589     ]
1590     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1591                                                           MockOutputApi())
1592     self.assertEqual(0, len(warnings))
1593
1594   def testNoNestedDeclaration(self):
1595     mock_input_api = MockInputApi()
1596     mock_input_api.files = [
1597       MockAffectedFile('somewhere/header.h', [
1598         'class SomeClass {',
1599         ' protected:',
1600         '  class NotAMatch;',
1601         '};'
1602       ])
1603     ]
1604     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1605                                                           MockOutputApi())
1606     self.assertEqual(0, len(warnings))
1607
1608   def testSubStrings(self):
1609     mock_input_api = MockInputApi()
1610     mock_input_api.files = [
1611       MockAffectedFile('somewhere/header.h', [
1612         'class NotUsefulClass;',
1613         'struct SomeStruct;',
1614         'UsefulClass *p1;',
1615         'SomeStructPtr *p2;'
1616       ])
1617     ]
1618     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1619                                                           MockOutputApi())
1620     self.assertEqual(2, len(warnings))
1621
1622   def testUselessForwardDeclaration(self):
1623     mock_input_api = MockInputApi()
1624     mock_input_api.files = [
1625       MockAffectedFile('somewhere/header.h', [
1626         'class DummyClass;',
1627         'struct DummyStruct;',
1628         'class UsefulClass;',
1629         'std::unique_ptr<UsefulClass> p;'
1630       ])
1631     ]
1632     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1633                                                           MockOutputApi())
1634     self.assertEqual(2, len(warnings))
1635
1636   def testBlinkHeaders(self):
1637     mock_input_api = MockInputApi()
1638     mock_input_api.files = [
1639       MockAffectedFile('third_party/blink/header.h', [
1640         'class DummyClass;',
1641         'struct DummyStruct;',
1642       ]),
1643       MockAffectedFile('third_party\\blink\\header.h', [
1644         'class DummyClass;',
1645         'struct DummyStruct;',
1646       ])
1647     ]
1648     warnings = PRESUBMIT.CheckUselessForwardDeclarations(mock_input_api,
1649                                                           MockOutputApi())
1650     self.assertEqual(4, len(warnings))
1651
1652
1653 class RelativeIncludesTest(unittest.TestCase):
1654   def testThirdPartyNotWebKitIgnored(self):
1655     mock_input_api = MockInputApi()
1656     mock_input_api.files = [
1657       MockAffectedFile('third_party/test.cpp', '#include "../header.h"'),
1658       MockAffectedFile('third_party/test/test.cpp', '#include "../header.h"'),
1659     ]
1660
1661     mock_output_api = MockOutputApi()
1662
1663     errors = PRESUBMIT.CheckForRelativeIncludes(
1664         mock_input_api, mock_output_api)
1665     self.assertEqual(0, len(errors))
1666
1667   def testNonCppFileIgnored(self):
1668     mock_input_api = MockInputApi()
1669     mock_input_api.files = [
1670       MockAffectedFile('test.py', '#include "../header.h"'),
1671     ]
1672
1673     mock_output_api = MockOutputApi()
1674
1675     errors = PRESUBMIT.CheckForRelativeIncludes(
1676         mock_input_api, mock_output_api)
1677     self.assertEqual(0, len(errors))
1678
1679   def testInnocuousChangesAllowed(self):
1680     mock_input_api = MockInputApi()
1681     mock_input_api.files = [
1682       MockAffectedFile('test.cpp', '#include "header.h"'),
1683       MockAffectedFile('test2.cpp', '../'),
1684     ]
1685
1686     mock_output_api = MockOutputApi()
1687
1688     errors = PRESUBMIT.CheckForRelativeIncludes(
1689         mock_input_api, mock_output_api)
1690     self.assertEqual(0, len(errors))
1691
1692   def testRelativeIncludeNonWebKitProducesError(self):
1693     mock_input_api = MockInputApi()
1694     mock_input_api.files = [
1695       MockAffectedFile('test.cpp', ['#include "../header.h"']),
1696     ]
1697
1698     mock_output_api = MockOutputApi()
1699
1700     errors = PRESUBMIT.CheckForRelativeIncludes(
1701         mock_input_api, mock_output_api)
1702     self.assertEqual(1, len(errors))
1703
1704   def testRelativeIncludeWebKitProducesError(self):
1705     mock_input_api = MockInputApi()
1706     mock_input_api.files = [
1707       MockAffectedFile('third_party/blink/test.cpp',
1708                        ['#include "../header.h']),
1709     ]
1710
1711     mock_output_api = MockOutputApi()
1712
1713     errors = PRESUBMIT.CheckForRelativeIncludes(
1714         mock_input_api, mock_output_api)
1715     self.assertEqual(1, len(errors))
1716
1717
1718 class CCIncludeTest(unittest.TestCase):
1719   def testThirdPartyNotBlinkIgnored(self):
1720     mock_input_api = MockInputApi()
1721     mock_input_api.files = [
1722       MockAffectedFile('third_party/test.cpp', '#include "file.cc"'),
1723     ]
1724
1725     mock_output_api = MockOutputApi()
1726
1727     errors = PRESUBMIT.CheckForCcIncludes(
1728         mock_input_api, mock_output_api)
1729     self.assertEqual(0, len(errors))
1730
1731   def testPythonFileIgnored(self):
1732     mock_input_api = MockInputApi()
1733     mock_input_api.files = [
1734       MockAffectedFile('test.py', '#include "file.cc"'),
1735     ]
1736
1737     mock_output_api = MockOutputApi()
1738
1739     errors = PRESUBMIT.CheckForCcIncludes(
1740         mock_input_api, mock_output_api)
1741     self.assertEqual(0, len(errors))
1742
1743   def testIncFilesAccepted(self):
1744     mock_input_api = MockInputApi()
1745     mock_input_api.files = [
1746       MockAffectedFile('test.py', '#include "file.inc"'),
1747     ]
1748
1749     mock_output_api = MockOutputApi()
1750
1751     errors = PRESUBMIT.CheckForCcIncludes(
1752         mock_input_api, mock_output_api)
1753     self.assertEqual(0, len(errors))
1754
1755   def testInnocuousChangesAllowed(self):
1756     mock_input_api = MockInputApi()
1757     mock_input_api.files = [
1758       MockAffectedFile('test.cpp', '#include "header.h"'),
1759       MockAffectedFile('test2.cpp', 'Something "file.cc"'),
1760     ]
1761
1762     mock_output_api = MockOutputApi()
1763
1764     errors = PRESUBMIT.CheckForCcIncludes(
1765         mock_input_api, mock_output_api)
1766     self.assertEqual(0, len(errors))
1767
1768   def testCcIncludeNonBlinkProducesError(self):
1769     mock_input_api = MockInputApi()
1770     mock_input_api.files = [
1771       MockAffectedFile('test.cpp', ['#include "file.cc"']),
1772     ]
1773
1774     mock_output_api = MockOutputApi()
1775
1776     errors = PRESUBMIT.CheckForCcIncludes(
1777         mock_input_api, mock_output_api)
1778     self.assertEqual(1, len(errors))
1779
1780   def testCppIncludeBlinkProducesError(self):
1781     mock_input_api = MockInputApi()
1782     mock_input_api.files = [
1783       MockAffectedFile('third_party/blink/test.cpp',
1784                        ['#include "foo/file.cpp"']),
1785     ]
1786
1787     mock_output_api = MockOutputApi()
1788
1789     errors = PRESUBMIT.CheckForCcIncludes(
1790         mock_input_api, mock_output_api)
1791     self.assertEqual(1, len(errors))
1792
1793
1794 class GnGlobForwardTest(unittest.TestCase):
1795   def testAddBareGlobs(self):
1796     mock_input_api = MockInputApi()
1797     mock_input_api.files = [
1798       MockAffectedFile('base/stuff.gni', [
1799           'forward_variables_from(invoker, "*")']),
1800       MockAffectedFile('base/BUILD.gn', [
1801           'forward_variables_from(invoker, "*")']),
1802     ]
1803     warnings = PRESUBMIT.CheckGnGlobForward(mock_input_api, MockOutputApi())
1804     self.assertEqual(1, len(warnings))
1805     msg = '\n'.join(warnings[0].items)
1806     self.assertIn('base/stuff.gni', msg)
1807     # Should not check .gn files. Local templates don't need to care about
1808     # visibility / testonly.
1809     self.assertNotIn('base/BUILD.gn', msg)
1810
1811   def testValidUses(self):
1812     mock_input_api = MockInputApi()
1813     mock_input_api.files = [
1814       MockAffectedFile('base/stuff.gni', [
1815           'forward_variables_from(invoker, "*", [])']),
1816       MockAffectedFile('base/stuff2.gni', [
1817           'forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)']),
1818       MockAffectedFile('base/stuff3.gni', [
1819           'forward_variables_from(invoker, [ "testonly" ])']),
1820     ]
1821     warnings = PRESUBMIT.CheckGnGlobForward(mock_input_api, MockOutputApi())
1822     self.assertEqual([], warnings)
1823
1824
1825 class NewHeaderWithoutGnChangeTest(unittest.TestCase):
1826   def testAddHeaderWithoutGn(self):
1827     mock_input_api = MockInputApi()
1828     mock_input_api.files = [
1829       MockAffectedFile('base/stuff.h', ''),
1830     ]
1831     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1832         mock_input_api, MockOutputApi())
1833     self.assertEqual(1, len(warnings))
1834     self.assertTrue('base/stuff.h' in warnings[0].items)
1835
1836   def testModifyHeader(self):
1837     mock_input_api = MockInputApi()
1838     mock_input_api.files = [
1839       MockAffectedFile('base/stuff.h', '', action='M'),
1840     ]
1841     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1842         mock_input_api, MockOutputApi())
1843     self.assertEqual(0, len(warnings))
1844
1845   def testDeleteHeader(self):
1846     mock_input_api = MockInputApi()
1847     mock_input_api.files = [
1848       MockAffectedFile('base/stuff.h', '', action='D'),
1849     ]
1850     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1851         mock_input_api, MockOutputApi())
1852     self.assertEqual(0, len(warnings))
1853
1854   def testAddHeaderWithGn(self):
1855     mock_input_api = MockInputApi()
1856     mock_input_api.files = [
1857       MockAffectedFile('base/stuff.h', ''),
1858       MockAffectedFile('base/BUILD.gn', 'stuff.h'),
1859     ]
1860     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1861         mock_input_api, MockOutputApi())
1862     self.assertEqual(0, len(warnings))
1863
1864   def testAddHeaderWithGni(self):
1865     mock_input_api = MockInputApi()
1866     mock_input_api.files = [
1867       MockAffectedFile('base/stuff.h', ''),
1868       MockAffectedFile('base/files.gni', 'stuff.h'),
1869     ]
1870     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1871         mock_input_api, MockOutputApi())
1872     self.assertEqual(0, len(warnings))
1873
1874   def testAddHeaderWithOther(self):
1875     mock_input_api = MockInputApi()
1876     mock_input_api.files = [
1877       MockAffectedFile('base/stuff.h', ''),
1878       MockAffectedFile('base/stuff.cc', 'stuff.h'),
1879     ]
1880     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1881         mock_input_api, MockOutputApi())
1882     self.assertEqual(1, len(warnings))
1883
1884   def testAddHeaderWithWrongGn(self):
1885     mock_input_api = MockInputApi()
1886     mock_input_api.files = [
1887       MockAffectedFile('base/stuff.h', ''),
1888       MockAffectedFile('base/BUILD.gn', 'stuff_h'),
1889     ]
1890     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1891         mock_input_api, MockOutputApi())
1892     self.assertEqual(1, len(warnings))
1893
1894   def testAddHeadersWithGn(self):
1895     mock_input_api = MockInputApi()
1896     mock_input_api.files = [
1897       MockAffectedFile('base/stuff.h', ''),
1898       MockAffectedFile('base/another.h', ''),
1899       MockAffectedFile('base/BUILD.gn', 'another.h\nstuff.h'),
1900     ]
1901     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1902         mock_input_api, MockOutputApi())
1903     self.assertEqual(0, len(warnings))
1904
1905   def testAddHeadersWithWrongGn(self):
1906     mock_input_api = MockInputApi()
1907     mock_input_api.files = [
1908       MockAffectedFile('base/stuff.h', ''),
1909       MockAffectedFile('base/another.h', ''),
1910       MockAffectedFile('base/BUILD.gn', 'another_h\nstuff.h'),
1911     ]
1912     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1913         mock_input_api, MockOutputApi())
1914     self.assertEqual(1, len(warnings))
1915     self.assertFalse('base/stuff.h' in warnings[0].items)
1916     self.assertTrue('base/another.h' in warnings[0].items)
1917
1918   def testAddHeadersWithWrongGn2(self):
1919     mock_input_api = MockInputApi()
1920     mock_input_api.files = [
1921       MockAffectedFile('base/stuff.h', ''),
1922       MockAffectedFile('base/another.h', ''),
1923       MockAffectedFile('base/BUILD.gn', 'another_h\nstuff_h'),
1924     ]
1925     warnings = PRESUBMIT.CheckNewHeaderWithoutGnChangeOnUpload(
1926         mock_input_api, MockOutputApi())
1927     self.assertEqual(1, len(warnings))
1928     self.assertTrue('base/stuff.h' in warnings[0].items)
1929     self.assertTrue('base/another.h' in warnings[0].items)
1930
1931
1932 class CorrectProductNameInMessagesTest(unittest.TestCase):
1933   def testProductNameInDesc(self):
1934     mock_input_api = MockInputApi()
1935     mock_input_api.files = [
1936       MockAffectedFile('chrome/app/google_chrome_strings.grd', [
1937         '<message name="Foo" desc="Welcome to Chrome">',
1938         '  Welcome to Chrome!',
1939         '</message>',
1940       ]),
1941       MockAffectedFile('chrome/app/chromium_strings.grd', [
1942         '<message name="Bar" desc="Welcome to Chrome">',
1943         '  Welcome to Chromium!',
1944         '</message>',
1945       ]),
1946     ]
1947     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
1948         mock_input_api, MockOutputApi())
1949     self.assertEqual(0, len(warnings))
1950
1951   def testChromeInChromium(self):
1952     mock_input_api = MockInputApi()
1953     mock_input_api.files = [
1954       MockAffectedFile('chrome/app/google_chrome_strings.grd', [
1955         '<message name="Foo" desc="Welcome to Chrome">',
1956         '  Welcome to Chrome!',
1957         '</message>',
1958       ]),
1959       MockAffectedFile('chrome/app/chromium_strings.grd', [
1960         '<message name="Bar" desc="Welcome to Chrome">',
1961         '  Welcome to Chrome!',
1962         '</message>',
1963       ]),
1964     ]
1965     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
1966         mock_input_api, MockOutputApi())
1967     self.assertEqual(1, len(warnings))
1968     self.assertTrue('chrome/app/chromium_strings.grd' in warnings[0].items[0])
1969
1970   def testChromiumInChrome(self):
1971     mock_input_api = MockInputApi()
1972     mock_input_api.files = [
1973       MockAffectedFile('chrome/app/google_chrome_strings.grd', [
1974         '<message name="Foo" desc="Welcome to Chrome">',
1975         '  Welcome to Chromium!',
1976         '</message>',
1977       ]),
1978       MockAffectedFile('chrome/app/chromium_strings.grd', [
1979         '<message name="Bar" desc="Welcome to Chrome">',
1980         '  Welcome to Chromium!',
1981         '</message>',
1982       ]),
1983     ]
1984     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
1985         mock_input_api, MockOutputApi())
1986     self.assertEqual(1, len(warnings))
1987     self.assertTrue(
1988         'chrome/app/google_chrome_strings.grd:2' in warnings[0].items[0])
1989
1990   def testMultipleInstances(self):
1991     mock_input_api = MockInputApi()
1992     mock_input_api.files = [
1993       MockAffectedFile('chrome/app/chromium_strings.grd', [
1994         '<message name="Bar" desc="Welcome to Chrome">',
1995         '  Welcome to Chrome!',
1996         '</message>',
1997         '<message name="Baz" desc="A correct message">',
1998         '  Chromium is the software you are using.',
1999         '</message>',
2000         '<message name="Bat" desc="An incorrect message">',
2001         '  Google Chrome is the software you are using.',
2002         '</message>',
2003       ]),
2004     ]
2005     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2006         mock_input_api, MockOutputApi())
2007     self.assertEqual(1, len(warnings))
2008     self.assertTrue(
2009         'chrome/app/chromium_strings.grd:2' in warnings[0].items[0])
2010     self.assertTrue(
2011         'chrome/app/chromium_strings.grd:8' in warnings[0].items[1])
2012
2013   def testMultipleWarnings(self):
2014     mock_input_api = MockInputApi()
2015     mock_input_api.files = [
2016       MockAffectedFile('chrome/app/chromium_strings.grd', [
2017         '<message name="Bar" desc="Welcome to Chrome">',
2018         '  Welcome to Chrome!',
2019         '</message>',
2020         '<message name="Baz" desc="A correct message">',
2021         '  Chromium is the software you are using.',
2022         '</message>',
2023         '<message name="Bat" desc="An incorrect message">',
2024         '  Google Chrome is the software you are using.',
2025         '</message>',
2026       ]),
2027       MockAffectedFile('components/components_google_chrome_strings.grd', [
2028         '<message name="Bar" desc="Welcome to Chrome">',
2029         '  Welcome to Chrome!',
2030         '</message>',
2031         '<message name="Baz" desc="A correct message">',
2032         '  Chromium is the software you are using.',
2033         '</message>',
2034         '<message name="Bat" desc="An incorrect message">',
2035         '  Google Chrome is the software you are using.',
2036         '</message>',
2037       ]),
2038     ]
2039     warnings = PRESUBMIT.CheckCorrectProductNameInMessages(
2040         mock_input_api, MockOutputApi())
2041     self.assertEqual(2, len(warnings))
2042     self.assertTrue(
2043         'components/components_google_chrome_strings.grd:5'
2044              in warnings[0].items[0])
2045     self.assertTrue(
2046         'chrome/app/chromium_strings.grd:2' in warnings[1].items[0])
2047     self.assertTrue(
2048         'chrome/app/chromium_strings.grd:8' in warnings[1].items[1])
2049
2050
2051 class ServiceManifestOwnerTest(unittest.TestCase):
2052   def testServiceManifestChangeNeedsSecurityOwner(self):
2053     mock_input_api = MockInputApi()
2054     mock_input_api.files = [
2055       MockAffectedFile('services/goat/public/cpp/manifest.cc',
2056                        [
2057                          '#include "services/goat/public/cpp/manifest.h"',
2058                          'const service_manager::Manifest& GetManifest() {}',
2059                        ])]
2060     mock_output_api = MockOutputApi()
2061     errors = PRESUBMIT.CheckSecurityOwners(
2062         mock_input_api, mock_output_api)
2063     self.assertEqual(1, len(errors))
2064     self.assertEqual(
2065         'Found OWNERS files that need to be updated for IPC security review ' +
2066         'coverage.\nPlease update the OWNERS files below:', errors[0].message)
2067
2068   def testNonServiceManifestSourceChangesDoNotRequireSecurityOwner(self):
2069     mock_input_api = MockInputApi()
2070     mock_input_api.files = [
2071       MockAffectedFile('some/non/service/thing/foo_manifest.cc',
2072                        [
2073                          'const char kNoEnforcement[] = "not a manifest!";',
2074                        ])]
2075     mock_output_api = MockOutputApi()
2076     errors = PRESUBMIT.CheckSecurityOwners(
2077         mock_input_api, mock_output_api)
2078     self.assertEqual([], errors)
2079
2080
2081 class FuchsiaSecurityOwnerTest(unittest.TestCase):
2082   def testFidlChangeNeedsSecurityOwner(self):
2083     mock_input_api = MockInputApi()
2084     mock_input_api.files = [
2085       MockAffectedFile('potentially/scary/ipc.fidl',
2086                        [
2087                          'library test.fidl'
2088                        ])]
2089     mock_output_api = MockOutputApi()
2090     errors = PRESUBMIT.CheckSecurityOwners(
2091         mock_input_api, mock_output_api)
2092     self.assertEqual(1, len(errors))
2093     self.assertEqual(
2094         'Found OWNERS files that need to be updated for IPC security review ' +
2095         'coverage.\nPlease update the OWNERS files below:', errors[0].message)
2096
2097   def testComponentManifestV1ChangeNeedsSecurityOwner(self):
2098     mock_input_api = MockInputApi()
2099     mock_input_api.files = [
2100       MockAffectedFile('potentially/scary/v2_manifest.cmx',
2101                        [
2102                          '{ "that is no": "manifest!" }'
2103                        ])]
2104     mock_output_api = MockOutputApi()
2105     errors = PRESUBMIT.CheckSecurityOwners(
2106         mock_input_api, mock_output_api)
2107     self.assertEqual(1, len(errors))
2108     self.assertEqual(
2109         'Found OWNERS files that need to be updated for IPC security review ' +
2110         'coverage.\nPlease update the OWNERS files below:', errors[0].message)
2111
2112   def testComponentManifestV2NeedsSecurityOwner(self):
2113     mock_input_api = MockInputApi()
2114     mock_input_api.files = [
2115       MockAffectedFile('potentially/scary/v2_manifest.cml',
2116                        [
2117                          '{ "that is no": "manifest!" }'
2118                        ])]
2119     mock_output_api = MockOutputApi()
2120     errors = PRESUBMIT.CheckSecurityOwners(
2121         mock_input_api, mock_output_api)
2122     self.assertEqual(1, len(errors))
2123     self.assertEqual(
2124         'Found OWNERS files that need to be updated for IPC security review ' +
2125         'coverage.\nPlease update the OWNERS files below:', errors[0].message)
2126
2127   def testThirdPartyTestsDoNotRequireSecurityOwner(self):
2128     mock_input_api = MockInputApi()
2129     mock_input_api.files = [
2130       MockAffectedFile('third_party/crashpad/test/tests.cmx',
2131                        [
2132                          'const char kNoEnforcement[] = "Security?!? Pah!";',
2133                        ])]
2134     mock_output_api = MockOutputApi()
2135     errors = PRESUBMIT.CheckSecurityOwners(
2136         mock_input_api, mock_output_api)
2137     self.assertEqual([], errors)
2138
2139   def testOtherFuchsiaChangesDoNotRequireSecurityOwner(self):
2140     mock_input_api = MockInputApi()
2141     mock_input_api.files = [
2142       MockAffectedFile('some/non/service/thing/fuchsia_fidl_cml_cmx_magic.cc',
2143                        [
2144                          'const char kNoEnforcement[] = "Security?!? Pah!";',
2145                        ])]
2146     mock_output_api = MockOutputApi()
2147     errors = PRESUBMIT.CheckSecurityOwners(
2148         mock_input_api, mock_output_api)
2149     self.assertEqual([], errors)
2150
2151
2152 class SecurityChangeTest(unittest.TestCase):
2153   class _MockOwnersClient(object):
2154     def ListOwners(self, f):
2155       return ['apple@chromium.org', 'orange@chromium.org']
2156
2157   def _mockChangeOwnerAndReviewers(self, input_api, owner, reviewers):
2158     def __MockOwnerAndReviewers(input_api, email_regexp, approval_needed=False):
2159       return [owner, reviewers]
2160     input_api.canned_checks.GetCodereviewOwnerAndReviewers = \
2161         __MockOwnerAndReviewers
2162
2163   def testDiffGetServiceSandboxType(self):
2164     mock_input_api = MockInputApi()
2165     mock_input_api.files = [
2166         MockAffectedFile(
2167           'services/goat/teleporter_host.cc',
2168           [
2169             'template <>',
2170             'inline content::SandboxType',
2171             'content::GetServiceSandboxType<chrome::mojom::GoatTeleporter>() {',
2172             '#if defined(OS_WIN)',
2173             '  return SandboxType::kGoaty;',
2174             '#else',
2175             '  return SandboxType::kNoSandbox;',
2176             '#endif  // !defined(OS_WIN)',
2177             '}'
2178           ]
2179         ),
2180     ]
2181     files_to_functions = PRESUBMIT._GetFilesUsingSecurityCriticalFunctions(
2182         mock_input_api)
2183     self.assertEqual({
2184         'services/goat/teleporter_host.cc': set([
2185             'content::GetServiceSandboxType<>()'
2186         ])},
2187         files_to_functions)
2188
2189   def testDiffRemovingLine(self):
2190     mock_input_api = MockInputApi()
2191     mock_file = MockAffectedFile('services/goat/teleporter_host.cc', '')
2192     mock_file._scm_diff = """--- old 2020-05-04 14:08:25.000000000 -0400
2193 +++ new 2020-05-04 14:08:32.000000000 -0400
2194 @@ -1,5 +1,4 @@
2195  template <>
2196  inline content::SandboxType
2197 -content::GetServiceSandboxType<chrome::mojom::GoatTeleporter>() {
2198  #if defined(OS_WIN)
2199    return SandboxType::kGoaty;
2200 """
2201     mock_input_api.files = [mock_file]
2202     files_to_functions = PRESUBMIT._GetFilesUsingSecurityCriticalFunctions(
2203         mock_input_api)
2204     self.assertEqual({
2205         'services/goat/teleporter_host.cc': set([
2206             'content::GetServiceSandboxType<>()'
2207         ])},
2208         files_to_functions)
2209
2210   def testChangeOwnersMissing(self):
2211     mock_input_api = MockInputApi()
2212     mock_input_api.owners_client = self._MockOwnersClient()
2213     mock_input_api.is_committing = False
2214     mock_input_api.files = [
2215         MockAffectedFile('file.cc', ['GetServiceSandboxType<Goat>(Sandbox)'])
2216     ]
2217     mock_output_api = MockOutputApi()
2218     self._mockChangeOwnerAndReviewers(
2219         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2220     result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2221     self.assertEqual(1, len(result))
2222     self.assertEqual(result[0].type, 'notify')
2223     self.assertEqual(result[0].message,
2224         'The following files change calls to security-sensive functions\n' \
2225         'that need to be reviewed by ipc/SECURITY_OWNERS.\n'
2226         '  file.cc\n'
2227         '    content::GetServiceSandboxType<>()\n\n')
2228
2229   def testChangeOwnersMissingAtCommit(self):
2230     mock_input_api = MockInputApi()
2231     mock_input_api.owners_client = self._MockOwnersClient()
2232     mock_input_api.is_committing = True
2233     mock_input_api.files = [
2234         MockAffectedFile('file.cc', ['GetServiceSandboxType<mojom::Goat>()'])
2235     ]
2236     mock_output_api = MockOutputApi()
2237     self._mockChangeOwnerAndReviewers(
2238         mock_input_api, 'owner@chromium.org', ['banana@chromium.org'])
2239     result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2240     self.assertEqual(1, len(result))
2241     self.assertEqual(result[0].type, 'error')
2242     self.assertEqual(result[0].message,
2243         'The following files change calls to security-sensive functions\n' \
2244         'that need to be reviewed by ipc/SECURITY_OWNERS.\n'
2245         '  file.cc\n'
2246         '    content::GetServiceSandboxType<>()\n\n')
2247
2248   def testChangeOwnersPresent(self):
2249     mock_input_api = MockInputApi()
2250     mock_input_api.owners_client = self._MockOwnersClient()
2251     mock_input_api.files = [
2252         MockAffectedFile('file.cc', ['WithSandboxType(Sandbox)'])
2253     ]
2254     mock_output_api = MockOutputApi()
2255     self._mockChangeOwnerAndReviewers(
2256         mock_input_api, 'owner@chromium.org',
2257         ['apple@chromium.org', 'banana@chromium.org'])
2258     result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2259     self.assertEqual(0, len(result))
2260
2261   def testChangeOwnerIsSecurityOwner(self):
2262     mock_input_api = MockInputApi()
2263     mock_input_api.owners_client = self._MockOwnersClient()
2264     mock_input_api.files = [
2265         MockAffectedFile('file.cc', ['GetServiceSandboxType<T>(Sandbox)'])
2266     ]
2267     mock_output_api = MockOutputApi()
2268     self._mockChangeOwnerAndReviewers(
2269         mock_input_api, 'orange@chromium.org', ['pear@chromium.org'])
2270     result = PRESUBMIT.CheckSecurityChanges(mock_input_api, mock_output_api)
2271     self.assertEqual(1, len(result))
2272
2273
2274 class BannedTypeCheckTest(unittest.TestCase):
2275
2276   def testBannedCppFunctions(self):
2277     input_api = MockInputApi()
2278     input_api.files = [
2279       MockFile('some/cpp/problematic/file.cc',
2280                ['using namespace std;']),
2281       MockFile('third_party/blink/problematic/file.cc',
2282                ['GetInterfaceProvider()']),
2283       MockFile('some/cpp/ok/file.cc',
2284                ['using std::string;']),
2285       MockFile('some/cpp/problematic/file2.cc',
2286                ['set_owned_by_client()']),
2287       MockFile('some/cpp/nocheck/file.cc',
2288                ['using namespace std;  // nocheck']),
2289       MockFile('some/cpp/comment/file.cc',
2290                ['  // A comment about `using namespace std;`']),
2291       MockFile('some/cpp/macro/file.h',
2292                ['DISALLOW_COPY_AND_ASSIGN(foo)']),
2293     ]
2294
2295     results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2296
2297      # warnings are results[0], errors are results[1]
2298     self.assertEqual(2, len(results))
2299     self.assertTrue('some/cpp/problematic/file.cc' in results[1].message)
2300     self.assertTrue(
2301         'third_party/blink/problematic/file.cc' in results[0].message)
2302     self.assertTrue('some/cpp/ok/file.cc' not in results[1].message)
2303     self.assertTrue('some/cpp/problematic/file2.cc' in results[0].message)
2304     self.assertFalse('some/cpp/nocheck/file.cc' in results[0].message)
2305     self.assertFalse('some/cpp/nocheck/file.cc' in results[1].message)
2306     self.assertFalse('some/cpp/comment/file.cc' in results[0].message)
2307     self.assertFalse('some/cpp/comment/file.cc' in results[1].message)
2308     self.assertTrue('some/cpp/macro/file.h' in results[0].message)
2309     self.assertFalse('some/cpp/macro/file.h' in results[1].message)
2310
2311   def testBannedIosObjcFunctions(self):
2312     input_api = MockInputApi()
2313     input_api.files = [
2314       MockFile('some/ios/file.mm',
2315                ['TEST(SomeClassTest, SomeInteraction) {',
2316                 '}']),
2317       MockFile('some/mac/file.mm',
2318                ['TEST(SomeClassTest, SomeInteraction) {',
2319                 '}']),
2320       MockFile('another/ios_file.mm',
2321                ['class SomeTest : public testing::Test {};']),
2322       MockFile('some/ios/file_egtest.mm',
2323                ['- (void)testSomething { EXPECT_OCMOCK_VERIFY(aMock); }']),
2324       MockFile('some/ios/file_unittest.mm',
2325                ['TEST_F(SomeTest, TestThis) { EXPECT_OCMOCK_VERIFY(aMock); }']),
2326     ]
2327
2328     errors = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2329     self.assertEqual(1, len(errors))
2330     self.assertTrue('some/ios/file.mm' in errors[0].message)
2331     self.assertTrue('another/ios_file.mm' in errors[0].message)
2332     self.assertTrue('some/mac/file.mm' not in errors[0].message)
2333     self.assertTrue('some/ios/file_egtest.mm' in errors[0].message)
2334     self.assertTrue('some/ios/file_unittest.mm' not in errors[0].message)
2335
2336   def testBannedMojoFunctions(self):
2337     input_api = MockInputApi()
2338     input_api.files = [
2339       MockFile('some/cpp/problematic/file2.cc',
2340                ['mojo::ConvertTo<>']),
2341       MockFile('third_party/blink/ok/file3.cc',
2342                ['mojo::ConvertTo<>']),
2343       MockFile('content/renderer/ok/file3.cc',
2344                ['mojo::ConvertTo<>']),
2345     ]
2346
2347     results = PRESUBMIT.CheckNoBannedFunctions(input_api, MockOutputApi())
2348
2349     # warnings are results[0], errors are results[1]
2350     self.assertEqual(1, len(results))
2351     self.assertTrue('some/cpp/problematic/file2.cc' in results[0].message)
2352     self.assertTrue('third_party/blink/ok/file3.cc' not in results[0].message)
2353     self.assertTrue('content/renderer/ok/file3.cc' not in results[0].message)
2354
2355   def testDeprecatedMojoTypes(self):
2356     ok_paths = ['components/arc']
2357     warning_paths = ['some/cpp']
2358     error_paths = ['third_party/blink', 'content']
2359     test_cases = [
2360       {
2361         'type': 'mojo::AssociatedInterfacePtrInfo<>',
2362         'file': 'file4.cc'
2363       },
2364       {
2365         'type': 'mojo::AssociatedInterfaceRequest<>',
2366         'file': 'file5.cc'
2367       },
2368       {
2369         'type': 'mojo::InterfacePtr<>',
2370         'file': 'file8.cc'
2371       },
2372       {
2373         'type': 'mojo::InterfacePtrInfo<>',
2374         'file': 'file9.cc'
2375       },
2376       {
2377         'type': 'mojo::InterfaceRequest<>',
2378         'file': 'file10.cc'
2379       },
2380       {
2381         'type': 'mojo::MakeRequest()',
2382         'file': 'file11.cc'
2383       },
2384     ]
2385
2386     # Build the list of MockFiles considering paths that should trigger warnings
2387     # as well as paths that should trigger errors.
2388     input_api = MockInputApi()
2389     input_api.files = []
2390     for test_case in test_cases:
2391       for path in ok_paths:
2392         input_api.files.append(MockFile(os.path.join(path, test_case['file']),
2393                                         [test_case['type']]))
2394       for path in warning_paths:
2395         input_api.files.append(MockFile(os.path.join(path, test_case['file']),
2396                                         [test_case['type']]))
2397       for path in error_paths:
2398         input_api.files.append(MockFile(os.path.join(path, test_case['file']),
2399                                         [test_case['type']]))
2400
2401     results = PRESUBMIT.CheckNoDeprecatedMojoTypes(input_api, MockOutputApi())
2402
2403     # warnings are results[0], errors are results[1]
2404     self.assertEqual(2, len(results))
2405
2406     for test_case in test_cases:
2407       # Check that no warnings nor errors have been triggered for these paths.
2408       for path in ok_paths:
2409         self.assertFalse(path in results[0].message)
2410         self.assertFalse(path in results[1].message)
2411
2412       # Check warnings have been triggered for these paths.
2413       for path in warning_paths:
2414         self.assertTrue(path in results[0].message)
2415         self.assertFalse(path in results[1].message)
2416
2417       # Check errors have been triggered for these paths.
2418       for path in error_paths:
2419         self.assertFalse(path in results[0].message)
2420         self.assertTrue(path in results[1].message)
2421
2422
2423 class NoProductionCodeUsingTestOnlyFunctionsTest(unittest.TestCase):
2424   def testTruePositives(self):
2425     mock_input_api = MockInputApi()
2426     mock_input_api.files = [
2427       MockFile('some/path/foo.cc', ['foo_for_testing();']),
2428       MockFile('some/path/foo.mm', ['FooForTesting();']),
2429       MockFile('some/path/foo.cxx', ['FooForTests();']),
2430       MockFile('some/path/foo.cpp', ['foo_for_test();']),
2431     ]
2432
2433     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctions(
2434         mock_input_api, MockOutputApi())
2435     self.assertEqual(1, len(results))
2436     self.assertEqual(4, len(results[0].items))
2437     self.assertTrue('foo.cc' in results[0].items[0])
2438     self.assertTrue('foo.mm' in results[0].items[1])
2439     self.assertTrue('foo.cxx' in results[0].items[2])
2440     self.assertTrue('foo.cpp' in results[0].items[3])
2441
2442   def testFalsePositives(self):
2443     mock_input_api = MockInputApi()
2444     mock_input_api.files = [
2445       MockFile('some/path/foo.h', ['foo_for_testing();']),
2446       MockFile('some/path/foo.mm', ['FooForTesting() {']),
2447       MockFile('some/path/foo.cc', ['::FooForTests();']),
2448       MockFile('some/path/foo.cpp', ['// foo_for_test();']),
2449     ]
2450
2451     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctions(
2452         mock_input_api, MockOutputApi())
2453     self.assertEqual(0, len(results))
2454
2455   def testAllowedFiles(self):
2456     mock_input_api = MockInputApi()
2457     mock_input_api.files = [
2458       MockFile('path/foo_unittest.cc', ['foo_for_testing();']),
2459       MockFile('path/bar_unittest_mac.cc', ['foo_for_testing();']),
2460       MockFile('path/baz_unittests.cc', ['foo_for_testing();']),
2461     ]
2462
2463     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctions(
2464         mock_input_api, MockOutputApi())
2465     self.assertEqual(0, len(results))
2466
2467
2468 class NoProductionJavaCodeUsingTestOnlyFunctionsTest(unittest.TestCase):
2469   def testTruePositives(self):
2470     mock_input_api = MockInputApi()
2471     mock_input_api.files = [
2472       MockFile('dir/java/src/foo.java', ['FooForTesting();']),
2473       MockFile('dir/java/src/bar.java', ['FooForTests(x);']),
2474       MockFile('dir/java/src/baz.java', ['FooForTest(', 'y', ');']),
2475       MockFile('dir/java/src/mult.java', [
2476         'int x = SomethingLongHere()',
2477         '    * SomethingLongHereForTesting();'
2478       ])
2479     ]
2480
2481     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctionsJava(
2482         mock_input_api, MockOutputApi())
2483     self.assertEqual(1, len(results))
2484     self.assertEqual(4, len(results[0].items))
2485     self.assertTrue('foo.java' in results[0].items[0])
2486     self.assertTrue('bar.java' in results[0].items[1])
2487     self.assertTrue('baz.java' in results[0].items[2])
2488     self.assertTrue('mult.java' in results[0].items[3])
2489
2490   def testFalsePositives(self):
2491     mock_input_api = MockInputApi()
2492     mock_input_api.files = [
2493       MockFile('dir/java/src/foo.xml', ['FooForTesting();']),
2494       MockFile('dir/java/src/foo.java', ['FooForTests() {']),
2495       MockFile('dir/java/src/bar.java', ['// FooForTest();']),
2496       MockFile('dir/java/src/bar2.java', ['x = 1; // FooForTest();']),
2497       MockFile('dir/java/src/bar3.java', ['@VisibleForTesting']),
2498       MockFile('dir/java/src/bar4.java', ['@VisibleForTesting()']),
2499       MockFile('dir/java/src/bar5.java', [
2500         '@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)'
2501       ]),
2502       MockFile('dir/javatests/src/baz.java', ['FooForTest(', 'y', ');']),
2503       MockFile('dir/junit/src/baz.java', ['FooForTest(', 'y', ');']),
2504       MockFile('dir/junit/src/javadoc.java', [
2505         '/** Use FooForTest(); to obtain foo in tests.'
2506         ' */'
2507       ]),
2508       MockFile('dir/junit/src/javadoc2.java', [
2509         '/** ',
2510         ' * Use FooForTest(); to obtain foo in tests.'
2511         ' */'
2512       ]),
2513     ]
2514
2515     results = PRESUBMIT.CheckNoProductionCodeUsingTestOnlyFunctionsJava(
2516         mock_input_api, MockOutputApi())
2517     self.assertEqual(0, len(results))
2518
2519
2520 class NewImagesWarningTest(unittest.TestCase):
2521   def testTruePositives(self):
2522     mock_input_api = MockInputApi()
2523     mock_input_api.files = [
2524       MockFile('dir/android/res/drawable/foo.png', []),
2525       MockFile('dir/android/res/drawable-v21/bar.svg', []),
2526       MockFile('dir/android/res/mipmap-v21-en/baz.webp', []),
2527       MockFile('dir/android/res_gshoe/drawable-mdpi/foobar.png', []),
2528     ]
2529
2530     results = PRESUBMIT._CheckNewImagesWarning(mock_input_api, MockOutputApi())
2531     self.assertEqual(1, len(results))
2532     self.assertEqual(4, len(results[0].items))
2533     self.assertTrue('foo.png' in results[0].items[0].LocalPath())
2534     self.assertTrue('bar.svg' in results[0].items[1].LocalPath())
2535     self.assertTrue('baz.webp' in results[0].items[2].LocalPath())
2536     self.assertTrue('foobar.png' in results[0].items[3].LocalPath())
2537
2538   def testFalsePositives(self):
2539     mock_input_api = MockInputApi()
2540     mock_input_api.files = [
2541       MockFile('dir/pngs/README.md', []),
2542       MockFile('java/test/res/drawable/foo.png', []),
2543       MockFile('third_party/blink/foo.png', []),
2544       MockFile('dir/third_party/libpng/src/foo.cc', ['foobar']),
2545       MockFile('dir/resources.webp/.gitignore', ['foo.png']),
2546     ]
2547
2548     results = PRESUBMIT._CheckNewImagesWarning(mock_input_api, MockOutputApi())
2549     self.assertEqual(0, len(results))
2550
2551
2552 class CheckUniquePtrTest(unittest.TestCase):
2553   def testTruePositivesNullptr(self):
2554     mock_input_api = MockInputApi()
2555     mock_input_api.files = [
2556       MockFile('dir/baz.cc', ['std::unique_ptr<T>()']),
2557       MockFile('dir/baz-p.cc', ['std::unique_ptr<T<P>>()']),
2558     ]
2559
2560     results = PRESUBMIT.CheckUniquePtrOnUpload(mock_input_api, MockOutputApi())
2561     self.assertEqual(1, len(results))
2562     self.assertTrue('nullptr' in results[0].message)
2563     self.assertEqual(2, len(results[0].items))
2564     self.assertTrue('baz.cc' in results[0].items[0])
2565     self.assertTrue('baz-p.cc' in results[0].items[1])
2566
2567   def testTruePositivesConstructor(self):
2568     mock_input_api = MockInputApi()
2569     mock_input_api.files = [
2570       MockFile('dir/foo.cc', ['return std::unique_ptr<T>(foo);']),
2571       MockFile('dir/bar.mm', ['bar = std::unique_ptr<T>(foo)']),
2572       MockFile('dir/mult.cc', [
2573         'return',
2574         '    std::unique_ptr<T>(barVeryVeryLongFooSoThatItWouldNotFitAbove);'
2575       ]),
2576       MockFile('dir/mult2.cc', [
2577         'barVeryVeryLongLongBaaaaaarSoThatTheLineLimitIsAlmostReached =',
2578         '    std::unique_ptr<T>(foo);'
2579       ]),
2580       MockFile('dir/mult3.cc', [
2581         'bar = std::unique_ptr<T>(',
2582         '    fooVeryVeryVeryLongStillGoingWellThisWillTakeAWhileFinallyThere);'
2583       ]),
2584       MockFile('dir/multi_arg.cc', [
2585           'auto p = std::unique_ptr<std::pair<T, D>>(new std::pair(T, D));']),
2586     ]
2587
2588     results = PRESUBMIT.CheckUniquePtrOnUpload(mock_input_api, MockOutputApi())
2589     self.assertEqual(1, len(results))
2590     self.assertTrue('std::make_unique' in results[0].message)
2591     self.assertEqual(6, len(results[0].items))
2592     self.assertTrue('foo.cc' in results[0].items[0])
2593     self.assertTrue('bar.mm' in results[0].items[1])
2594     self.assertTrue('mult.cc' in results[0].items[2])
2595     self.assertTrue('mult2.cc' in results[0].items[3])
2596     self.assertTrue('mult3.cc' in results[0].items[4])
2597     self.assertTrue('multi_arg.cc' in results[0].items[5])
2598
2599   def testFalsePositives(self):
2600     mock_input_api = MockInputApi()
2601     mock_input_api.files = [
2602       MockFile('dir/foo.cc', ['return std::unique_ptr<T[]>(foo);']),
2603       MockFile('dir/bar.mm', ['bar = std::unique_ptr<T[]>(foo)']),
2604       MockFile('dir/file.cc', ['std::unique_ptr<T> p = Foo();']),
2605       MockFile('dir/baz.cc', [
2606         'std::unique_ptr<T> result = std::make_unique<T>();'
2607       ]),
2608       MockFile('dir/baz2.cc', [
2609         'std::unique_ptr<T> result = std::make_unique<T>('
2610       ]),
2611       MockFile('dir/nested.cc', ['set<std::unique_ptr<T>>();']),
2612       MockFile('dir/nested2.cc', ['map<U, std::unique_ptr<T>>();']),
2613
2614       # Two-argument invocation of std::unique_ptr is exempt because there is
2615       # no equivalent using std::make_unique.
2616       MockFile('dir/multi_arg.cc', [
2617         'auto p = std::unique_ptr<T, D>(new T(), D());']),
2618     ]
2619
2620     results = PRESUBMIT.CheckUniquePtrOnUpload(mock_input_api, MockOutputApi())
2621     self.assertEqual(0, len(results))
2622
2623 class CheckNoDirectIncludesHeadersWhichRedefineStrCat(unittest.TestCase):
2624   def testBlocksDirectIncludes(self):
2625     mock_input_api = MockInputApi()
2626     mock_input_api.files = [
2627       MockFile('dir/foo_win.cc', ['#include "shlwapi.h"']),
2628       MockFile('dir/bar.h', ['#include <propvarutil.h>']),
2629       MockFile('dir/baz.h', ['#include <atlbase.h>']),
2630       MockFile('dir/jumbo.h', ['#include "sphelper.h"']),
2631     ]
2632     results = PRESUBMIT._CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
2633     self.assertEqual(1, len(results))
2634     self.assertEqual(4, len(results[0].items))
2635     self.assertTrue('StrCat' in results[0].message)
2636     self.assertTrue('foo_win.cc' in results[0].items[0])
2637     self.assertTrue('bar.h' in results[0].items[1])
2638     self.assertTrue('baz.h' in results[0].items[2])
2639     self.assertTrue('jumbo.h' in results[0].items[3])
2640
2641   def testAllowsToIncludeWrapper(self):
2642     mock_input_api = MockInputApi()
2643     mock_input_api.files = [
2644       MockFile('dir/baz_win.cc', ['#include "base/win/shlwapi.h"']),
2645       MockFile('dir/baz-win.h', ['#include "base/win/atl.h"']),
2646     ]
2647     results = PRESUBMIT._CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
2648     self.assertEqual(0, len(results))
2649
2650   def testAllowsToCreateWrapper(self):
2651     mock_input_api = MockInputApi()
2652     mock_input_api.files = [
2653       MockFile('base/win/shlwapi.h', [
2654         '#include <shlwapi.h>',
2655         '#include "base/win/windows_defines.inc"']),
2656     ]
2657     results = PRESUBMIT._CheckNoStrCatRedefines(mock_input_api, MockOutputApi())
2658     self.assertEqual(0, len(results))
2659
2660
2661 class StringTest(unittest.TestCase):
2662   """Tests ICU syntax check and translation screenshots check."""
2663
2664   # An empty grd file.
2665   OLD_GRD_CONTENTS = """<?xml version="1.0" encoding="UTF-8"?>
2666            <grit latest_public_release="1" current_release="1">
2667              <release seq="1">
2668                <messages></messages>
2669              </release>
2670            </grit>
2671         """.splitlines()
2672   # A grd file with a single message.
2673   NEW_GRD_CONTENTS1 = """<?xml version="1.0" encoding="UTF-8"?>
2674            <grit latest_public_release="1" current_release="1">
2675              <release seq="1">
2676                <messages>
2677                  <message name="IDS_TEST1">
2678                    Test string 1
2679                  </message>
2680                  <message name="IDS_TEST_STRING_NON_TRANSLATEABLE1"
2681                      translateable="false">
2682                    Non translateable message 1, should be ignored
2683                  </message>
2684                  <message name="IDS_TEST_STRING_ACCESSIBILITY"
2685                      is_accessibility_with_no_ui="true">
2686                    Accessibility label 1, should be ignored
2687                  </message>
2688                </messages>
2689              </release>
2690            </grit>
2691         """.splitlines()
2692   # A grd file with two messages.
2693   NEW_GRD_CONTENTS2 = """<?xml version="1.0" encoding="UTF-8"?>
2694            <grit latest_public_release="1" current_release="1">
2695              <release seq="1">
2696                <messages>
2697                  <message name="IDS_TEST1">
2698                    Test string 1
2699                  </message>
2700                  <message name="IDS_TEST2">
2701                    Test string 2
2702                  </message>
2703                  <message name="IDS_TEST_STRING_NON_TRANSLATEABLE2"
2704                      translateable="false">
2705                    Non translateable message 2, should be ignored
2706                  </message>
2707                </messages>
2708              </release>
2709            </grit>
2710         """.splitlines()
2711   # A grd file with one ICU syntax message without syntax errors.
2712   NEW_GRD_CONTENTS_ICU_SYNTAX_OK1 = """<?xml version="1.0" encoding="UTF-8"?>
2713            <grit latest_public_release="1" current_release="1">
2714              <release seq="1">
2715                <messages>
2716                  <message name="IDS_TEST1">
2717                    {NUM, plural,
2718                     =1 {Test text for numeric one}
2719                     other {Test text for plural with {NUM} as number}}
2720                  </message>
2721                </messages>
2722              </release>
2723            </grit>
2724         """.splitlines()
2725   # A grd file with one ICU syntax message without syntax errors.
2726   NEW_GRD_CONTENTS_ICU_SYNTAX_OK2 = """<?xml version="1.0" encoding="UTF-8"?>
2727            <grit latest_public_release="1" current_release="1">
2728              <release seq="1">
2729                <messages>
2730                  <message name="IDS_TEST1">
2731                    {NUM, plural,
2732                     =1 {Different test text for numeric one}
2733                     other {Different test text for plural with {NUM} as number}}
2734                  </message>
2735                </messages>
2736              </release>
2737            </grit>
2738         """.splitlines()
2739   # A grd file with one ICU syntax message with syntax errors (misses a comma).
2740   NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR = """<?xml version="1.0" encoding="UTF-8"?>
2741            <grit latest_public_release="1" current_release="1">
2742              <release seq="1">
2743                <messages>
2744                  <message name="IDS_TEST1">
2745                    {NUM, plural
2746                     =1 {Test text for numeric one}
2747                     other {Test text for plural with {NUM} as number}}
2748                  </message>
2749                </messages>
2750              </release>
2751            </grit>
2752         """.splitlines()
2753
2754   OLD_GRDP_CONTENTS = (
2755     '<?xml version="1.0" encoding="utf-8"?>',
2756       '<grit-part>',
2757     '</grit-part>'
2758   )
2759
2760   NEW_GRDP_CONTENTS1 = (
2761     '<?xml version="1.0" encoding="utf-8"?>',
2762       '<grit-part>',
2763         '<message name="IDS_PART_TEST1">',
2764           'Part string 1',
2765         '</message>',
2766     '</grit-part>')
2767
2768   NEW_GRDP_CONTENTS2 = (
2769     '<?xml version="1.0" encoding="utf-8"?>',
2770       '<grit-part>',
2771         '<message name="IDS_PART_TEST1">',
2772           'Part string 1',
2773         '</message>',
2774         '<message name="IDS_PART_TEST2">',
2775           'Part string 2',
2776       '</message>',
2777     '</grit-part>')
2778
2779   NEW_GRDP_CONTENTS3 = (
2780     '<?xml version="1.0" encoding="utf-8"?>',
2781       '<grit-part>',
2782         '<message name="IDS_PART_TEST1" desc="Description with typo.">',
2783           'Part string 1',
2784         '</message>',
2785     '</grit-part>')
2786
2787   NEW_GRDP_CONTENTS4 = (
2788     '<?xml version="1.0" encoding="utf-8"?>',
2789       '<grit-part>',
2790         '<message name="IDS_PART_TEST1" desc="Description with typo fixed.">',
2791           'Part string 1',
2792         '</message>',
2793     '</grit-part>')
2794
2795   NEW_GRDP_CONTENTS5 = (
2796     '<?xml version="1.0" encoding="utf-8"?>',
2797       '<grit-part>',
2798         '<message name="IDS_PART_TEST1" meaning="Meaning with typo.">',
2799           'Part string 1',
2800         '</message>',
2801     '</grit-part>')
2802
2803   NEW_GRDP_CONTENTS6 = (
2804     '<?xml version="1.0" encoding="utf-8"?>',
2805       '<grit-part>',
2806         '<message name="IDS_PART_TEST1" meaning="Meaning with typo fixed.">',
2807           'Part string 1',
2808         '</message>',
2809     '</grit-part>')
2810
2811   # A grdp file with one ICU syntax message without syntax errors.
2812   NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1 = (
2813     '<?xml version="1.0" encoding="utf-8"?>',
2814       '<grit-part>',
2815         '<message name="IDS_PART_TEST1">',
2816            '{NUM, plural,',
2817             '=1 {Test text for numeric one}',
2818             'other {Test text for plural with {NUM} as number}}',
2819         '</message>',
2820     '</grit-part>')
2821   # A grdp file with one ICU syntax message without syntax errors.
2822   NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2 = (
2823     '<?xml version="1.0" encoding="utf-8"?>',
2824       '<grit-part>',
2825         '<message name="IDS_PART_TEST1">',
2826            '{NUM, plural,',
2827             '=1 {Different test text for numeric one}',
2828             'other {Different test text for plural with {NUM} as number}}',
2829         '</message>',
2830     '</grit-part>')
2831
2832   # A grdp file with one ICU syntax message with syntax errors (superfluent
2833   # whitespace).
2834   NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR = (
2835     '<?xml version="1.0" encoding="utf-8"?>',
2836       '<grit-part>',
2837         '<message name="IDS_PART_TEST1">',
2838            '{NUM, plural,',
2839             '= 1 {Test text for numeric one}',
2840             'other {Test text for plural with {NUM} as number}}',
2841         '</message>',
2842     '</grit-part>')
2843
2844   DO_NOT_UPLOAD_PNG_MESSAGE = ('Do not include actual screenshots in the '
2845                                'changelist. Run '
2846                                'tools/translate/upload_screenshots.py to '
2847                                'upload them instead:')
2848   GENERATE_SIGNATURES_MESSAGE = ('You are adding or modifying UI strings.\n'
2849                                  'To ensure the best translations, take '
2850                                  'screenshots of the relevant UI '
2851                                  '(https://g.co/chrome/translation) and add '
2852                                  'these files to your changelist:')
2853   REMOVE_SIGNATURES_MESSAGE = ('You removed strings associated with these '
2854                                'files. Remove:')
2855   ICU_SYNTAX_ERROR_MESSAGE = ('ICU syntax errors were found in the following '
2856                               'strings (problems or feedback? Contact '
2857                               'rainhard@chromium.org):')
2858
2859   def makeInputApi(self, files):
2860     input_api = MockInputApi()
2861     input_api.files = files
2862     # Override os_path.exists because the presubmit uses the actual
2863     # os.path.exists.
2864     input_api.CreateMockFileInPath(
2865         [x.LocalPath() for x in input_api.AffectedFiles(include_deletes=True)])
2866     return input_api
2867
2868   """ CL modified and added messages, but didn't add any screenshots."""
2869   def testNoScreenshots(self):
2870     # No new strings (file contents same). Should not warn.
2871     input_api = self.makeInputApi([
2872       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS1,
2873                        self.NEW_GRD_CONTENTS1, action='M'),
2874       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS1,
2875                        self.NEW_GRDP_CONTENTS1, action='M')])
2876     warnings = PRESUBMIT.CheckStrings(input_api,
2877                                                       MockOutputApi())
2878     self.assertEqual(0, len(warnings))
2879
2880     # Add two new strings. Should have two warnings.
2881     input_api = self.makeInputApi([
2882       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS2,
2883                        self.NEW_GRD_CONTENTS1, action='M'),
2884       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS2,
2885                        self.NEW_GRDP_CONTENTS1, action='M')])
2886     warnings = PRESUBMIT.CheckStrings(input_api,
2887                                                       MockOutputApi())
2888     self.assertEqual(1, len(warnings))
2889     self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[0].message)
2890     self.assertEqual('error', warnings[0].type)
2891     self.assertEqual([
2892       os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
2893       os.path.join('test_grd', 'IDS_TEST2.png.sha1')],
2894                      warnings[0].items)
2895
2896     # Add four new strings. Should have four warnings.
2897     input_api = self.makeInputApi([
2898       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS2,
2899                        self.OLD_GRD_CONTENTS, action='M'),
2900       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS2,
2901                        self.OLD_GRDP_CONTENTS, action='M')])
2902     warnings = PRESUBMIT.CheckStrings(input_api,
2903                                                       MockOutputApi())
2904     self.assertEqual(1, len(warnings))
2905     self.assertEqual('error', warnings[0].type)
2906     self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[0].message)
2907     self.assertEqual([
2908         os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
2909         os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
2910         os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
2911         os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
2912     ], warnings[0].items)
2913
2914   def testModifiedMessageDescription(self):
2915     # CL modified a message description for a message that does not yet have a
2916     # screenshot. Should not warn.
2917     input_api = self.makeInputApi([
2918       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS3,
2919                        self.NEW_GRDP_CONTENTS4, action='M')])
2920     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
2921     self.assertEqual(0, len(warnings))
2922
2923     # CL modified a message description for a message that already has a
2924     # screenshot. Should not warn.
2925     input_api = self.makeInputApi([
2926       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS3,
2927                        self.NEW_GRDP_CONTENTS4, action='M'),
2928       MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
2929                'binary', action='A')])
2930     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
2931     self.assertEqual(0, len(warnings))
2932
2933   def testModifiedMessageMeaning(self):
2934     # CL modified a message meaning for a message that does not yet have a
2935     # screenshot. Should warn.
2936     input_api = self.makeInputApi([
2937       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS5,
2938                        self.NEW_GRDP_CONTENTS6, action='M')])
2939     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
2940     self.assertEqual(1, len(warnings))
2941
2942     # CL modified a message meaning for a message that already has a
2943     # screenshot. Should not warn.
2944     input_api = self.makeInputApi([
2945       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS5,
2946                        self.NEW_GRDP_CONTENTS6, action='M'),
2947       MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
2948                'binary', action='A')])
2949     warnings = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
2950     self.assertEqual(0, len(warnings))
2951
2952   def testPngAddedSha1NotAdded(self):
2953     # CL added one new message in a grd file and added the png file associated
2954     # with it, but did not add the corresponding sha1 file. This should warn
2955     # twice:
2956     # - Once for the added png file (because we don't want developers to upload
2957     #   actual images)
2958     # - Once for the missing .sha1 file
2959     input_api = self.makeInputApi([
2960         MockAffectedFile(
2961             'test.grd',
2962             self.NEW_GRD_CONTENTS1,
2963             self.OLD_GRD_CONTENTS,
2964             action='M'),
2965         MockAffectedFile(
2966             os.path.join('test_grd', 'IDS_TEST1.png'), 'binary', action='A')
2967     ])
2968     warnings = PRESUBMIT.CheckStrings(input_api,
2969                                                       MockOutputApi())
2970     self.assertEqual(2, len(warnings))
2971     self.assertEqual('error', warnings[0].type)
2972     self.assertEqual(self.DO_NOT_UPLOAD_PNG_MESSAGE, warnings[0].message)
2973     self.assertEqual([os.path.join('test_grd', 'IDS_TEST1.png')],
2974                      warnings[0].items)
2975     self.assertEqual('error', warnings[1].type)
2976     self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[1].message)
2977     self.assertEqual([os.path.join('test_grd', 'IDS_TEST1.png.sha1')],
2978                      warnings[1].items)
2979
2980     # CL added two messages (one in grd, one in grdp) and added the png files
2981     # associated with the messages, but did not add the corresponding sha1
2982     # files. This should warn twice:
2983     # - Once for the added png files (because we don't want developers to upload
2984     #   actual images)
2985     # - Once for the missing .sha1 files
2986     input_api = self.makeInputApi([
2987         # Modified files:
2988         MockAffectedFile(
2989             'test.grd',
2990             self.NEW_GRD_CONTENTS1,
2991             self.OLD_GRD_CONTENTS,
2992             action='M'),
2993         MockAffectedFile(
2994             'part.grdp',
2995             self.NEW_GRDP_CONTENTS1,
2996             self.OLD_GRDP_CONTENTS,
2997             action='M'),
2998         # Added files:
2999         MockAffectedFile(
3000             os.path.join('test_grd', 'IDS_TEST1.png'), 'binary', action='A'),
3001         MockAffectedFile(
3002             os.path.join('part_grdp', 'IDS_PART_TEST1.png'), 'binary',
3003             action='A')
3004     ])
3005     warnings = PRESUBMIT.CheckStrings(input_api,
3006                                                       MockOutputApi())
3007     self.assertEqual(2, len(warnings))
3008     self.assertEqual('error', warnings[0].type)
3009     self.assertEqual(self.DO_NOT_UPLOAD_PNG_MESSAGE, warnings[0].message)
3010     self.assertEqual([os.path.join('part_grdp', 'IDS_PART_TEST1.png'),
3011                       os.path.join('test_grd', 'IDS_TEST1.png')],
3012                      warnings[0].items)
3013     self.assertEqual('error', warnings[0].type)
3014     self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[1].message)
3015     self.assertEqual([os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3016                       os.path.join('test_grd', 'IDS_TEST1.png.sha1')],
3017                       warnings[1].items)
3018
3019   def testScreenshotsWithSha1(self):
3020     # CL added four messages (two each in a grd and grdp) and their
3021     # corresponding .sha1 files. No warnings.
3022     input_api = self.makeInputApi([
3023         # Modified files:
3024         MockAffectedFile(
3025             'test.grd',
3026             self.NEW_GRD_CONTENTS2,
3027             self.OLD_GRD_CONTENTS,
3028             action='M'),
3029         MockAffectedFile(
3030             'part.grdp',
3031             self.NEW_GRDP_CONTENTS2,
3032             self.OLD_GRDP_CONTENTS,
3033             action='M'),
3034         # Added files:
3035         MockFile(
3036             os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3037             'binary',
3038             action='A'),
3039         MockFile(
3040             os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3041             'binary',
3042             action='A'),
3043         MockFile(
3044             os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3045             'binary',
3046             action='A'),
3047         MockFile(
3048             os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3049             'binary',
3050             action='A'),
3051     ])
3052     warnings = PRESUBMIT.CheckStrings(input_api,
3053                                                       MockOutputApi())
3054     self.assertEqual([], warnings)
3055
3056   def testScreenshotsRemovedWithSha1(self):
3057     # Replace new contents with old contents in grd and grp files, removing
3058     # IDS_TEST1, IDS_TEST2, IDS_PART_TEST1 and IDS_PART_TEST2.
3059     # Should warn to remove the sha1 files associated with these strings.
3060     input_api = self.makeInputApi([
3061         # Modified files:
3062         MockAffectedFile(
3063             'test.grd',
3064             self.OLD_GRD_CONTENTS, # new_contents
3065             self.NEW_GRD_CONTENTS2, # old_contents
3066             action='M'),
3067         MockAffectedFile(
3068             'part.grdp',
3069             self.OLD_GRDP_CONTENTS, # new_contents
3070             self.NEW_GRDP_CONTENTS2, # old_contents
3071             action='M'),
3072         # Unmodified files:
3073         MockFile(os.path.join('test_grd', 'IDS_TEST1.png.sha1'), 'binary', ''),
3074         MockFile(os.path.join('test_grd', 'IDS_TEST2.png.sha1'), 'binary', ''),
3075         MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3076                  'binary', ''),
3077         MockFile(os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3078                  'binary', '')
3079     ])
3080     warnings = PRESUBMIT.CheckStrings(input_api,
3081                                                       MockOutputApi())
3082     self.assertEqual(1, len(warnings))
3083     self.assertEqual('error', warnings[0].type)
3084     self.assertEqual(self.REMOVE_SIGNATURES_MESSAGE, warnings[0].message)
3085     self.assertEqual([
3086         os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3087         os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3088         os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3089         os.path.join('test_grd', 'IDS_TEST2.png.sha1')
3090     ], warnings[0].items)
3091
3092     # Same as above, but this time one of the .sha1 files is also removed.
3093     input_api = self.makeInputApi([
3094         # Modified files:
3095         MockAffectedFile(
3096             'test.grd',
3097             self.OLD_GRD_CONTENTS, # new_contents
3098             self.NEW_GRD_CONTENTS2, # old_contents
3099             action='M'),
3100         MockAffectedFile(
3101             'part.grdp',
3102             self.OLD_GRDP_CONTENTS, # new_contents
3103             self.NEW_GRDP_CONTENTS2, # old_contents
3104             action='M'),
3105         # Unmodified files:
3106         MockFile(os.path.join('test_grd', 'IDS_TEST1.png.sha1'), 'binary', ''),
3107         MockFile(os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3108                  'binary', ''),
3109         # Deleted files:
3110         MockAffectedFile(
3111             os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3112             '',
3113             'old_contents',
3114             action='D'),
3115         MockAffectedFile(
3116             os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3117             '',
3118             'old_contents',
3119             action='D')
3120     ])
3121     warnings = PRESUBMIT.CheckStrings(input_api,
3122                                                       MockOutputApi())
3123     self.assertEqual(1, len(warnings))
3124     self.assertEqual('error', warnings[0].type)
3125     self.assertEqual(self.REMOVE_SIGNATURES_MESSAGE, warnings[0].message)
3126     self.assertEqual([os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3127                       os.path.join('test_grd', 'IDS_TEST1.png.sha1')
3128                      ], warnings[0].items)
3129
3130     # Remove all sha1 files. There should be no warnings.
3131     input_api = self.makeInputApi([
3132         # Modified files:
3133         MockAffectedFile(
3134             'test.grd',
3135             self.OLD_GRD_CONTENTS,
3136             self.NEW_GRD_CONTENTS2,
3137             action='M'),
3138         MockAffectedFile(
3139             'part.grdp',
3140             self.OLD_GRDP_CONTENTS,
3141             self.NEW_GRDP_CONTENTS2,
3142             action='M'),
3143         # Deleted files:
3144         MockFile(
3145             os.path.join('test_grd', 'IDS_TEST1.png.sha1'),
3146             'binary',
3147             action='D'),
3148         MockFile(
3149             os.path.join('test_grd', 'IDS_TEST2.png.sha1'),
3150             'binary',
3151             action='D'),
3152         MockFile(
3153             os.path.join('part_grdp', 'IDS_PART_TEST1.png.sha1'),
3154             'binary',
3155             action='D'),
3156         MockFile(
3157             os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'),
3158             'binary',
3159             action='D')
3160     ])
3161     warnings = PRESUBMIT.CheckStrings(input_api,
3162                                                       MockOutputApi())
3163     self.assertEqual([], warnings)
3164
3165   def testIcuSyntax(self):
3166     # Add valid ICU syntax string. Should not raise an error.
3167     input_api = self.makeInputApi([
3168       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK2,
3169                        self.NEW_GRD_CONTENTS1, action='M'),
3170       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2,
3171                        self.NEW_GRDP_CONTENTS1, action='M')])
3172     results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3173     # We expect no ICU syntax errors.
3174     icu_errors = [e for e in results
3175         if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3176     self.assertEqual(0, len(icu_errors))
3177
3178     # Valid changes in ICU syntax. Should not raise an error.
3179     input_api = self.makeInputApi([
3180       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK2,
3181                        self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK1, action='M'),
3182       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2,
3183                        self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1, action='M')])
3184     results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3185     # We expect no ICU syntax errors.
3186     icu_errors = [e for e in results
3187         if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3188     self.assertEqual(0, len(icu_errors))
3189
3190     # Add invalid ICU syntax strings. Should raise two errors.
3191     input_api = self.makeInputApi([
3192       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR,
3193                        self.NEW_GRD_CONTENTS1, action='M'),
3194       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR,
3195                        self.NEW_GRD_CONTENTS1, action='M')])
3196     results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3197     # We expect 2 ICU syntax errors.
3198     icu_errors = [e for e in results
3199         if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3200     self.assertEqual(1, len(icu_errors))
3201     self.assertEqual([
3202         'IDS_TEST1: This message looks like an ICU plural, but does not follow '
3203         'ICU syntax.',
3204         'IDS_PART_TEST1: Variant "= 1" is not valid for plural message'
3205       ], icu_errors[0].items)
3206
3207     # Change two strings to have ICU syntax errors. Should raise two errors.
3208     input_api = self.makeInputApi([
3209       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR,
3210                        self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK1, action='M'),
3211       MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR,
3212                        self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1, action='M')])
3213     results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
3214     # We expect 2 ICU syntax errors.
3215     icu_errors = [e for e in results
3216         if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
3217     self.assertEqual(1, len(icu_errors))
3218     self.assertEqual([
3219         'IDS_TEST1: This message looks like an ICU plural, but does not follow '
3220         'ICU syntax.',
3221         'IDS_PART_TEST1: Variant "= 1" is not valid for plural message'
3222       ], icu_errors[0].items)
3223
3224
3225 class TranslationExpectationsTest(unittest.TestCase):
3226   ERROR_MESSAGE_FORMAT = (
3227     "Failed to get a list of translatable grd files. "
3228     "This happens when:\n"
3229     " - One of the modified grd or grdp files cannot be parsed or\n"
3230     " - %s is not updated.\n"
3231     "Stack:\n"
3232   )
3233   REPO_ROOT = os.path.join('tools', 'translation', 'testdata')
3234   # This lists all .grd files under REPO_ROOT.
3235   EXPECTATIONS = os.path.join(REPO_ROOT,
3236                               "translation_expectations.pyl")
3237   # This lists all .grd files under REPO_ROOT except unlisted.grd.
3238   EXPECTATIONS_WITHOUT_UNLISTED_FILE = os.path.join(
3239       REPO_ROOT, "translation_expectations_without_unlisted_file.pyl")
3240
3241   # Tests that the presubmit doesn't return when no grd or grdp files are
3242   # modified.
3243   def testExpectationsNoModifiedGrd(self):
3244     input_api = MockInputApi()
3245     input_api.files = [
3246         MockAffectedFile('not_used.txt', 'not used', 'not used', action='M')
3247     ]
3248     # Fake list of all grd files in the repo. This list is missing all grd/grdps
3249     # under tools/translation/testdata. This is OK because the presubmit won't
3250     # run in the first place since there are no modified grd/grps in input_api.
3251     grd_files = ['doesnt_exist_doesnt_matter.grd']
3252     warnings = PRESUBMIT.CheckTranslationExpectations(
3253         input_api, MockOutputApi(), self.REPO_ROOT, self.EXPECTATIONS,
3254         grd_files)
3255     self.assertEqual(0, len(warnings))
3256
3257
3258   # Tests that the list of files passed to the presubmit matches the list of
3259   # files in the expectations.
3260   def testExpectationsSuccess(self):
3261     # Mock input file list needs a grd or grdp file in order to run the
3262     # presubmit. The file itself doesn't matter.
3263     input_api = MockInputApi()
3264     input_api.files = [
3265         MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3266     ]
3267     # List of all grd files in the repo.
3268     grd_files = ['test.grd', 'unlisted.grd', 'not_translated.grd',
3269                  'internal.grd']
3270     warnings = PRESUBMIT.CheckTranslationExpectations(
3271         input_api, MockOutputApi(), self.REPO_ROOT, self.EXPECTATIONS,
3272         grd_files)
3273     self.assertEqual(0, len(warnings))
3274
3275   # Tests that the presubmit warns when a file is listed in expectations, but
3276   # does not actually exist.
3277   def testExpectationsMissingFile(self):
3278     # Mock input file list needs a grd or grdp file in order to run the
3279     # presubmit.
3280     input_api = MockInputApi()
3281     input_api.files = [
3282       MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3283     ]
3284     # unlisted.grd is listed under tools/translation/testdata but is not
3285     # included in translation expectations.
3286     grd_files = ['unlisted.grd', 'not_translated.grd', 'internal.grd']
3287     warnings = PRESUBMIT.CheckTranslationExpectations(
3288         input_api, MockOutputApi(), self.REPO_ROOT, self.EXPECTATIONS,
3289         grd_files)
3290     self.assertEqual(1, len(warnings))
3291     self.assertTrue(warnings[0].message.startswith(
3292         self.ERROR_MESSAGE_FORMAT % self.EXPECTATIONS))
3293     self.assertTrue(
3294         ("test.grd is listed in the translation expectations, "
3295          "but this grd file does not exist")
3296         in warnings[0].message)
3297
3298   # Tests that the presubmit warns when a file is not listed in expectations but
3299   # does actually exist.
3300   def testExpectationsUnlistedFile(self):
3301     # Mock input file list needs a grd or grdp file in order to run the
3302     # presubmit.
3303     input_api = MockInputApi()
3304     input_api.files = [
3305       MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3306     ]
3307     # unlisted.grd is listed under tools/translation/testdata but is not
3308     # included in translation expectations.
3309     grd_files = ['test.grd', 'unlisted.grd', 'not_translated.grd',
3310                  'internal.grd']
3311     warnings = PRESUBMIT.CheckTranslationExpectations(
3312         input_api, MockOutputApi(), self.REPO_ROOT,
3313         self.EXPECTATIONS_WITHOUT_UNLISTED_FILE, grd_files)
3314     self.assertEqual(1, len(warnings))
3315     self.assertTrue(warnings[0].message.startswith(
3316         self.ERROR_MESSAGE_FORMAT % self.EXPECTATIONS_WITHOUT_UNLISTED_FILE))
3317     self.assertTrue(
3318         ("unlisted.grd appears to be translatable "
3319          "(because it contains <file> or <message> elements), "
3320          "but is not listed in the translation expectations.")
3321         in warnings[0].message)
3322
3323   # Tests that the presubmit warns twice:
3324   # - for a non-existing file listed in expectations
3325   # - for an existing file not listed in expectations
3326   def testMultipleWarnings(self):
3327     # Mock input file list needs a grd or grdp file in order to run the
3328     # presubmit.
3329     input_api = MockInputApi()
3330     input_api.files = [
3331       MockAffectedFile('dummy.grd', 'not used', 'not used', action='M')
3332     ]
3333     # unlisted.grd is listed under tools/translation/testdata but is not
3334     # included in translation expectations.
3335     # test.grd is not listed under tools/translation/testdata but is included
3336     # in translation expectations.
3337     grd_files = ['unlisted.grd', 'not_translated.grd', 'internal.grd']
3338     warnings = PRESUBMIT.CheckTranslationExpectations(
3339         input_api, MockOutputApi(), self.REPO_ROOT,
3340         self.EXPECTATIONS_WITHOUT_UNLISTED_FILE, grd_files)
3341     self.assertEqual(1, len(warnings))
3342     self.assertTrue(warnings[0].message.startswith(
3343         self.ERROR_MESSAGE_FORMAT % self.EXPECTATIONS_WITHOUT_UNLISTED_FILE))
3344     self.assertTrue(
3345         ("unlisted.grd appears to be translatable "
3346          "(because it contains <file> or <message> elements), "
3347          "but is not listed in the translation expectations.")
3348         in warnings[0].message)
3349     self.assertTrue(
3350         ("test.grd is listed in the translation expectations, "
3351          "but this grd file does not exist")
3352         in warnings[0].message)
3353
3354
3355 class DISABLETypoInTest(unittest.TestCase):
3356
3357   def testPositive(self):
3358     # Verify the typo "DISABLE_" instead of "DISABLED_" in various contexts
3359     # where the desire is to disable a test.
3360     tests = [
3361         # Disabled on one platform:
3362         '#if defined(OS_WIN)\n'
3363         '#define MAYBE_FoobarTest DISABLE_FoobarTest\n'
3364         '#else\n'
3365         '#define MAYBE_FoobarTest FoobarTest\n'
3366         '#endif\n',
3367         # Disabled on one platform spread cross lines:
3368         '#if defined(OS_WIN)\n'
3369         '#define MAYBE_FoobarTest \\\n'
3370         '    DISABLE_FoobarTest\n'
3371         '#else\n'
3372         '#define MAYBE_FoobarTest FoobarTest\n'
3373         '#endif\n',
3374         # Disabled on all platforms:
3375         '  TEST_F(FoobarTest, DISABLE_Foo)\n{\n}',
3376         # Disabled on all platforms but multiple lines
3377         '  TEST_F(FoobarTest,\n   DISABLE_foo){\n}\n',
3378     ]
3379
3380     for test in tests:
3381       mock_input_api = MockInputApi()
3382       mock_input_api.files = [
3383           MockFile('some/path/foo_unittest.cc', test.splitlines()),
3384       ]
3385
3386       results = PRESUBMIT.CheckNoDISABLETypoInTests(mock_input_api,
3387                                                      MockOutputApi())
3388       self.assertEqual(
3389           1,
3390           len(results),
3391           msg=('expected len(results) == 1 but got %d in test: %s' %
3392                (len(results), test)))
3393       self.assertTrue(
3394           'foo_unittest.cc' in results[0].message,
3395           msg=('expected foo_unittest.cc in message but got %s in test %s' %
3396                (results[0].message, test)))
3397
3398   def testIngoreNotTestFiles(self):
3399     mock_input_api = MockInputApi()
3400     mock_input_api.files = [
3401         MockFile('some/path/foo.cc', 'TEST_F(FoobarTest, DISABLE_Foo)'),
3402     ]
3403
3404     results = PRESUBMIT.CheckNoDISABLETypoInTests(mock_input_api,
3405                                                    MockOutputApi())
3406     self.assertEqual(0, len(results))
3407
3408   def testIngoreDeletedFiles(self):
3409     mock_input_api = MockInputApi()
3410     mock_input_api.files = [
3411         MockFile('some/path/foo.cc', 'TEST_F(FoobarTest, Foo)', action='D'),
3412     ]
3413
3414     results = PRESUBMIT.CheckNoDISABLETypoInTests(mock_input_api,
3415                                                    MockOutputApi())
3416     self.assertEqual(0, len(results))
3417
3418
3419 class CheckFuzzTargetsTest(unittest.TestCase):
3420
3421   def _check(self, files):
3422     mock_input_api = MockInputApi()
3423     mock_input_api.files = []
3424     for fname, contents in files.items():
3425       mock_input_api.files.append(MockFile(fname, contents.splitlines()))
3426     return PRESUBMIT.CheckFuzzTargetsOnUpload(mock_input_api, MockOutputApi())
3427
3428   def testLibFuzzerSourcesIgnored(self):
3429     results = self._check({
3430         "third_party/lib/Fuzzer/FuzzerDriver.cpp": "LLVMFuzzerInitialize",
3431     })
3432     self.assertEqual(results, [])
3433
3434   def testNonCodeFilesIgnored(self):
3435     results = self._check({
3436         "README.md": "LLVMFuzzerInitialize",
3437     })
3438     self.assertEqual(results, [])
3439
3440   def testNoErrorHeaderPresent(self):
3441     results = self._check({
3442         "fuzzer.cc": (
3443             "#include \"testing/libfuzzer/libfuzzer_exports.h\"\n" +
3444             "LLVMFuzzerInitialize"
3445         )
3446     })
3447     self.assertEqual(results, [])
3448
3449   def testErrorMissingHeader(self):
3450     results = self._check({
3451         "fuzzer.cc": "LLVMFuzzerInitialize"
3452     })
3453     self.assertEqual(len(results), 1)
3454     self.assertEqual(results[0].items, ['fuzzer.cc'])
3455
3456
3457 class SetNoParentTest(unittest.TestCase):
3458   def testSetNoParentTopLevelAllowed(self):
3459     mock_input_api = MockInputApi()
3460     mock_input_api.files = [
3461       MockAffectedFile('goat/OWNERS',
3462                        [
3463                          'set noparent',
3464                          'jochen@chromium.org',
3465                        ])
3466     ]
3467     mock_output_api = MockOutputApi()
3468     errors = PRESUBMIT.CheckSetNoParent(mock_input_api, mock_output_api)
3469     self.assertEqual([], errors)
3470
3471   def testSetNoParentMissing(self):
3472     mock_input_api = MockInputApi()
3473     mock_input_api.files = [
3474       MockAffectedFile('services/goat/OWNERS',
3475                        [
3476                          'set noparent',
3477                          'jochen@chromium.org',
3478                          'per-file *.json=set noparent',
3479                          'per-file *.json=jochen@chromium.org',
3480                        ])
3481     ]
3482     mock_output_api = MockOutputApi()
3483     errors = PRESUBMIT.CheckSetNoParent(mock_input_api, mock_output_api)
3484     self.assertEqual(1, len(errors))
3485     self.assertTrue('goat/OWNERS:1' in errors[0].long_text)
3486     self.assertTrue('goat/OWNERS:3' in errors[0].long_text)
3487
3488   def testSetNoParentWithCorrectRule(self):
3489     mock_input_api = MockInputApi()
3490     mock_input_api.files = [
3491       MockAffectedFile('services/goat/OWNERS',
3492                        [
3493                          'set noparent',
3494                          'file://ipc/SECURITY_OWNERS',
3495                          'per-file *.json=set noparent',
3496                          'per-file *.json=file://ipc/SECURITY_OWNERS',
3497                        ])
3498     ]
3499     mock_output_api = MockOutputApi()
3500     errors = PRESUBMIT.CheckSetNoParent(mock_input_api, mock_output_api)
3501     self.assertEqual([], errors)
3502
3503
3504 class MojomStabilityCheckTest(unittest.TestCase):
3505   def runTestWithAffectedFiles(self, affected_files):
3506     mock_input_api = MockInputApi()
3507     mock_input_api.files = affected_files
3508     mock_output_api = MockOutputApi()
3509     return PRESUBMIT.CheckStableMojomChanges(
3510         mock_input_api, mock_output_api)
3511
3512   def testSafeChangePasses(self):
3513     errors = self.runTestWithAffectedFiles([
3514       MockAffectedFile('foo/foo.mojom',
3515                        ['[Stable] struct S { [MinVersion=1] int32 x; };'],
3516                        old_contents=['[Stable] struct S {};'])
3517     ])
3518     self.assertEqual([], errors)
3519
3520   def testBadChangeFails(self):
3521     errors = self.runTestWithAffectedFiles([
3522       MockAffectedFile('foo/foo.mojom',
3523                        ['[Stable] struct S { int32 x; };'],
3524                        old_contents=['[Stable] struct S {};'])
3525     ])
3526     self.assertEqual(1, len(errors))
3527     self.assertTrue('not backward-compatible' in errors[0].message)
3528
3529   def testDeletedFile(self):
3530     """Regression test for https://crbug.com/1091407."""
3531     errors = self.runTestWithAffectedFiles([
3532       MockAffectedFile('a.mojom', [], old_contents=['struct S {};'],
3533                        action='D'),
3534       MockAffectedFile('b.mojom',
3535                        ['struct S {}; struct T { S s; };'],
3536                        old_contents=['import "a.mojom"; struct T { S s; };'])
3537     ])
3538     self.assertEqual([], errors)
3539
3540 class CheckForUseOfChromeAppsDeprecationsTest(unittest.TestCase):
3541
3542   ERROR_MSG_PIECE = 'technologies which will soon be deprecated'
3543
3544   # Each positive test is also a naive negative test for the other cases.
3545
3546   def testWarningNMF(self):
3547     mock_input_api = MockInputApi()
3548     mock_input_api.files = [
3549         MockAffectedFile(
3550             'foo.NMF',
3551             ['"program"', '"Z":"content"', 'B'],
3552             ['"program"', 'B'],
3553             scm_diff='\n'.join([
3554                 '--- foo.NMF.old  2020-12-02 20:40:54.430676385 +0100',
3555                 '+++ foo.NMF.new  2020-12-02 20:41:02.086700197 +0100',
3556                 '@@ -1,2 +1,3 @@',
3557                 ' "program"',
3558                 '+"Z":"content"',
3559                 ' B']),
3560             action='M')
3561     ]
3562     mock_output_api = MockOutputApi()
3563     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
3564                                                      mock_output_api)
3565     self.assertEqual(1, len(errors))
3566     self.assertTrue( self.ERROR_MSG_PIECE in errors[0].message)
3567     self.assertTrue( 'foo.NMF' in errors[0].message)
3568
3569   def testWarningManifest(self):
3570     mock_input_api = MockInputApi()
3571     mock_input_api.files = [
3572         MockAffectedFile(
3573             'manifest.json',
3574             ['"app":', '"Z":"content"', 'B'],
3575             ['"app":"', 'B'],
3576             scm_diff='\n'.join([
3577                 '--- manifest.json.old  2020-12-02 20:40:54.430676385 +0100',
3578                 '+++ manifest.json.new  2020-12-02 20:41:02.086700197 +0100',
3579                 '@@ -1,2 +1,3 @@',
3580                 ' "app"',
3581                 '+"Z":"content"',
3582                 ' B']),
3583             action='M')
3584     ]
3585     mock_output_api = MockOutputApi()
3586     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
3587                                                      mock_output_api)
3588     self.assertEqual(1, len(errors))
3589     self.assertTrue( self.ERROR_MSG_PIECE in errors[0].message)
3590     self.assertTrue( 'manifest.json' in errors[0].message)
3591
3592   def testOKWarningManifestWithoutApp(self):
3593     mock_input_api = MockInputApi()
3594     mock_input_api.files = [
3595         MockAffectedFile(
3596             'manifest.json',
3597             ['"name":', '"Z":"content"', 'B'],
3598             ['"name":"', 'B'],
3599             scm_diff='\n'.join([
3600                 '--- manifest.json.old  2020-12-02 20:40:54.430676385 +0100',
3601                 '+++ manifest.json.new  2020-12-02 20:41:02.086700197 +0100',
3602                 '@@ -1,2 +1,3 @@',
3603                 ' "app"',
3604                 '+"Z":"content"',
3605                 ' B']),
3606             action='M')
3607     ]
3608     mock_output_api = MockOutputApi()
3609     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
3610                                                      mock_output_api)
3611     self.assertEqual(0, len(errors))
3612
3613   def testWarningPPAPI(self):
3614     mock_input_api = MockInputApi()
3615     mock_input_api.files = [
3616         MockAffectedFile(
3617             'foo.hpp',
3618             ['A', '#include <ppapi.h>', 'B'],
3619             ['A', 'B'],
3620             scm_diff='\n'.join([
3621                 '--- foo.hpp.old  2020-12-02 20:40:54.430676385 +0100',
3622                 '+++ foo.hpp.new  2020-12-02 20:41:02.086700197 +0100',
3623                 '@@ -1,2 +1,3 @@',
3624                 ' A',
3625                 '+#include <ppapi.h>',
3626                 ' B']),
3627             action='M')
3628     ]
3629     mock_output_api = MockOutputApi()
3630     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
3631                                                      mock_output_api)
3632     self.assertEqual(1, len(errors))
3633     self.assertTrue( self.ERROR_MSG_PIECE in errors[0].message)
3634     self.assertTrue( 'foo.hpp' in errors[0].message)
3635
3636   def testNoWarningPPAPI(self):
3637     mock_input_api = MockInputApi()
3638     mock_input_api.files = [
3639         MockAffectedFile(
3640             'foo.txt',
3641             ['A', 'Peppapig', 'B'],
3642             ['A', 'B'],
3643             scm_diff='\n'.join([
3644                 '--- foo.txt.old  2020-12-02 20:40:54.430676385 +0100',
3645                 '+++ foo.txt.new  2020-12-02 20:41:02.086700197 +0100',
3646                 '@@ -1,2 +1,3 @@',
3647                 ' A',
3648                 '+Peppapig',
3649                 ' B']),
3650             action='M')
3651     ]
3652     mock_output_api = MockOutputApi()
3653     errors = PRESUBMIT.CheckForUseOfChromeAppsDeprecations(mock_input_api,
3654                                                      mock_output_api)
3655     self.assertEqual(0, len(errors))
3656
3657 class CheckDeprecationOfPreferencesTest(unittest.TestCase):
3658   # Test that a warning is generated if a preference registration is removed
3659   # from a random file.
3660   def testWarning(self):
3661     mock_input_api = MockInputApi()
3662     mock_input_api.files = [
3663         MockAffectedFile(
3664             'foo.cc',
3665             ['A', 'B'],
3666             ['A', 'prefs->RegisterStringPref("foo", "default");', 'B'],
3667             scm_diff='\n'.join([
3668                 '--- foo.cc.old  2020-12-02 20:40:54.430676385 +0100',
3669                 '+++ foo.cc.new  2020-12-02 20:41:02.086700197 +0100',
3670                 '@@ -1,3 +1,2 @@',
3671                 ' A',
3672                 '-prefs->RegisterStringPref("foo", "default");',
3673                 ' B']),
3674             action='M')
3675     ]
3676     mock_output_api = MockOutputApi()
3677     errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
3678                                                      mock_output_api)
3679     self.assertEqual(1, len(errors))
3680     self.assertTrue(
3681         'Discovered possible removal of preference registrations' in
3682         errors[0].message)
3683
3684   # Test that a warning is inhibited if the preference registration was moved
3685   # to the deprecation functions in browser prefs.
3686   def testNoWarningForMigration(self):
3687     mock_input_api = MockInputApi()
3688     mock_input_api.files = [
3689         # RegisterStringPref was removed from foo.cc.
3690         MockAffectedFile(
3691             'foo.cc',
3692             ['A', 'B'],
3693             ['A', 'prefs->RegisterStringPref("foo", "default");', 'B'],
3694             scm_diff='\n'.join([
3695                 '--- foo.cc.old  2020-12-02 20:40:54.430676385 +0100',
3696                 '+++ foo.cc.new  2020-12-02 20:41:02.086700197 +0100',
3697                 '@@ -1,3 +1,2 @@',
3698                 ' A',
3699                 '-prefs->RegisterStringPref("foo", "default");',
3700                 ' B']),
3701             action='M'),
3702         # But the preference was properly migrated.
3703         MockAffectedFile(
3704             'chrome/browser/prefs/browser_prefs.cc',
3705             [
3706                  '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3707                  '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3708                  '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
3709                  'prefs->RegisterStringPref("foo", "default");',
3710                  '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
3711             ],
3712             [
3713                  '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3714                  '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3715                  '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
3716                  '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
3717             ],
3718             scm_diff='\n'.join([
3719                  '--- browser_prefs.cc.old 2020-12-02 20:51:40.812686731 +0100',
3720                  '+++ browser_prefs.cc.new 2020-12-02 20:52:02.936755539 +0100',
3721                  '@@ -2,3 +2,4 @@',
3722                  ' // END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3723                  ' // BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
3724                  '+prefs->RegisterStringPref("foo", "default");',
3725                  ' // END_MIGRATE_OBSOLETE_PROFILE_PREFS']),
3726             action='M'),
3727     ]
3728     mock_output_api = MockOutputApi()
3729     errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
3730                                                      mock_output_api)
3731     self.assertEqual(0, len(errors))
3732
3733   # Test that a warning is NOT inhibited if the preference registration was
3734   # moved to a place outside of the migration functions in browser_prefs.cc
3735   def testWarningForImproperMigration(self):
3736     mock_input_api = MockInputApi()
3737     mock_input_api.files = [
3738         # RegisterStringPref was removed from foo.cc.
3739         MockAffectedFile(
3740             'foo.cc',
3741             ['A', 'B'],
3742             ['A', 'prefs->RegisterStringPref("foo", "default");', 'B'],
3743             scm_diff='\n'.join([
3744                 '--- foo.cc.old  2020-12-02 20:40:54.430676385 +0100',
3745                 '+++ foo.cc.new  2020-12-02 20:41:02.086700197 +0100',
3746                 '@@ -1,3 +1,2 @@',
3747                 ' A',
3748                 '-prefs->RegisterStringPref("foo", "default");',
3749                 ' B']),
3750             action='M'),
3751         # The registration call was moved to a place in browser_prefs.cc that
3752         # is outside the migration functions.
3753         MockAffectedFile(
3754             'chrome/browser/prefs/browser_prefs.cc',
3755             [
3756                  'prefs->RegisterStringPref("foo", "default");',
3757                  '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3758                  '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3759                  '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
3760                  '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
3761             ],
3762             [
3763                  '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3764                  '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3765                  '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
3766                  '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
3767             ],
3768             scm_diff='\n'.join([
3769                  '--- browser_prefs.cc.old 2020-12-02 20:51:40.812686731 +0100',
3770                  '+++ browser_prefs.cc.new 2020-12-02 20:52:02.936755539 +0100',
3771                  '@@ -1,2 +1,3 @@',
3772                  '+prefs->RegisterStringPref("foo", "default");',
3773                  ' // BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3774                  ' // END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS']),
3775             action='M'),
3776     ]
3777     mock_output_api = MockOutputApi()
3778     errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
3779                                                      mock_output_api)
3780     self.assertEqual(1, len(errors))
3781     self.assertTrue(
3782         'Discovered possible removal of preference registrations' in
3783         errors[0].message)
3784
3785   # Check that the presubmit fails if a marker line in brower_prefs.cc is
3786   # deleted.
3787   def testDeletedMarkerRaisesError(self):
3788     mock_input_api = MockInputApi()
3789     mock_input_api.files = [
3790         MockAffectedFile('chrome/browser/prefs/browser_prefs.cc',
3791                          [
3792                            '// BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3793                            '// END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS',
3794                            '// BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS',
3795                            # The following line is deleted for this test
3796                            # '// END_MIGRATE_OBSOLETE_PROFILE_PREFS',
3797                          ])
3798     ]
3799     mock_output_api = MockOutputApi()
3800     errors = PRESUBMIT.CheckDeprecationOfPreferences(mock_input_api,
3801                                                      mock_output_api)
3802     self.assertEqual(1, len(errors))
3803     self.assertEqual(
3804         'Broken .*MIGRATE_OBSOLETE_.*_PREFS markers in browser_prefs.cc.',
3805         errors[0].message)
3806
3807
3808 if __name__ == '__main__':
3809   unittest.main()