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

Side by Side Diff: chrome/browser/extensions/PRESUBMIT.py

Issue 170233008: Add presubmit check and automatic update script for ExtensionPermission enum. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 10 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
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Chromium presubmit script for src/chrome/browser/extensions. 5 """Chromium presubmit script for src/chrome/browser/extensions.
6 6
7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts 7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8 for more details on the presubmit API built into gcl. 8 for more details on the presubmit API built into gcl.
9 """ 9 """
10 import sys
10 11
11 def GetPreferredTrySlaves(): 12 def GetPreferredTrySlaves():
12 return ['linux_chromeos'] 13 return ['linux_chromeos']
13 14
14 class HistogramValueChecker(object): 15 def _CheckHistogramEnumValue(input_api, output_api):
15 """Verify that changes to "extension_function_histogram_value.h" are valid. 16 LOCAL_PATH = 'chrome/browser/extensions/extension_function_histogram_value.h'
17 ENUM_START_MARKER = 'enum HistogramValue {'
18 ENUM_END_MARKER = ' ENUM BOUNDARY'
19 original_sys_path = sys.path
16 20
17 See comments at the top of the "extension_function_histogram_value.h" file 21 try:
18 for what are considered valid changes. There are situations where this script 22 sys.path = sys.path + [input_api.os_path.join(
Jeffrey Yasskin 2014/02/20 19:07:36 "sys.path.append(input_api...)"
19 gives false positive warnings, i.e. it warns even though the edit is 23 input_api.PresubmitLocalPath(), '../../../', 'tools', 'extra_imports')]
Jeffrey Yasskin 2014/02/20 19:07:36 I'm nervous about this pattern for imports. Howeve
Jeffrey Yasskin 2014/02/20 19:07:36 Please check with a src/PRESUBMIT.py owner to make
20 legitimate. Since the script warns using prompt warnings, the user can always 24 from strict_enum_value_checker import StrictEnumValueChecker
21 choose to continue. The main point is to attract the attention to all 25 finally:
22 (potentially or not) invalid edits. 26 sys.path = original_sys_path
23 27
24 """ 28 return StrictEnumValueChecker(input_api, output_api, ENUM_START_MARKER,
Jeffrey Yasskin 2014/02/20 19:07:36 Instead of defining constants to describe the stri
25 29 ENUM_END_MARKER, LOCAL_PATH)
26 # The name of the file we want to check against
27 LOCAL_PATH = "chrome/browser/extensions/extension_function_histogram_value.h"
28
29 # The markers we look for in the above source file as delimiters of the enum
30 # definition.
31 ENUM_START_MARKER = "enum HistogramValue {"
32 ENUM_END_MARKER = " ENUM_BOUNDARY"
33
34 def __init__(self, input_api, output_api):
35 self.input_api = input_api
36 self.output_api = output_api
37 self.results = []
38
39 class EnumRange(object):
40 """Represents a range of line numbers (1-based)"""
41 def __init__(self, first_line, last_line):
42 self.first_line = first_line
43 self.last_line = last_line
44
45 def Count(self):
46 return self.last_line - self.first_line + 1
47
48 def Contains(self, line_num):
49 return self.first_line <= line_num and line_num <= self.last_line
50
51 def LogInfo(self, message):
52 self.input_api.logging.info(message)
53 return
54
55 def LogDebug(self, message):
56 self.input_api.logging.debug(message)
57 return
58
59 def ComputeEnumRangeInContents(self, contents):
60 """Returns an |EnumRange| object representing the line extent of the
61 HistogramValue enum members in |contents|. The line numbers are 1-based,
62 compatible with line numbers returned by AffectedFile.ChangeContents().
63 |contents| is a list of strings reprenting the lines of a text file.
64
65 If either ENUM_START_MARKER or ENUM_END_MARKER cannot be found in
66 |contents|, returns None and emits detailed warnings about the problem.
67
68 """
69 first_enum_line = 0
70 last_enum_line = 0
71 line_num = 1 # Line numbers are 1-based
72 for line in contents:
73 if line.startswith(self.ENUM_START_MARKER):
74 first_enum_line = line_num + 1
75 elif line.startswith(self.ENUM_END_MARKER):
76 last_enum_line = line_num
77 line_num += 1
78
79 if first_enum_line == 0:
80 self.EmitWarning("The presubmit script could not find the start of the "
81 "enum definition (\"%s\"). Did the enum definition "
82 "change?" % self.ENUM_START_MARKER)
83 return None
84
85 if last_enum_line == 0:
86 self.EmitWarning("The presubmit script could not find the end of the "
87 "enum definition (\"%s\"). Did the enum definition "
88 "change?" % self.ENUM_END_MARKER)
89 return None
90
91 if first_enum_line >= last_enum_line:
92 self.EmitWarning("The presubmit script located the start of the enum "
93 "definition (\"%s\" at line %d) *after* its end "
94 "(\"%s\" at line %d). Something is not quite right."
95 % (self.ENUM_START_MARKER, first_enum_line,
96 self.ENUM_END_MARKER, last_enum_line))
97 return None
98
99 self.LogInfo("Line extent of |HistogramValue| enum definition: "
100 "first_line=%d, last_line=%d."
101 % (first_enum_line, last_enum_line))
102 return self.EnumRange(first_enum_line, last_enum_line)
103
104 def ComputeEnumRangeInNewFile(self, affected_file):
105 return self.ComputeEnumRangeInContents(affected_file.NewContents())
106
107 def GetLongMessage(self):
108 return str("The file \"%s\" contains the definition of the "
109 "|HistogramValue| enum which should be edited in specific ways "
110 "only - *** read the comments at the top of the header file ***"
111 ". There are changes to the file that may be incorrect and "
112 "warrant manual confirmation after review. Note that this "
113 "presubmit script can not reliably report the nature of all "
114 "types of invalid changes, especially when the diffs are "
115 "complex. For example, an invalid deletion may be reported "
116 "whereas the change contains a valid rename."
117 % self.LOCAL_PATH)
118
119 def EmitWarning(self, message, line_number=None, line_text=None):
120 """Emits a presubmit prompt warning containing the short message
121 |message|. |item| is |LOCAL_PATH| with optional |line_number| and
122 |line_text|.
123
124 """
125 if line_number is not None and line_text is not None:
126 item = "%s(%d): %s" % (self.LOCAL_PATH, line_number, line_text)
127 elif line_number is not None:
128 item = "%s(%d)" % (self.LOCAL_PATH, line_number)
129 else:
130 item = self.LOCAL_PATH
131 long_message = self.GetLongMessage()
132 self.LogInfo(message)
133 self.results.append(
134 self.output_api.PresubmitPromptWarning(message, [item], long_message))
135
136 def CollectRangesInsideEnumDefinition(self, affected_file,
137 first_line, last_line):
138 """Returns a list of triplet (line_start, line_end, line_text) of ranges of
139 edits changes. The |line_text| part is the text at line |line_start|.
140 Since it used only for reporting purposes, we do not need all the text
141 lines in the range.
142
143 """
144 results = []
145 previous_line_number = 0
146 previous_range_start_line_number = 0
147 previous_range_start_text = ""
148
149 def addRange():
150 tuple = (previous_range_start_line_number,
151 previous_line_number,
152 previous_range_start_text)
153 results.append(tuple)
154
155 for line_number, line_text in affected_file.ChangedContents():
156 if first_line <= line_number and line_number <= last_line:
157 self.LogDebug("Line change at line number " + str(line_number) + ": " +
158 line_text)
159 # Start a new interval if none started
160 if previous_range_start_line_number == 0:
161 previous_range_start_line_number = line_number
162 previous_range_start_text = line_text
163 # Add new interval if we reached past the previous one
164 elif line_number != previous_line_number + 1:
165 addRange()
166 previous_range_start_line_number = line_number
167 previous_range_start_text = line_text
168 previous_line_number = line_number
169
170 # Add a last interval if needed
171 if previous_range_start_line_number != 0:
172 addRange()
173 return results
174
175 def CheckForFileDeletion(self, affected_file):
176 """Emits a warning notification if file has been deleted """
177 if not affected_file.NewContents():
178 self.EmitWarning("The file seems to be deleted in the changelist. If "
179 "your intent is to really delete the file, the code in "
180 "PRESUBMIT.py should be updated to remove the "
181 "|HistogramValueChecker| class.");
182 return False
183 return True
184
185 def GetDeletedLinesFromScmDiff(self, affected_file):
186 """Return a list of of line numbers (1-based) corresponding to lines
187 deleted from the new source file (if they had been present in it). Note
188 that if multiple contiguous lines have been deleted, the returned list will
189 contain contiguous line number entries. To prevent false positives, we
190 return deleted line numbers *only* from diff chunks which decrease the size
191 of the new file.
192
193 Note: We need this method because we have access to neither the old file
194 content nor the list of "delete" changes from the current presubmit script
195 API.
196
197 """
198 results = []
199 line_num = 0
200 deleting_lines = False
201 for line in affected_file.GenerateScmDiff().splitlines():
202 # Parse the unified diff chunk optional section heading, which looks like
203 # @@ -l,s +l,s @@ optional section heading
204 m = self.input_api.re.match(
205 r'^@@ \-([0-9]+)\,([0-9]+) \+([0-9]+)\,([0-9]+) @@', line)
206 if m:
207 old_line_num = int(m.group(1))
208 old_size = int(m.group(2))
209 new_line_num = int(m.group(3))
210 new_size = int(m.group(4))
211 line_num = new_line_num
212 # Return line numbers only from diff chunks decreasing the size of the
213 # new file
214 deleting_lines = old_size > new_size
215 continue
216 if not line.startswith('-'):
217 line_num += 1
218 if deleting_lines and line.startswith('-') and not line.startswith('--'):
219 results.append(line_num)
220 return results
221
222 def CheckForEnumEntryDeletions(self, affected_file):
223 """Look for deletions inside the enum definition. We currently use a
224 simple heuristics (not 100% accurate): if there are deleted lines inside
225 the enum definition, this might be a deletion.
226
227 """
228 range_new = self.ComputeEnumRangeInNewFile(affected_file)
229 if not range_new:
230 return False
231
232 is_ok = True
233 for line_num in self.GetDeletedLinesFromScmDiff(affected_file):
234 if range_new.Contains(line_num):
235 self.EmitWarning("It looks like you are deleting line(s) from the "
236 "enum definition. This should never happen.",
237 line_num)
238 is_ok = False
239 return is_ok
240
241 def CheckForEnumEntryInsertions(self, affected_file):
242 range = self.ComputeEnumRangeInNewFile(affected_file)
243 if not range:
244 return False
245
246 first_line = range.first_line
247 last_line = range.last_line
248
249 # Collect the range of changes inside the enum definition range.
250 is_ok = True
251 for line_start, line_end, line_text in \
252 self.CollectRangesInsideEnumDefinition(affected_file,
253 first_line,
254 last_line):
255 # The only edit we consider valid is adding 1 or more entries *exactly*
256 # at the end of the enum definition. Every other edit inside the enum
257 # definition will result in a "warning confirmation" message.
258 #
259 # TODO(rpaquay): We currently cannot detect "renames" of existing entries
260 # vs invalid insertions, so we sometimes will warn for valid edits.
261 is_valid_edit = (line_end == last_line - 1)
262
263 self.LogDebug("Edit range in new file at starting at line number %d and "
264 "ending at line number %d: valid=%s"
265 % (line_start, line_end, is_valid_edit))
266
267 if not is_valid_edit:
268 self.EmitWarning("The change starting at line %d and ending at line "
269 "%d is *not* located *exactly* at the end of the "
270 "enum definition. Unless you are renaming an "
271 "existing entry, this is not a valid changes, as new "
272 "entries should *always* be added at the end of the "
273 "enum definition, right before the 'ENUM_BOUNDARY' "
274 "entry." % (line_start, line_end),
275 line_start,
276 line_text)
277 is_ok = False
278 return is_ok
279
280 def PerformChecks(self, affected_file):
281 if not self.CheckForFileDeletion(affected_file):
282 return
283 if not self.CheckForEnumEntryDeletions(affected_file):
284 return
285 if not self.CheckForEnumEntryInsertions(affected_file):
286 return
287
288 def ProcessHistogramValueFile(self, affected_file):
289 self.LogInfo("Start processing file \"%s\"" % affected_file.LocalPath())
290 self.PerformChecks(affected_file)
291 self.LogInfo("Done processing file \"%s\"" % affected_file.LocalPath())
292
293 def Run(self):
294 for file in self.input_api.AffectedFiles(include_deletes=True):
295 if file.LocalPath() == self.LOCAL_PATH:
296 self.ProcessHistogramValueFile(file)
297 return self.results
298 30
299 def CheckChangeOnUpload(input_api, output_api): 31 def CheckChangeOnUpload(input_api, output_api):
300 results = [] 32 results = []
301 results += HistogramValueChecker(input_api, output_api).Run() 33 results += _CheckHistogramEnumValue(input_api, output_api).Run()
rpaquay 2014/02/18 21:39:26 nit: rename "_CheckHistogramEnumValue" as this lin
302 results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api) 34 results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
303 return results 35 return results
304 36
37 def CheckChangeOnCommit(input_api, output_api):
38 results = []
39 results += _CheckHistogramEnumValue(input_api, output_api).Run()
Jeffrey Yasskin 2014/02/20 19:07:36 Just "return _CheckHistogram..." should be equival
40 return results
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/extensions/PRESUBMIT_test.py » ('j') | chrome/browser/extensions/PRESUBMIT_test.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698