Index: tools/metrics/histograms/verify_enum_custom_flags.py |
diff --git a/tools/metrics/histograms/verify_enum_custom_flags.py b/tools/metrics/histograms/verify_enum_custom_flags.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d55092f5cf49dd25ce1fc21d6949581738d29b83 |
--- /dev/null |
+++ b/tools/metrics/histograms/verify_enum_custom_flags.py |
@@ -0,0 +1,236 @@ |
+# Copyright 2014 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Check enum LoginCustomFlags .""" |
+ |
+class LoginCustomFlagsChecker(object): |
+ """Verify that changes to enum LoginCustomFlags in histograms.xml are valid. |
+ |
+ This class is used to check that LoginCustomFlags enum is never shrinked. |
+ |
+ Note: this check would probably fail if enum is completely moved to |
+ another part of histograms.xml, but it works for changes inside enum. |
+ """ |
+ def __init__(self, input_api, output_api, path): |
+ self.input_api = input_api |
+ self.output_api = output_api |
+ self.path = path |
+ self.warnings_exist = False |
+ self.enum_start = 0 |
+ self.enum_end = 0 |
+ self.results = [] |
+ |
+ def LogInfo(self, message): |
+ self.input_api.logging.info(message) |
+ return |
+ |
+ def LogDebug(self, message): |
+ self.input_api.logging.debug(message) |
+ return |
+ |
+ def GetLongMessage(self, local_path): |
+ return str("The file \"%s\"\n" |
+ "contains the definition of the enum LoginCustomFlags,\n" |
+ "which should never be shrinked or removed.\n" |
+ % (local_path)) |
+ |
+ def EmitWarning(self, message, line_number=None, line_text=None): |
+ """Emits a presubmit prompt warning containing the short message |
+ |message|. |item| is |LOCAL_PATH| with optional |line_number| and |
+ |line_text|. |
+ |
+ """ |
+ if line_number is not None and line_text is not None: |
+ item = "%s(%d): %s" % (self.path, line_number, line_text) |
+ elif line_number is not None: |
+ item = "%s(%d)" % (self.path, line_number) |
+ else: |
+ item = self.path |
+ if self.warnings_exist : |
+ long_message = '' |
+ else : |
+ long_message = self.GetLongMessage(self.path) |
+ self.warnings_exist = True |
+ |
+ self.LogInfo(message) |
+ self.results.append( |
+ self.output_api.PresubmitPromptWarning(message, [item], long_message)) |
+ |
+ def ReportDeletedLabels(self, removed_labels, prefix=''): |
+ message = prefix |
+ message += str("It looks like you are deleting entries from the " |
+ "enum definition. This should never happen.\n") |
+ message += "The following labels have been deleted:\n" |
+ for value in removed_labels : |
+ message += "\t" + value + "\n" |
+ self.EmitWarning(message) |
+ |
+ def ExtractFistAndLasteLoginCustomFlagsLineNumberFromHistograms(self): |
+ """Returns tuple (first line number, last line number) in the new version. |
+ Line number starts with 1. |
+ """ |
+ import os |
+ with open(self.path, 'r') as file_handle: |
+ line_number = 0 |
+ enum_start = 0 |
+ enum_end = 0 |
+ for line in file_handle: |
+ line_number = line_number + 1 |
+ if self.input_api.re.match(r"^<enum name=\"LoginCustomFlags\"", line): |
+ if enum_start > 0: |
+ error = str("Bad '" + str(xml_file) + "' format: start of enum " |
+ "LoginCustomFlags found twice: at lines " |
+ + str(enum_start) + " and " + str(line_number) |
+ + ".") |
+ self.EmitWarning(error, line_number, line) |
+ return |
+ enum_start = line_number |
+ elif enum_start > 0 and self.input_api.re.match(r"^ *<enum", line): |
+ error = str("Bad '" + str(xml_file) + "' format: end of enum " |
+ "LoginCustomFlags not found. Found new enum at line " |
+ + str(line_number) + ":\n" + line) |
+ self.EmitWarning(error, line_number, line) |
+ return |
+ elif enum_start > 0 and self.input_api.re.match(r"^</enum", line): |
+ enum_end = line_number |
+ break |
+ |
+ if enum_start > 0 and enum_end > enum_start: |
+ return (enum_start, enum_end) |
+ |
+ if enum_start == 0: |
+ error = str("Bad '" + str(xml_file) + "' format: start of enum " |
+ "LoginCustomFlags is not found.") |
+ self.EmitWarning(error, line_number, line) |
+ return |
+ |
+ if enum_end == 0: |
+ error = str("Bad '" + str(xml_file) + "' format: end of enum " |
+ "LoginCustomFlags is not found.") |
+ self.EmitWarning(error, line_number, line) |
+ return |
+ |
+ if enum_start == enum_end: |
+ error = str("Bad '" + str(xml_file) + "' format: end of enum " |
+ "LoginCustomFlags is empty.") |
+ self.EmitWarning(error, line_number, line) |
+ return |
+ |
+ raise Exception("Not reached.") |
+ |
+ def CheckDiff(self, affected_file): |
+ """Check diff is valid.""" |
+ added_labels = [] |
+ removed_labels = [] |
+ skip_chunk = True |
+ line_num = 0 |
+ old_enum_start_found = False |
+ old_enum_end_found = False |
+ for line in affected_file.GenerateScmDiff().splitlines(): |
+ # Parse the unified diff chunk optional section heading, which looks like |
+ # @@ -l,s +l,s @@ optional section heading |
+ m = self.input_api.re.match( |
+ r"^@@ \-([0-9]+)\,([0-9]+) \+([0-9]+)\,([0-9]+) @@", line) |
+ if m: |
+ old_line_num = int(m.group(1)) |
+ old_size = int(m.group(2)) |
+ new_line_num = int(m.group(3)) |
+ new_size = int(m.group(4)) |
+ line_num = new_line_num |
+ skip_chunk = False |
+ if line_num >= self.enum_end: |
+ skip_chunk = True |
+ elif line_num + new_size <= self.enum_start: |
+ skip_chunk = True |
+ # This line number applies for the next input line, so subtract 1. |
+ line_num -= 1 |
+ continue |
+ |
+ if skip_chunk: |
+ continue |
+ |
+ if not line.startswith("-"): |
+ line_num += 1 |
+ |
+ if line_num >= self.enum_end: |
+ break |
+ |
+ self.LogDebug("Analysing line '" + line + "'") |
+ |
+ if line_num == self.enum_start: |
+ self.LogDebug("Found enum start.") |
+ |
+ if line_num >= self.enum_start: |
+ if not old_enum_start_found: |
+ self.LogDebug("old_enum_start_found is true because " |
+ "line_num >= self.enum_start") |
+ old_enum_start_found = True |
+ else: |
+ m = self.input_api.re.match( |
+ r"^([+ -])<enum name=\"LoginCustomFlags\"", line) |
+ if m: |
+ if m.group(1) == "-" : |
+ old_enum_start_found = True |
+ self.LogDebug("old_enum_start_found is true because " |
+ "removal of old enum found.") |
+ else: |
+ # Raise exception if enum LoginCustomFlags found added (or not |
+ # modified) at the line number before it was found in current |
+ # histograms.xml version. |
+ raise Exception("Error parsing changes to histograms.xml:\n" |
+ "Diff line '" + line + "' unexpected."); |
+ |
+ if not old_enum_start_found: |
+ continue |
+ |
+ m = self.input_api.re.match(r"^- *</enum", line) |
+ if m: |
+ old_enum_end_found = True |
+ continue |
+ |
+ # Ignore unchanged items here. |
+ m = self.input_api.re.match( |
+ r"^([+-]) *<int .*label *= *\"?([^\"]+)", line) |
+ |
+ if not m: |
+ continue |
+ |
+ self.LogDebug("MATCHED '" + line + "'") |
+ |
+ if m.group(1) == "+" and line_num > self.enum_start and \ |
+ line_num < self.enum_end: |
+ added_labels.append(m.group(2)) |
+ elif m.group(1) == "-" and old_enum_start_found and \ |
+ not old_enum_end_found: |
+ removed_labels.append(m.group(2)) |
+ |
+ self.LogDebug("removed_labels: " + str(removed_labels)) |
+ self.LogDebug("added_labels: " + str(added_labels)) |
+ |
+ removed_completely = [] |
+ for label in removed_labels: |
+ if label not in added_labels: |
+ removed_completely.append(label) |
+ if len(removed_completely): |
+ self.ReportDeletedLabels(removed_completely) |
+ |
+ return False if len(removed_completely) else True |
+ |
+ def PerformChecks(self, affected_file): |
+ (self.enum_start, self.enum_end) = \ |
+ self.ExtractFistAndLasteLoginCustomFlagsLineNumberFromHistograms() |
+ self.LogDebug("enum first-last lines are:" |
+ + str((self.enum_start, self.enum_end))) |
+ |
+ if len(self.results) == 0: |
+ self.CheckDiff(affected_file) |
+ |
+ def Run(self): |
+ for file in self.input_api.AffectedFiles(include_deletes=True): |
+ if self.input_api.basename(file.LocalPath()) == \ |
+ self.input_api.basename(self.path): |
+ self.LogInfo("LoginCustomFlagsChecker: Start processing file.") |
+ self.PerformChecks(file) |
+ self.LogInfo("LoginCustomFlagsChecker: Done processing file.") |
+ 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.
|