pnoland | dfef663b | 2016-04-08 19:49:54 | [diff] [blame] | 1 | # Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | import os |
| 6 | import re |
| 7 | import sys |
| 8 | import unittest |
| 9 | import PRESUBMIT |
| 10 | |
pnoland | 4feacb1e | 2016-10-21 21:50:03 | [diff] [blame] | 11 | sys.path.append( |
| 12 | os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) |
pnoland | dfef663b | 2016-04-08 19:49:54 | [diff] [blame] | 13 | from PRESUBMIT_test_mocks import MockOutputApi, MockChange |
| 14 | |
| 15 | class MockInputApi(object): |
| 16 | """ Mocked input api for unit testing of presubmit. |
| 17 | This lets us mock things like file system operations and changed files. |
| 18 | """ |
| 19 | def __init__(self): |
| 20 | self.re = re |
| 21 | self.os_path = os.path |
| 22 | self.files = [] |
| 23 | self.is_committing = False |
| 24 | |
| 25 | def AffectedFiles(self): |
| 26 | return self.files |
| 27 | |
| 28 | def AffectedSourceFiles(self): |
| 29 | return self.files |
| 30 | |
| 31 | def ReadFile(self, f): |
| 32 | """ Returns the mock contents of f if they've been defined. |
| 33 | """ |
| 34 | for api_file in self.files: |
| 35 | if api_file.LocalPath() == f: |
| 36 | return api_file.NewContents() |
| 37 | |
| 38 | |
| 39 | class MockFile(object): |
| 40 | """Mock file object so that presubmit can act invoke file system operations. |
| 41 | """ |
| 42 | def __init__(self, local_path, new_contents): |
| 43 | self._local_path = local_path |
| 44 | self._new_contents = new_contents |
| 45 | self._changed_contents = ([(i + 1, l) for i, l in enumerate(new_contents)]) |
| 46 | |
| 47 | def ChangedContents(self): |
| 48 | return self._changed_contents |
| 49 | |
| 50 | def NewContents(self): |
| 51 | return self._new_contents |
| 52 | |
| 53 | def LocalPath(self): |
| 54 | return self._local_path |
| 55 | |
| 56 | def AbsoluteLocalPath(self): |
| 57 | return self._local_path |
| 58 | # Format string used as the contents of a mock sync.proto in order to |
| 59 | # test presubmit parsing of EntitySpecifics definition in that file. |
| 60 | MOCK_PROTOFILE_CONTENTS = ('\n' |
| 61 | 'message EntitySpecifics {\n' |
| 62 | '//comment\n' |
| 63 | '\n' |
| 64 | 'optional AutofillSpecifics autofill = 123;\n' |
| 65 | 'optional AppSpecifics app = 456;\n' |
| 66 | 'optional AppSettingSpecifics app_setting = 789;\n' |
pnoland | 4feacb1e | 2016-10-21 21:50:03 | [diff] [blame] | 67 | 'optional ExtensionSettingSpecifics extension_setting = 910;\n' |
pnoland | dfef663b | 2016-04-08 19:49:54 | [diff] [blame] | 68 | '//comment\n' |
| 69 | '}\n') |
| 70 | |
| 71 | |
| 72 | # Format string used as the contents of a mock model_type.cc |
| 73 | # in order to test presubmit parsing of the ModelTypeInfoMap in that file. |
| 74 | MOCK_MODELTYPE_CONTENTS =('\n' |
| 75 | 'const ModelTypeInfo kModelTypeInfoMap[] = {\n' |
| 76 | '{APP_SETTINGS, "APP_SETTING", "app_settings", "App settings",\n' |
| 77 | 'sync_pb::EntitySpecifics::kAppSettingFieldNumber, 13},\n' |
| 78 | '%s\n' |
| 79 | '};\n') |
| 80 | |
| 81 | |
| 82 | class ModelTypeInfoChangeTest(unittest.TestCase): |
| 83 | """Unit testing class that contains tests for sync/PRESUBMIT.py. |
| 84 | """ |
| 85 | def test_ValidChangeMultiLine(self): |
| 86 | results = self._testChange('{APPS, "APP", "apps", "Apps",\n' |
| 87 | 'sync_pb::EntitySpecifics::kAppFieldNumber, 12},') |
| 88 | self.assertEqual(0, len(results)) |
| 89 | |
| 90 | def testValidChangeToleratesPluralization(self): |
| 91 | results = self._testChange('{APPS, "APP", "apps", "App",\n' |
| 92 | 'sync_pb::EntitySpecifics::kAppFieldNumber, 12},') |
| 93 | self.assertEqual(0, len(results)) |
| 94 | |
| 95 | def testValidChangeGrandfatheredEntry(self): |
| 96 | results = self._testChange('{PROXY_TABS, "", "", "Tabs", -1, 25},') |
| 97 | self.assertEqual(0, len(results)) |
| 98 | |
| 99 | def testInvalidChangeMismatchedNotificationType(self): |
| 100 | results = self._testChange('{AUTOFILL, "AUTOFILL_WRONG", "autofill",\n' |
| 101 | '"Autofill",sync_pb::EntitySpecifics::kAutofillFieldNumber, 6},') |
| 102 | self.assertEqual(1, len(results)) |
| 103 | self.assertTrue('notification type' in results[0].message) |
| 104 | |
| 105 | def testInvalidChangeInconsistentModelType(self): |
| 106 | results = self._testChange('{AUTOFILL, "AUTOFILL", "autofill",\n' |
| 107 | '"Autofill Extra",sync_pb::EntitySpecifics::kAutofillFieldNumber, 6},') |
| 108 | self.assertEqual(1, len(results)) |
| 109 | self.assertTrue('model type string' in results[0].message) |
| 110 | |
| 111 | def testInvalidChangeNotTitleCased(self): |
| 112 | results = self._testChange('{AUTOFILL, "AUTOFILL", "autofill",\n' |
| 113 | '"autofill",sync_pb::EntitySpecifics::kAutofillFieldNumber, 6},') |
| 114 | self.assertEqual(1, len(results)) |
| 115 | self.assertTrue('title' in results[0].message) |
| 116 | |
| 117 | def testInvalidChangeInconsistentRootTag(self): |
| 118 | results = self._testChange('{AUTOFILL, "AUTOFILL", "autofill root",\n' |
| 119 | '"Autofill",sync_pb::EntitySpecifics::kAutofillFieldNumber, 6},') |
| 120 | self.assertEqual(1, len(results)) |
| 121 | self.assertTrue('root tag' in results[0].message) |
| 122 | |
| 123 | def testInvalidChangeDuplicatedValues(self): |
| 124 | results = self._testChange('{APP_SETTINGS, "APP_SETTING",\n' |
| 125 | '"app_settings", "App settings",\n' |
| 126 | 'sync_pb::EntitySpecifics::kAppSettingFieldNumber, 13},\n') |
| 127 | self.assertEqual(6, len(results)) |
| 128 | self.assertTrue('APP_SETTINGS' in results[0].message) |
| 129 | |
pnoland | 4feacb1e | 2016-10-21 21:50:03 | [diff] [blame] | 130 | def testBlacklistedRootTag(self): |
| 131 | results = self._testChange('{EXTENSION_SETTING, "EXTENSION_SETTING",\n' |
| 132 | '"_mts_schema_descriptor","Extension Setting",\n' |
| 133 | 'sync_pb::EntitySpecifics::kExtensionSettingFieldNumber, 6},') |
| 134 | self.assertEqual(2, len(results)) |
| 135 | self.assertTrue('_mts_schema_descriptor' in results[0].message) |
| 136 | self.assertTrue("blacklist" in results[0].message) |
| 137 | |
pnoland | dfef663b | 2016-04-08 19:49:54 | [diff] [blame] | 138 | def _testChange(self, modeltype_literal): |
| 139 | mock_input_api = MockInputApi() |
| 140 | mock_input_api.files = [ |
| 141 | MockFile(os.path.abspath('./protocol/sync.proto'), |
| 142 | MOCK_PROTOFILE_CONTENTS), |
| 143 | MockFile(os.path.abspath('./syncable/model_type.cc'), |
| 144 | MOCK_MODELTYPE_CONTENTS % (modeltype_literal)) |
| 145 | ] |
| 146 | |
| 147 | return PRESUBMIT.CheckChangeOnCommit(mock_input_api, MockOutputApi()) |
| 148 | if __name__ == '__main__': |
| 149 | unittest.main() |