OLD | NEW |
---|---|
(Empty) | |
1 # Copyright 2014 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 """Check enum LoginCustomFlags .""" | |
6 | |
7 class LoginCustomFlagsChecker(object): | |
8 """Verify that changes to enum LoginCustomFlags in histograms.xml are valid. | |
9 | |
10 This class is used to check that LoginCustomFlags enum is never shrinked. | |
11 | |
12 Note: this check would probably fail if enum is completely moved to | |
13 another part of histograms.xml, but it works for changes inside enum. | |
14 """ | |
15 def __init__(self, input_api, output_api, path): | |
16 self.input_api = input_api | |
17 self.output_api = output_api | |
18 self.path = path | |
19 self.warnings_exist = False | |
20 self.enum_start = 0 | |
21 self.enum_end = 0 | |
22 self.results = [] | |
23 | |
24 def LogInfo(self, message): | |
25 self.input_api.logging.info(message) | |
26 return | |
27 | |
28 def LogDebug(self, message): | |
29 self.input_api.logging.debug(message) | |
30 return | |
31 | |
32 def GetLongMessage(self, local_path): | |
33 return str("The file \"%s\"\n" | |
34 "contains the definition of the enum LoginCustomFlags,\n" | |
35 "which should never be shrinked or removed.\n" | |
36 % (local_path)) | |
37 | |
38 def EmitWarning(self, message, line_number=None, line_text=None): | |
39 """Emits a presubmit prompt warning containing the short message | |
40 |message|. |item| is |LOCAL_PATH| with optional |line_number| and | |
41 |line_text|. | |
42 | |
43 """ | |
44 if line_number is not None and line_text is not None: | |
45 item = "%s(%d): %s" % (self.path, line_number, line_text) | |
46 elif line_number is not None: | |
47 item = "%s(%d)" % (self.path, line_number) | |
48 else: | |
49 item = self.path | |
50 if self.warnings_exist : | |
51 long_message = '' | |
52 else : | |
53 long_message = self.GetLongMessage(self.path) | |
54 self.warnings_exist = True | |
55 | |
56 self.LogInfo(message) | |
57 self.results.append( | |
58 self.output_api.PresubmitPromptWarning(message, [item], long_message)) | |
59 | |
60 def ReportDeletedLabels(self, removed_labels, prefix=''): | |
61 message = prefix | |
62 message += str("It looks like you are deleting entries from the " | |
63 "enum definition. This should never happen.\n") | |
64 message += "The following labels have been deleted:\n" | |
65 for value in removed_labels : | |
66 message += "\t" + value + "\n" | |
67 self.EmitWarning(message) | |
68 | |
69 def ExtractFistAndLasteLoginCustomFlagsLineNumberFromHistograms(self): | |
70 """Returns tuple (first line number, last line number) in the new version. | |
71 Line number starts with 1. | |
72 """ | |
73 import os | |
74 with open(self.path, 'r') as file_handle: | |
75 line_number = 0 | |
76 enum_start = 0 | |
77 enum_end = 0 | |
78 for line in file_handle: | |
79 line_number = line_number + 1 | |
80 if self.input_api.re.match(r"^<enum name=\"LoginCustomFlags\"", line): | |
81 if enum_start > 0: | |
82 error = str("Bad '" + str(xml_file) + "' format: start of enum " | |
83 "LoginCustomFlags found twice: at lines " | |
84 + str(enum_start) + " and " + str(line_number) | |
85 + ".") | |
86 self.EmitWarning(error, line_number, line) | |
87 return | |
88 enum_start = line_number | |
89 elif enum_start > 0 and self.input_api.re.match(r"^ *<enum", line): | |
90 error = str("Bad '" + str(xml_file) + "' format: end of enum " | |
91 "LoginCustomFlags not found. Found new enum at line " | |
92 + str(line_number) + ":\n" + line) | |
93 self.EmitWarning(error, line_number, line) | |
94 return | |
95 elif enum_start > 0 and self.input_api.re.match(r"^</enum", line): | |
96 enum_end = line_number | |
97 break | |
98 | |
99 if enum_start > 0 and enum_end > enum_start: | |
100 return (enum_start, enum_end) | |
101 | |
102 if enum_start == 0: | |
103 error = str("Bad '" + str(xml_file) + "' format: start of enum " | |
104 "LoginCustomFlags is not found.") | |
105 self.EmitWarning(error, line_number, line) | |
106 return | |
107 | |
108 if enum_end == 0: | |
109 error = str("Bad '" + str(xml_file) + "' format: end of enum " | |
110 "LoginCustomFlags is not found.") | |
111 self.EmitWarning(error, line_number, line) | |
112 return | |
113 | |
114 if enum_start == enum_end: | |
115 error = str("Bad '" + str(xml_file) + "' format: end of enum " | |
116 "LoginCustomFlags is empty.") | |
117 self.EmitWarning(error, line_number, line) | |
118 return | |
119 | |
120 raise Exception("Not reached.") | |
121 | |
122 def CheckDiff(self, affected_file): | |
123 """Check diff is valid.""" | |
124 added_labels = [] | |
125 removed_labels = [] | |
126 skip_chunk = True | |
127 line_num = 0 | |
128 old_enum_start_found = False | |
129 old_enum_end_found = False | |
130 for line in affected_file.GenerateScmDiff().splitlines(): | |
131 # Parse the unified diff chunk optional section heading, which looks like | |
132 # @@ -l,s +l,s @@ optional section heading | |
133 m = self.input_api.re.match( | |
134 r"^@@ \-([0-9]+)\,([0-9]+) \+([0-9]+)\,([0-9]+) @@", line) | |
135 if m: | |
136 old_line_num = int(m.group(1)) | |
137 old_size = int(m.group(2)) | |
138 new_line_num = int(m.group(3)) | |
139 new_size = int(m.group(4)) | |
140 line_num = new_line_num | |
141 skip_chunk = False | |
142 if line_num >= self.enum_end: | |
143 skip_chunk = True | |
144 elif line_num + new_size <= self.enum_start: | |
145 skip_chunk = True | |
146 # This line number applies for the next input line, so subtract 1. | |
147 line_num -= 1 | |
148 continue | |
149 | |
150 if skip_chunk: | |
151 continue | |
152 | |
153 if not line.startswith("-"): | |
154 line_num += 1 | |
155 | |
156 if line_num >= self.enum_end: | |
157 break | |
158 | |
159 self.LogDebug("Analysing line '" + line + "'") | |
160 | |
161 if line_num == self.enum_start: | |
162 self.LogDebug("Found enum start.") | |
163 | |
164 if line_num >= self.enum_start: | |
165 if not old_enum_start_found: | |
166 self.LogDebug("old_enum_start_found is true because " | |
167 "line_num >= self.enum_start") | |
168 old_enum_start_found = True | |
169 else: | |
170 m = self.input_api.re.match( | |
171 r"^([+ -])<enum name=\"LoginCustomFlags\"", line) | |
172 if m: | |
173 if m.group(1) == "-" : | |
174 old_enum_start_found = True | |
175 self.LogDebug("old_enum_start_found is true because " | |
176 "removal of old enum found.") | |
177 else: | |
178 # Raise exception if enum LoginCustomFlags found added (or not | |
179 # modified) at the line number before it was found in current | |
180 # histograms.xml version. | |
181 raise Exception("Error parsing changes to histograms.xml:\n" | |
182 "Diff line '" + line + "' unexpected."); | |
183 | |
184 if not old_enum_start_found: | |
185 continue | |
186 | |
187 m = self.input_api.re.match(r"^- *</enum", line) | |
188 if m: | |
189 old_enum_end_found = True | |
190 continue | |
191 | |
192 # Ignore unchanged items here. | |
193 m = self.input_api.re.match( | |
194 r"^([+-]) *<int .*label *= *\"?([^\"]+)", line) | |
195 | |
196 if not m: | |
197 continue | |
198 | |
199 self.LogDebug("MATCHED '" + line + "'") | |
200 | |
201 if m.group(1) == "+" and line_num > self.enum_start and \ | |
202 line_num < self.enum_end: | |
203 added_labels.append(m.group(2)) | |
204 elif m.group(1) == "-" and old_enum_start_found and \ | |
205 not old_enum_end_found: | |
206 removed_labels.append(m.group(2)) | |
207 | |
208 self.LogDebug("removed_labels: " + str(removed_labels)) | |
209 self.LogDebug("added_labels: " + str(added_labels)) | |
210 | |
211 removed_completely = [] | |
212 for label in removed_labels: | |
213 if label not in added_labels: | |
214 removed_completely.append(label) | |
215 if len(removed_completely): | |
216 self.ReportDeletedLabels(removed_completely) | |
217 | |
218 return False if len(removed_completely) else True | |
219 | |
220 def PerformChecks(self, affected_file): | |
221 (self.enum_start, self.enum_end) = \ | |
222 self.ExtractFistAndLasteLoginCustomFlagsLineNumberFromHistograms() | |
223 self.LogDebug("enum first-last lines are:" | |
224 + str((self.enum_start, self.enum_end))) | |
225 | |
226 if len(self.results) == 0: | |
227 self.CheckDiff(affected_file) | |
228 | |
229 def Run(self): | |
230 for file in self.input_api.AffectedFiles(include_deletes=True): | |
231 if self.input_api.basename(file.LocalPath()) == \ | |
232 self.input_api.basename(self.path): | |
233 self.LogInfo("LoginCustomFlagsChecker: Start processing file.") | |
234 self.PerformChecks(file) | |
235 self.LogInfo("LoginCustomFlagsChecker: Done processing file.") | |
236 return self.results | |
Ilya Sherman
2014/08/05 20:14:47
IMO this presubmit script is not needed now that y
Alexei Svitkine (slow)
2014/08/05 20:47:52
Don't we want to have a presubmit that ensures tha
Ilya Sherman
2014/08/05 20:50:36
Alex added a unit test that does this. According
Alexander Alekseev
2014/08/07 23:24:56
Unit test cannot check that values are not removed
Ilya Sherman
2014/08/08 03:49:45
(a) None of the reviewers of this file would be li
Alexander Alekseev
2014/08/09 01:30:01
Done.
| |
OLD | NEW |