Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(258)

Side by Side Diff: sync/PRESUBMIT.py

Issue 2130453004: [Sync] Move //sync to //components/sync. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase. Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sync/OWNERS ('k') | sync/PRESUBMIT_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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 """Presubmit script for sync
6 This checks that ModelTypeInfo entries in model_type.cc follow conventions.
7 See CheckModelTypeInfoMap or model_type.cc for more detail on the rules.
8 """
9
10 import os
11
12 # Some definitions don't follow all the conventions we want to enforce.
13 # It's either difficult or impossible to fix this, so we ignore the problem(s).
14 GRANDFATHERED_MODEL_TYPES = [
15 'UNSPECIFIED', # Doesn't have a root tag or notification type.
16 'TOP_LEVEL_FOLDER', # Doesn't have a root tag or notification type.
17 'AUTOFILL_WALLET_DATA', # Root tag and model type string lack DATA suffix.
18 'APP_SETTINGS', # Model type string has inconsistent capitalization.
19 'EXTENSION_SETTINGS', # Model type string has inconsistent capitalization.
20 'SUPERVISED_USER_SETTINGS', # Root tag and model type string replace
21 # 'Supervised' with 'Managed'
22 'SUPERVISED_USERS', # See previous.
23 'SUPERVISED_USER_WHITELISTS', # See previous.
24 'SUPERVISED_USER_SHARED_SETTINGS', # See previous.
25 'PROXY_TABS', # Doesn't have a root tag or notification type.
26 'NIGORI'] # Model type string is 'encryption keys'.
27
28 # Number of distinct fields in a map entry; used to create
29 # sets that check for uniqueness.
30 MAP_ENTRY_FIELD_COUNT = 6
31
32 # String that precedes the ModelType when referencing the
33 # proto field number enum e.g.
34 # sync_pb::EntitySpecifics::kManagedUserFieldNumber.
35 # Used to map from enum references to the ModelType.
36 FIELD_NUMBER_PREFIX = 'sync_pb::EntitySpecifics::k'
37
38 # Start and end regexes for finding the EntitySpecifics definition in
39 # sync.proto.
40 PROTO_DEFINITION_START_PATTERN = '^message EntitySpecifics'
41 PROTO_DEFINITION_END_PATTERN = '^\}'
42
43 # Start and end regexes for finding the ModelTypeInfoMap definition
44 # in model_type.cc.
45 MODEL_TYPE_START_PATTERN = '^const ModelTypeInfo kModelTypeInfoMap'
46 MODEL_TYPE_END_PATTERN = '^\};'
47
48 # Strings relating to files we'll need to read.
49 # model_type.cc is where the ModelTypeInfoMap is
50 # sync.proto is where the proto definitions for ModelTypes are.
51 PROTO_FILE_PATH = './protocol/sync.proto'
52 MODEL_TYPE_FILE_NAME = 'model_type.cc'
53
54
55 def CheckChangeOnUpload(input_api, output_api):
56 """Preupload check function required by presubmit convention.
57 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
58 """
59 for f in input_api.AffectedFiles():
60 if(f.LocalPath().endswith(MODEL_TYPE_FILE_NAME)):
61 return CheckModelTypeInfoMap(input_api, output_api, f)
62 return []
63
64
65 def CheckChangeOnCommit(input_api, output_api):
66 """Precommit check function required by presubmit convention.
67 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
68 """
69 for f in input_api.AffectedFiles():
70 if f.LocalPath().endswith(MODEL_TYPE_FILE_NAME):
71 return CheckModelTypeInfoMap(input_api, output_api, f)
72 return []
73
74
75 def CheckModelTypeInfoMap(input_api, output_api, model_type_file):
76 """Checks the kModelTypeInfoMap in model_type.cc follows conventions.
77 Checks that the kModelTypeInfoMap follows the below rules:
78 1) The model type string should match the model type name, but with
79 only the first letter capitalized and spaces instead of underscores.
80 2) The root tag should be the same as the model type but all lowercase.
81 3) The notification type should match the proto message name.
82 4) No duplicate data across model types.
83 Args:
84 input_api: presubmit_support InputApi instance
85 output_api: presubmit_support OutputApi instance
86 model_type_file: AffectedFile object where the ModelTypeInfoMap is
87 Returns:
88 A (potentially empty) list PresubmitError objects corresponding to
89 violations of the above rules.
90 """
91 accumulated_problems = []
92 map_entries = ParseModelTypeEntries(
93 input_api, model_type_file.AbsoluteLocalPath())
94 # If any line of the map changed, we check the whole thing since
95 # definitions span multiple lines and there are rules that apply across
96 # all definitions e.g. no duplicated field values.
97 check_map = False
98 for line_num, _ in model_type_file.ChangedContents():
99 for map_entry in map_entries:
100 if line_num in map_entry.affected_lines:
101 check_map = True
102 break
103
104 if not check_map:
105 return []
106 proto_field_definitions = ParseSyncProtoFieldIdentifiers(
107 input_api, os.path.abspath(PROTO_FILE_PATH))
108 accumulated_problems.extend(
109 CheckNoDuplicatedFieldValues(output_api, map_entries))
110
111 for map_entry in map_entries:
112 entry_problems = []
113 entry_problems.extend(
114 CheckNotificationTypeMatchesProtoMessageName(
115 output_api, map_entry, proto_field_definitions))
116
117 if map_entry.model_type not in GRANDFATHERED_MODEL_TYPES:
118 entry_problems.extend(
119 CheckModelTypeStringMatchesModelType(output_api, map_entry))
120 entry_problems.extend(
121 CheckRootTagMatchesModelType(output_api, map_entry))
122
123 if len(entry_problems) > 0:
124 accumulated_problems.extend(entry_problems)
125
126 return accumulated_problems
127
128
129 class ModelTypeEnumEntry(object):
130 """Class that encapsulates a ModelTypeInfo definition in model_type.cc.
131 Allows access to each of the named fields in the definition and also
132 which lines the definition spans.
133 Attributes:
134 model_type: entry's ModelType enum value
135 notification_type: model type's notification string
136 root_tag: model type's root tag
137 model_type_string: string corresponding to the ModelType
138 field_number: proto field number
139 histogram_val: value identifying ModelType in histogram
140 affected_lines: lines in model_type.cc that the definition spans
141 """
142 def __init__(self, entry_strings, affected_lines):
143 (model_type, notification_type, root_tag, model_type_string,
144 field_number, histogram_val) = entry_strings
145 self.model_type = model_type
146 self.notification_type = notification_type
147 self.root_tag = root_tag
148 self.model_type_string = model_type_string
149 self.field_number = field_number
150 self.histogram_val = histogram_val
151 self.affected_lines = affected_lines
152
153
154 def ParseModelTypeEntries(input_api, model_type_cc_path):
155 """Parses model_type_cc_path for ModelTypeEnumEntries
156 Args:
157 input_api: presubmit_support InputAPI instance
158 model_type_cc_path: path to file containing the ModelTypeInfo entries
159 Returns:
160 A list of ModelTypeEnumEntry objects read from model_type.cc.
161 e.g. ('AUTOFILL_WALLET_METADATA', 'WALLET_METADATA',
162 'autofill_wallet_metadata', 'Autofill Wallet Metadata',
163 'sync_pb::EntitySpecifics::kWalletMetadataFieldNumber', '35',
164 [63, 64, 65])
165 """
166 file_contents = input_api.ReadFile(model_type_cc_path)
167 start_pattern = input_api.re.compile(MODEL_TYPE_START_PATTERN)
168 end_pattern = input_api.re.compile(MODEL_TYPE_END_PATTERN)
169 results, definition_strings, definition_lines = [], [], []
170 inside_enum = False
171 current_line_number = 1
172 for line in file_contents.splitlines():
173 if start_pattern.match(line):
174 inside_enum = True
175 continue
176 if inside_enum:
177 if end_pattern.match(line):
178 break
179 line_entries = line.strip().strip('{},').split(',')
180 definition_strings.extend([entry.strip('" ') for entry in line_entries])
181 definition_lines.append(current_line_number)
182 if line.endswith('},'):
183 results.append(ModelTypeEnumEntry(definition_strings, definition_lines))
184 definition_strings = []
185 definition_lines = []
186 current_line_number += 1
187 return results
188
189
190 def ParseSyncProtoFieldIdentifiers(input_api, sync_proto_path):
191 """Parses proto field identifiers from the EntitySpecifics definition.
192 Args:
193 input_api: presubmit_support InputAPI instance
194 proto_path: path to the file containing the proto field definitions
195 Returns:
196 A dictionary of the format {'SyncDataType': 'field_identifier'}
197 e.g. {'AutofillSpecifics': 'autofill'}
198 """
199 proto_field_definitions = {}
200 proto_file_contents = input_api.ReadFile(sync_proto_path).splitlines()
201 start_pattern = input_api.re.compile(PROTO_DEFINITION_START_PATTERN)
202 end_pattern = input_api.re.compile(PROTO_DEFINITION_END_PATTERN)
203 in_proto_def = False
204 for line in proto_file_contents:
205 if start_pattern.match(line):
206 in_proto_def = True
207 continue
208 if in_proto_def:
209 if end_pattern.match(line):
210 break
211 line = line.strip()
212 split_proto_line = line.split(' ')
213 # ignore comments and lines that don't contain definitions.
214 if '//' in line or len(split_proto_line) < 3:
215 continue
216
217 field_typename = split_proto_line[1]
218 field_identifier = split_proto_line[2]
219 proto_field_definitions[field_typename] = field_identifier
220 return proto_field_definitions
221
222
223 def StripTrailingS(string):
224 return string.rstrip('sS')
225
226
227 def IsTitleCased(string):
228 return reduce(lambda bool1, bool2: bool1 and bool2,
229 [s[0].isupper() for s in string.split(' ')])
230
231
232 def FormatPresubmitError(output_api, message, affected_lines):
233 """ Outputs a formatted error message with filename and line number(s).
234 """
235 if len(affected_lines) > 1:
236 message_including_lines = 'Error at lines %d-%d in model_type.cc: %s' %(
237 affected_lines[0], affected_lines[-1], message)
238 else:
239 message_including_lines = 'Error at line %d in model_type.cc: %s' %(
240 affected_lines[0], message)
241 return output_api.PresubmitError(message_including_lines)
242
243
244 def CheckNotificationTypeMatchesProtoMessageName(
245 output_api, map_entry, proto_field_definitions):
246 """Check that map_entry's notification type matches sync.proto.
247 Verifies that the notification_type matches the name of the field defined
248 in the sync.proto by looking it up in the proto_field_definitions map.
249 Args:
250 output_api: presubmit_support OutputApi instance
251 map_entry: ModelTypeEnumEntry instance
252 proto_field_definitions: dict of proto field types and field names
253 Returns:
254 A potentially empty list of PresubmitError objects corresponding to
255 violations of the above rule
256 """
257 if map_entry.field_number == '-1':
258 return []
259 proto_message_name = proto_field_definitions[
260 FieldNumberToPrototypeString(map_entry.field_number)]
261 if map_entry.notification_type.lower() != proto_message_name:
262 return [
263 FormatPresubmitError(
264 output_api,'notification type "%s" does not match proto message'
265 ' name defined in sync.proto: ' '"%s"' %
266 (map_entry.notification_type, proto_message_name),
267 map_entry.affected_lines)]
268 return []
269
270
271 def CheckNoDuplicatedFieldValues(output_api, map_entries):
272 """Check that map_entries has no duplicated field values.
273 Verifies that every map_entry in map_entries doesn't have a field value
274 used elsewhere in map_entries, ignoring special values ("" and -1).
275 Args:
276 output_api: presubmit_support OutputApi instance
277 map_entries: list of ModelTypeEnumEntry objects to check
278 Returns:
279 A list PresubmitError objects for each duplicated field value
280 """
281 problem_list = []
282 field_value_sets = [set() for i in range(MAP_ENTRY_FIELD_COUNT)]
283 for map_entry in map_entries:
284 field_values = [
285 map_entry.model_type, map_entry.notification_type,
286 map_entry.root_tag, map_entry.model_type_string,
287 map_entry.field_number, map_entry.histogram_val]
288 for i in range(MAP_ENTRY_FIELD_COUNT):
289 field_value = field_values[i]
290 field_value_set = field_value_sets[i]
291 if field_value in field_value_set:
292 problem_list.append(
293 FormatPresubmitError(
294 output_api, 'Duplicated field value "%s"' % field_value,
295 map_entry.affected_lines))
296 elif len(field_value) > 0 and field_value != '-1':
297 field_value_set.add(field_value)
298 return problem_list
299
300
301 def CheckModelTypeStringMatchesModelType(output_api, map_entry):
302 """Check that map_entry's model_type_string matches ModelType.
303 Args:
304 output_api: presubmit_support OutputApi instance
305 map_entry: ModelTypeEnumEntry object to check
306 Returns:
307 A list of PresubmitError objects for each violation
308 """
309 problem_list = []
310 expected_model_type_string = map_entry.model_type.lower().replace('_', ' ')
311 if (StripTrailingS(expected_model_type_string) !=
312 StripTrailingS(map_entry.model_type_string.lower())):
313 problem_list.append(
314 FormatPresubmitError(
315 output_api,'model type string "%s" does not match model type.'
316 ' It should be "%s"' % (
317 map_entry.model_type_string, expected_model_type_string.title()),
318 map_entry.affected_lines))
319 if not IsTitleCased(map_entry.model_type_string):
320 problem_list.append(
321 FormatPresubmitError(
322 output_api,'model type string "%s" should be title cased' %
323 (map_entry.model_type_string), map_entry.affected_lines))
324 return problem_list
325
326
327 def CheckRootTagMatchesModelType(output_api, map_entry):
328 """Check that map_entry's root tag matches ModelType.
329 Args:
330 output_api: presubmit_support OutputAPI instance
331 map_entry: ModelTypeEnumEntry object to check
332 Returns:
333 A list of PresubmitError objects for each violation
334 """
335 expected_root_tag = map_entry.model_type.lower()
336 if (StripTrailingS(expected_root_tag) !=
337 StripTrailingS(map_entry.root_tag)):
338 return [
339 FormatPresubmitError(
340 output_api,'root tag "%s" does not match model type. It should'
341 'be "%s"' % (map_entry.root_tag, expected_root_tag),
342 map_entry.affected_lines)]
343 return []
344
345
346 def FieldNumberToPrototypeString(field_number):
347 """Converts a field number enum reference to an EntitySpecifics string.
348 Converts a reference to the field number enum to the corresponding
349 proto data type string.
350 Args:
351 field_number: string representation of a field number enum reference
352 Returns:
353 A string that is the corresponding proto field data type. e.g.
354 FieldNumberToPrototypeString('EntitySpecifics::kAppFieldNumber')
355 => 'AppSpecifics'
356 """
357 return field_number.replace(FIELD_NUMBER_PREFIX, '').replace(
358 'FieldNumber', 'Specifics').replace(
359 'AppNotificationSpecifics', 'AppNotification')
OLDNEW
« no previous file with comments | « sync/OWNERS ('k') | sync/PRESUBMIT_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698