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