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