diff options
Diffstat (limited to 'chromium/PRESUBMIT_test.py')
-rwxr-xr-x | chromium/PRESUBMIT_test.py | 348 |
1 files changed, 313 insertions, 35 deletions
diff --git a/chromium/PRESUBMIT_test.py b/chromium/PRESUBMIT_test.py index a15c15fa029..e2e8971de90 100755 --- a/chromium/PRESUBMIT_test.py +++ b/chromium/PRESUBMIT_test.py @@ -713,10 +713,19 @@ class PydepsNeedsUpdatingTest(unittest.TestCase): class MockSubprocess(object): CalledProcessError = subprocess.CalledProcessError + def _MockParseGclientArgs(self, is_android=True): + return lambda: {'checkout_android': 'true' if is_android else 'false' } + def setUp(self): - mock_all_pydeps = ['A.pydeps', 'B.pydeps'] + mock_all_pydeps = ['A.pydeps', 'B.pydeps', 'D.pydeps'] self.old_ALL_PYDEPS_FILES = PRESUBMIT._ALL_PYDEPS_FILES PRESUBMIT._ALL_PYDEPS_FILES = mock_all_pydeps + mock_android_pydeps = ['D.pydeps'] + self.old_ANDROID_SPECIFIC_PYDEPS_FILES = ( + PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES) + PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES = mock_android_pydeps + self.old_ParseGclientArgs = PRESUBMIT._ParseGclientArgs + PRESUBMIT._ParseGclientArgs = self._MockParseGclientArgs() self.mock_input_api = MockInputApi() self.mock_output_api = MockOutputApi() self.mock_input_api.subprocess = PydepsNeedsUpdatingTest.MockSubprocess() @@ -724,10 +733,14 @@ class PydepsNeedsUpdatingTest(unittest.TestCase): self.checker._file_cache = { 'A.pydeps': '# Generated by:\n# CMD A\nA.py\nC.py\n', 'B.pydeps': '# Generated by:\n# CMD B\nB.py\nC.py\n', + 'D.pydeps': '# Generated by:\n# CMD D\nD.py\n', } def tearDown(self): PRESUBMIT._ALL_PYDEPS_FILES = self.old_ALL_PYDEPS_FILES + PRESUBMIT._ANDROID_SPECIFIC_PYDEPS_FILES = ( + self.old_ANDROID_SPECIFIC_PYDEPS_FILES) + PRESUBMIT._ParseGclientArgs = self.old_ParseGclientArgs def _RunCheck(self): return PRESUBMIT._CheckPydepsNeedsUpdating(self.mock_input_api, @@ -735,7 +748,7 @@ class PydepsNeedsUpdatingTest(unittest.TestCase): checker_for_tests=self.checker) def testAddedPydep(self): - # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Android. + # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Linux. if self.mock_input_api.platform != 'linux2': return [] @@ -759,7 +772,7 @@ class PydepsNeedsUpdatingTest(unittest.TestCase): self.assertEqual(0, len(results)) def testRemovedPydep(self): - # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Android. + # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Linux. if self.mock_input_api.platform != 'linux2': return [] @@ -774,7 +787,7 @@ class PydepsNeedsUpdatingTest(unittest.TestCase): self.assertTrue('PYDEPS_FILES' in str(results[0])) def testRandomPyIgnored(self): - # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Android. + # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Linux. if self.mock_input_api.platform != 'linux2': return [] @@ -786,7 +799,7 @@ class PydepsNeedsUpdatingTest(unittest.TestCase): self.assertEqual(0, len(results), 'Unexpected results: %r' % results) def testRelevantPyNoChange(self): - # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Android. + # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Linux. if self.mock_input_api.platform != 'linux2': return [] @@ -804,7 +817,7 @@ class PydepsNeedsUpdatingTest(unittest.TestCase): self.assertEqual(0, len(results), 'Unexpected results: %r' % results) def testRelevantPyOneChange(self): - # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Android. + # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Linux. if self.mock_input_api.platform != 'linux2': return [] @@ -823,7 +836,7 @@ class PydepsNeedsUpdatingTest(unittest.TestCase): self.assertTrue('File is stale' in str(results[0])) def testRelevantPyTwoChanges(self): - # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Android. + # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Linux. if self.mock_input_api.platform != 'linux2': return [] @@ -841,6 +854,27 @@ class PydepsNeedsUpdatingTest(unittest.TestCase): self.assertTrue('File is stale' in str(results[0])) self.assertTrue('File is stale' in str(results[1])) + def testRelevantAndroidPyInNonAndroidCheckout(self): + # PRESUBMIT._CheckPydepsNeedsUpdating is only implemented for Linux. + if self.mock_input_api.platform != 'linux2': + return [] + + self.mock_input_api.files = [ + MockAffectedFile('D.py', []), + ] + + def mock_check_output(cmd, shell=False, env=None): + self.assertEqual('CMD D --output ""', cmd) + return 'changed data' + + self.mock_input_api.subprocess.check_output = mock_check_output + PRESUBMIT._ParseGclientArgs = self._MockParseGclientArgs(is_android=False) + + results = self._RunCheck() + self.assertEqual(1, len(results)) + self.assertTrue('Android' in str(results[0])) + self.assertTrue('D.pydeps' in str(results[0])) + class IncludeGuardTest(unittest.TestCase): def testIncludeGuardChecks(self): @@ -1028,6 +1062,7 @@ class AccessibilityRelnotesFieldTest(unittest.TestCase): mock_output_api = MockOutputApi() mock_input_api.files = [MockAffectedFile('ui/accessibility/foo.bar', [''])] + mock_input_api.change.DescriptionText = lambda : 'Commit description' mock_input_api.change.footers['AX-Relnotes'] = [ 'Important user facing change'] @@ -1046,6 +1081,7 @@ class AccessibilityRelnotesFieldTest(unittest.TestCase): MockAffectedFile('ui/accessibility/foo.bar', ['']), MockAffectedFile('some/other/file', ['']) ] + mock_input_api.change.DescriptionText = lambda : 'Commit description' msgs = PRESUBMIT._CheckAccessibilityRelnotesField( mock_input_api, mock_output_api) @@ -1065,6 +1101,7 @@ class AccessibilityRelnotesFieldTest(unittest.TestCase): MockAffectedFile('some/file', ['']), MockAffectedFile('some/other/file', ['']) ] + mock_input_api.change.DescriptionText = lambda : 'Commit description' msgs = PRESUBMIT._CheckAccessibilityRelnotesField( mock_input_api, mock_output_api) @@ -1097,6 +1134,7 @@ class AccessibilityRelnotesFieldTest(unittest.TestCase): mock_input_api.files = [ MockAffectedFile(testFile, ['']) ] + mock_input_api.change.DescriptionText = lambda : 'Commit description' msgs = PRESUBMIT._CheckAccessibilityRelnotesField( mock_input_api, mock_output_api) @@ -1107,6 +1145,58 @@ class AccessibilityRelnotesFieldTest(unittest.TestCase): ('Missing AX-Relnotes field message not found in errors ' ' for file %s' % (testFile))) + # Test that AX-Relnotes field can appear in the commit description (as long + # as it appears at the beginning of a line). + def testRelnotesInCommitDescription(self): + mock_input_api = MockInputApi() + mock_output_api = MockOutputApi() + + mock_input_api.files = [ + MockAffectedFile('ui/accessibility/foo.bar', ['']), + ] + mock_input_api.change.DescriptionText = lambda : ('Description:\n' + + 'AX-Relnotes: solves all accessibility issues forever') + + msgs = PRESUBMIT._CheckAccessibilityRelnotesField( + mock_input_api, mock_output_api) + self.assertEqual(0, len(msgs), + 'Expected %d messages, found %d: %s' + % (0, len(msgs), msgs)) + + # Test that we don't match AX-Relnotes if it appears in the middle of a line. + def testRelnotesMustAppearAtBeginningOfLine(self): + mock_input_api = MockInputApi() + mock_output_api = MockOutputApi() + + mock_input_api.files = [ + MockAffectedFile('ui/accessibility/foo.bar', ['']), + ] + mock_input_api.change.DescriptionText = lambda : ('Description:\n' + + 'This change has no AX-Relnotes: we should print a warning') + + msgs = PRESUBMIT._CheckAccessibilityRelnotesField( + mock_input_api, mock_output_api) + self.assertTrue("Missing 'AX-Relnotes:' field" in msgs[0].message, + 'Missing AX-Relnotes field message not found in errors') + + # Tests that the AX-Relnotes field can be lowercase and use a '=' in place + # of a ':'. + def testRelnotesLowercaseWithEqualSign(self): + mock_input_api = MockInputApi() + mock_output_api = MockOutputApi() + + mock_input_api.files = [ + MockAffectedFile('ui/accessibility/foo.bar', ['']), + ] + mock_input_api.change.DescriptionText = lambda : ('Description:\n' + + 'ax-relnotes= this is a valid format for accessibiliy relnotes') + + msgs = PRESUBMIT._CheckAccessibilityRelnotesField( + mock_input_api, mock_output_api) + self.assertEqual(0, len(msgs), + 'Expected %d messages, found %d: %s' + % (0, len(msgs), msgs)) + class AndroidDeprecatedTestAnnotationTest(unittest.TestCase): def testCheckAndroidTestAnnotationUsage(self): mock_input_api = MockInputApi() @@ -2105,17 +2195,21 @@ class SecurityChangeTest(unittest.TestCase): input_api.canned_checks.GetCodereviewOwnerAndReviewers = \ __MockOwnerAndReviewers - def testDiffWithSandboxType(self): + def testDiffGetServiceSandboxType(self): mock_input_api = MockInputApi() mock_input_api.files = [ MockAffectedFile( 'services/goat/teleporter_host.cc', [ - 'content::ServiceProcessHost::Launch<mojom::GoatTeleporter>(', - ' content::ServiceProcessHost::LaunchOptions()', - ' .WithSandboxType(content::SandboxType::kGoaty)', - ' .WithDisplayName("goat_teleporter")', - ' .Build())' + 'template <>', + 'inline content::SandboxType', + 'content::GetServiceSandboxType<chrome::mojom::GoatTeleporter>() {', + '#if defined(OS_WIN)', + ' return SandboxType::kGoaty;', + '#else', + ' return SandboxType::kNoSandbox;', + '#endif // !defined(OS_WIN)', + '}' ] ), ] @@ -2123,7 +2217,7 @@ class SecurityChangeTest(unittest.TestCase): mock_input_api) self.assertEqual({ 'services/goat/teleporter_host.cc': set([ - 'content::ServiceProcessHost::LaunchOptions::WithSandboxType' + 'content::GetServiceSandboxType<>()' ])}, files_to_functions) @@ -2133,18 +2227,18 @@ class SecurityChangeTest(unittest.TestCase): mock_file._scm_diff = """--- old 2020-05-04 14:08:25.000000000 -0400 +++ new 2020-05-04 14:08:32.000000000 -0400 @@ -1,5 +1,4 @@ - content::ServiceProcessHost::Launch<mojom::GoatTeleporter>( - content::ServiceProcessHost::LaunchOptions() -- .WithSandboxType(content::SandboxType::kGoaty) - .WithDisplayName("goat_teleporter") - .Build()) + template <> + inline content::SandboxType +-content::GetServiceSandboxType<chrome::mojom::GoatTeleporter>() { + #if defined(OS_WIN) + return SandboxType::kGoaty; """ mock_input_api.files = [mock_file] files_to_functions = PRESUBMIT._GetFilesUsingSecurityCriticalFunctions( mock_input_api) self.assertEqual({ 'services/goat/teleporter_host.cc': set([ - 'content::ServiceProcessHost::LaunchOptions::WithSandboxType' + 'content::GetServiceSandboxType<>()' ])}, files_to_functions) @@ -2153,7 +2247,7 @@ class SecurityChangeTest(unittest.TestCase): mock_input_api.owners_db = self._MockOwnersDB() mock_input_api.is_committing = False mock_input_api.files = [ - MockAffectedFile('file.cc', ['WithSandboxType(Sandbox)']) + MockAffectedFile('file.cc', ['GetServiceSandboxType<Goat>(Sandbox)']) ] mock_output_api = MockOutputApi() self._mockChangeOwnerAndReviewers( @@ -2165,14 +2259,14 @@ class SecurityChangeTest(unittest.TestCase): 'The following files change calls to security-sensive functions\n' \ 'that need to be reviewed by ipc/SECURITY_OWNERS.\n' ' file.cc\n' - ' content::ServiceProcessHost::LaunchOptions::WithSandboxType\n\n') + ' content::GetServiceSandboxType<>()\n\n') def testChangeOwnersMissingAtCommit(self): mock_input_api = MockInputApi() mock_input_api.owners_db = self._MockOwnersDB() mock_input_api.is_committing = True mock_input_api.files = [ - MockAffectedFile('file.cc', ['WithSandboxType(Sandbox)']) + MockAffectedFile('file.cc', ['GetServiceSandboxType<mojom::Goat>()']) ] mock_output_api = MockOutputApi() self._mockChangeOwnerAndReviewers( @@ -2184,7 +2278,7 @@ class SecurityChangeTest(unittest.TestCase): 'The following files change calls to security-sensive functions\n' \ 'that need to be reviewed by ipc/SECURITY_OWNERS.\n' ' file.cc\n' - ' content::ServiceProcessHost::LaunchOptions::WithSandboxType\n\n') + ' content::GetServiceSandboxType<>()\n\n') def testChangeOwnersPresent(self): mock_input_api = MockInputApi() @@ -2203,7 +2297,7 @@ class SecurityChangeTest(unittest.TestCase): mock_input_api = MockInputApi() mock_input_api.owners_db = self._MockOwnersDB() mock_input_api.files = [ - MockAffectedFile('file.cc', ['WithSandboxType(Sandbox)']) + MockAffectedFile('file.cc', ['GetServiceSandboxType<T>(Sandbox)']) ] mock_output_api = MockOutputApi() self._mockChangeOwnerAndReviewers( @@ -2651,7 +2745,9 @@ class CheckNoDirectIncludesHeadersWhichRedefineStrCat(unittest.TestCase): self.assertEquals(0, len(results)) -class TranslationScreenshotsTest(unittest.TestCase): +class StringTest(unittest.TestCase): + """Tests ICU syntax check and translation screenshots check.""" + # An empty grd file. OLD_GRD_CONTENTS = """<?xml version="1.0" encoding="UTF-8"?> <grit latest_public_release="1" current_release="1"> @@ -2668,6 +2764,10 @@ class TranslationScreenshotsTest(unittest.TestCase): <message name="IDS_TEST1"> Test string 1 </message> + <message name="IDS_TEST_STRING_NON_TRANSLATEABLE1" + translateable="false"> + Non translateable message 1, should be ignored + </message> </messages> </release> </grit> @@ -2683,6 +2783,52 @@ class TranslationScreenshotsTest(unittest.TestCase): <message name="IDS_TEST2"> Test string 2 </message> + <message name="IDS_TEST_STRING_NON_TRANSLATEABLE2" + translateable="false"> + Non translateable message 2, should be ignored + </message> + </messages> + </release> + </grit> + """.splitlines() + # A grd file with one ICU syntax message without syntax errors. + NEW_GRD_CONTENTS_ICU_SYNTAX_OK1 = """<?xml version="1.0" encoding="UTF-8"?> + <grit latest_public_release="1" current_release="1"> + <release seq="1"> + <messages> + <message name="IDS_TEST1"> + {NUM, plural, + =1 {Test text for numeric one} + other {Test text for plural with {NUM} as number}} + </message> + </messages> + </release> + </grit> + """.splitlines() + # A grd file with one ICU syntax message without syntax errors. + NEW_GRD_CONTENTS_ICU_SYNTAX_OK2 = """<?xml version="1.0" encoding="UTF-8"?> + <grit latest_public_release="1" current_release="1"> + <release seq="1"> + <messages> + <message name="IDS_TEST1"> + {NUM, plural, + =1 {Different test text for numeric one} + other {Different test text for plural with {NUM} as number}} + </message> + </messages> + </release> + </grit> + """.splitlines() + # A grd file with one ICU syntax message with syntax errors (misses a comma). + NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR = """<?xml version="1.0" encoding="UTF-8"?> + <grit latest_public_release="1" current_release="1"> + <release seq="1"> + <messages> + <message name="IDS_TEST1"> + {NUM, plural + =1 {Test text for numeric one} + other {Test text for plural with {NUM} as number}} + </message> </messages> </release> </grit> @@ -2713,6 +2859,39 @@ class TranslationScreenshotsTest(unittest.TestCase): '</message>', '</grit-part>') + # A grdp file with one ICU syntax message without syntax errors. + NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1 = ( + '<?xml version="1.0" encoding="utf-8"?>', + '<grit-part>', + '<message name="IDS_PART_TEST1">', + '{NUM, plural,', + '=1 {Test text for numeric one}', + 'other {Test text for plural with {NUM} as number}}', + '</message>', + '</grit-part>') + # A grdp file with one ICU syntax message without syntax errors. + NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2 = ( + '<?xml version="1.0" encoding="utf-8"?>', + '<grit-part>', + '<message name="IDS_PART_TEST1">', + '{NUM, plural,', + '=1 {Different test text for numeric one}', + 'other {Different test text for plural with {NUM} as number}}', + '</message>', + '</grit-part>') + + # A grdp file with one ICU syntax message with syntax errors (superfluent + # whitespace). + NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR = ( + '<?xml version="1.0" encoding="utf-8"?>', + '<grit-part>', + '<message name="IDS_PART_TEST1">', + '{NUM, plural,', + '= 1 {Test text for numeric one}', + 'other {Test text for plural with {NUM} as number}}', + '</message>', + '</grit-part>') + DO_NOT_UPLOAD_PNG_MESSAGE = ('Do not include actual screenshots in the ' 'changelist. Run ' 'tools/translate/upload_screenshots.py to ' @@ -2724,6 +2903,9 @@ class TranslationScreenshotsTest(unittest.TestCase): 'these files to your changelist:') REMOVE_SIGNATURES_MESSAGE = ('You removed strings associated with these ' 'files. Remove:') + ICU_SYNTAX_ERROR_MESSAGE = ('ICU syntax errors were found in the following ' + 'strings (problems or feedback? Contact ' + 'rainhard@chromium.org):') def makeInputApi(self, files): input_api = MockInputApi() @@ -2742,7 +2924,7 @@ class TranslationScreenshotsTest(unittest.TestCase): self.NEW_GRD_CONTENTS1, action='M'), MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS1, self.NEW_GRDP_CONTENTS1, action='M')]) - warnings = PRESUBMIT._CheckTranslationScreenshots(input_api, + warnings = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) self.assertEqual(0, len(warnings)) @@ -2752,7 +2934,7 @@ class TranslationScreenshotsTest(unittest.TestCase): self.NEW_GRD_CONTENTS1, action='M'), MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS2, self.NEW_GRDP_CONTENTS1, action='M')]) - warnings = PRESUBMIT._CheckTranslationScreenshots(input_api, + warnings = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) self.assertEqual(1, len(warnings)) self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[0].message) @@ -2767,7 +2949,7 @@ class TranslationScreenshotsTest(unittest.TestCase): self.OLD_GRD_CONTENTS, action='M'), MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS2, self.OLD_GRDP_CONTENTS, action='M')]) - warnings = PRESUBMIT._CheckTranslationScreenshots(input_api, + warnings = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) self.assertEqual(1, len(warnings)) self.assertEqual(self.GENERATE_SIGNATURES_MESSAGE, warnings[0].message) @@ -2794,7 +2976,7 @@ class TranslationScreenshotsTest(unittest.TestCase): MockAffectedFile( os.path.join('test_grd', 'IDS_TEST1.png'), 'binary', action='A') ]) - warnings = PRESUBMIT._CheckTranslationScreenshots(input_api, + warnings = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) self.assertEqual(2, len(warnings)) self.assertEqual(self.DO_NOT_UPLOAD_PNG_MESSAGE, warnings[0].message) @@ -2829,7 +3011,7 @@ class TranslationScreenshotsTest(unittest.TestCase): os.path.join('part_grdp', 'IDS_PART_TEST1.png'), 'binary', action='A') ]) - warnings = PRESUBMIT._CheckTranslationScreenshots(input_api, + warnings = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) self.assertEqual(2, len(warnings)) self.assertEqual(self.DO_NOT_UPLOAD_PNG_MESSAGE, warnings[0].message) @@ -2874,7 +3056,7 @@ class TranslationScreenshotsTest(unittest.TestCase): 'binary', action='A'), ]) - warnings = PRESUBMIT._CheckTranslationScreenshots(input_api, + warnings = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) self.assertEqual([], warnings) @@ -2902,7 +3084,7 @@ class TranslationScreenshotsTest(unittest.TestCase): MockFile(os.path.join('part_grdp', 'IDS_PART_TEST2.png.sha1'), 'binary', '') ]) - warnings = PRESUBMIT._CheckTranslationScreenshots(input_api, + warnings = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) self.assertEqual(1, len(warnings)) self.assertEqual(self.REMOVE_SIGNATURES_MESSAGE, warnings[0].message) @@ -2942,7 +3124,7 @@ class TranslationScreenshotsTest(unittest.TestCase): 'old_contents', action='D') ]) - warnings = PRESUBMIT._CheckTranslationScreenshots(input_api, + warnings = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) self.assertEqual(1, len(warnings)) self.assertEqual(self.REMOVE_SIGNATURES_MESSAGE, warnings[0].message) @@ -2981,10 +3163,69 @@ class TranslationScreenshotsTest(unittest.TestCase): 'binary', action='D') ]) - warnings = PRESUBMIT._CheckTranslationScreenshots(input_api, + warnings = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) self.assertEqual([], warnings) + def testIcuSyntax(self): + # Add valid ICU syntax string. Should not raise an error. + input_api = self.makeInputApi([ + MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK2, + self.NEW_GRD_CONTENTS1, action='M'), + MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2, + self.NEW_GRDP_CONTENTS1, action='M')]) + results = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) + # We expect no ICU syntax errors. + icu_errors = [e for e in results + if e.message == self.ICU_SYNTAX_ERROR_MESSAGE] + self.assertEqual(0, len(icu_errors)) + + # Valid changes in ICU syntax. Should not raise an error. + input_api = self.makeInputApi([ + MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK2, + self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK1, action='M'), + MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK2, + self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1, action='M')]) + results = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) + # We expect no ICU syntax errors. + icu_errors = [e for e in results + if e.message == self.ICU_SYNTAX_ERROR_MESSAGE] + self.assertEqual(0, len(icu_errors)) + + # Add invalid ICU syntax strings. Should raise two errors. + input_api = self.makeInputApi([ + MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR, + self.NEW_GRD_CONTENTS1, action='M'), + MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR, + self.NEW_GRD_CONTENTS1, action='M')]) + results = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) + # We expect 2 ICU syntax errors. + icu_errors = [e for e in results + if e.message == self.ICU_SYNTAX_ERROR_MESSAGE] + self.assertEqual(1, len(icu_errors)) + self.assertEqual([ + 'IDS_TEST1: This message looks like an ICU plural, but does not follow ' + 'ICU syntax.', + 'IDS_PART_TEST1: Variant "= 1" is not valid for plural message' + ], icu_errors[0].items) + + # Change two strings to have ICU syntax errors. Should raise two errors. + input_api = self.makeInputApi([ + MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR, + self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK1, action='M'), + MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR, + self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1, action='M')]) + results = PRESUBMIT._CheckStrings(input_api, MockOutputApi()) + # We expect 2 ICU syntax errors. + icu_errors = [e for e in results + if e.message == self.ICU_SYNTAX_ERROR_MESSAGE] + self.assertEqual(1, len(icu_errors)) + self.assertEqual([ + 'IDS_TEST1: This message looks like an ICU plural, but does not follow ' + 'ICU syntax.', + 'IDS_PART_TEST1: Variant "= 1" is not valid for plural message' + ], icu_errors[0].items) + class TranslationExpectationsTest(unittest.TestCase): ERROR_MESSAGE_FORMAT = ( @@ -3296,5 +3537,42 @@ class SetNoParentTest(unittest.TestCase): self.assertEqual([], errors) +class MojomStabilityCheckTest(unittest.TestCase): + def runTestWithAffectedFiles(self, affected_files): + mock_input_api = MockInputApi() + mock_input_api.files = affected_files + mock_output_api = MockOutputApi() + return PRESUBMIT._CheckStableMojomChanges( + mock_input_api, mock_output_api) + + def testSafeChangePasses(self): + errors = self.runTestWithAffectedFiles([ + MockAffectedFile('foo/foo.mojom', + ['[Stable] struct S { [MinVersion=1] int32 x; };'], + old_contents=['[Stable] struct S {};']) + ]) + self.assertEqual([], errors) + + def testBadChangeFails(self): + errors = self.runTestWithAffectedFiles([ + MockAffectedFile('foo/foo.mojom', + ['[Stable] struct S { int32 x; };'], + old_contents=['[Stable] struct S {};']) + ]) + self.assertEqual(1, len(errors)) + self.assertTrue('not backward-compatible' in errors[0].message) + + def testDeletedFile(self): + """Regression test for https://crbug.com/1091407.""" + errors = self.runTestWithAffectedFiles([ + MockAffectedFile('a.mojom', [], old_contents=['struct S {};'], + action='D'), + MockAffectedFile('b.mojom', + ['struct S {}; struct T { S s; };'], + old_contents=['import "a.mojom"; struct T { S s; };']) + ]) + self.assertEqual([], errors) + + if __name__ == '__main__': unittest.main() |