| 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
|
|
|