Commit e109f456 authored by Caitlin Fischer's avatar Caitlin Fischer Committed by Commit Bot

Improves the detection of poorly-formatted <owner> tags.

Bug: 988089
Change-Id: I69bcbcbf3bd18181db15304711f8b17ce8104eed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1721083Reviewed-by: default avatarRobert Kaplow <rkaplow@chromium.org>
Commit-Queue: Caitlin Fischer <caitlinfischer@google.com>
Cr-Commit-Position: refs/heads/master@{#682360}
parent 57ec7ab6
...@@ -61,7 +61,9 @@ import logging ...@@ -61,7 +61,9 @@ import logging
import re import re
import xml.dom.minidom import xml.dom.minidom
OWNER_FIELD_PLACEHOLDER = ( BASIC_EMAIL_REGEXP = r'^[\w\-\+\%\.]+\@[\w\-\+\%\.]+$'
OWNER_PLACEHOLDER = (
'Please list the metric\'s owners. Add more owner tags as needed.') 'Please list the metric\'s owners. Add more owner tags as needed.')
MAX_HISTOGRAM_SUFFIX_DEPENDENCY_DEPTH = 5 MAX_HISTOGRAM_SUFFIX_DEPENDENCY_DEPTH = 5
...@@ -72,12 +74,13 @@ DEFAULT_BASE_HISTOGRAM_OBSOLETE_REASON = ( ...@@ -72,12 +74,13 @@ DEFAULT_BASE_HISTOGRAM_OBSOLETE_REASON = (
EXPIRY_DATE_PATTERN = "%Y-%m-%d" EXPIRY_DATE_PATTERN = "%Y-%m-%d"
EXPIRY_MILESTONE_RE = re.compile(r'M[0-9]{2,3}\Z') EXPIRY_MILESTONE_RE = re.compile(r'M[0-9]{2,3}\Z')
class Error(Exception): class Error(Exception):
pass pass
def _JoinChildNodes(tag): def _JoinChildNodes(tag):
"""Join child nodes into a single text. """Joins child nodes into a single text.
Applicable to leafs like 'summary' and 'detail'. Removes any comment in the Applicable to leafs like 'summary' and 'detail'. Removes any comment in the
node. node.
...@@ -94,7 +97,7 @@ def _JoinChildNodes(tag): ...@@ -94,7 +97,7 @@ def _JoinChildNodes(tag):
def _NormalizeString(s): def _NormalizeString(s):
r"""Replaces all whitespace sequences with a single space. """Replaces all whitespace sequences with a single space.
The function properly handles multi-line strings and XML escaped characters. The function properly handles multi-line strings and XML escaped characters.
...@@ -188,7 +191,7 @@ def _ExpandHistogramNameWithSuffixes(suffix_name, histogram_name, ...@@ -188,7 +191,7 @@ def _ExpandHistogramNameWithSuffixes(suffix_name, histogram_name,
def ExtractEnumsFromXmlTree(tree): def ExtractEnumsFromXmlTree(tree):
"""Extract all <enum> nodes in the tree into a dictionary.""" """Extracts all <enum> nodes in the tree into a dictionary."""
enums = {} enums = {}
have_errors = False have_errors = False
...@@ -251,31 +254,38 @@ def ExtractEnumsFromXmlTree(tree): ...@@ -251,31 +254,38 @@ def ExtractEnumsFromXmlTree(tree):
return enums, have_errors return enums, have_errors
def _ExtractOwners(xml_node): def _ExtractOwners(histogram):
"""Extract owners information from owner tag under |xml_node|. """Extracts owners information from the given histogram element.
Args: Args:
xml_node: The histogram node in histograms.xml. histogram: A DOM Element corresponding to a histogram.
Returns: Returns:
A tuple of owners information where the first element is a list of owners A tuple of owner-related info, e.g. (['alice@chromium.org'], True)
extract from |xml_node| excluding the owner placeholder string, and the
second element is whether the owner tag is presented in |xml_node| The first element is a list of the owners' email addresses, excluding the
including the owner placeholder string. owner placeholder string. The second element is a boolean indicating
whether the histogram has an owner. A histogram whose owner is the owner
placeholder string has an owner.
""" """
email_pattern = re.compile(BASIC_EMAIL_REGEXP)
owners = [] owners = []
hasOwner = False has_owner = False
for owner_node in xml_node.getElementsByTagName('owner'):
owner_entry = _NormalizeString(_JoinChildNodes(owner_node)) for owner_node in histogram.getElementsByTagName('owner'):
hasOwner = True owner_text = _NormalizeString(_JoinChildNodes(owner_node))
if OWNER_FIELD_PLACEHOLDER not in owner_entry: is_email = email_pattern.match(owner_text)
owners.append(owner_entry)
return owners, hasOwner if owner_text and (is_email or OWNER_PLACEHOLDER in owner_text):
has_owner = True
if is_email:
owners.append(owner_text)
return owners, has_owner
def _ValidateDateString(date_str): def _ValidateDateString(date_str):
"""Check if |date_str| matches 'YYYY-MM-DD'. """Checks if |date_str| matches 'YYYY-MM-DD'.
Args: Args:
date_str: string date_str: string
...@@ -302,7 +312,7 @@ def _ProcessBaseHistogramAttribute(node, histogram_entry): ...@@ -302,7 +312,7 @@ def _ProcessBaseHistogramAttribute(node, histogram_entry):
def _ExtractHistogramsFromXmlTree(tree, enums): def _ExtractHistogramsFromXmlTree(tree, enums):
"""Extract all <histogram> nodes in the tree into a dictionary.""" """Extracts all <histogram> nodes in the tree into a dictionary."""
# Process the histograms. The descriptions can include HTML tags. # Process the histograms. The descriptions can include HTML tags.
histograms = {} histograms = {}
...@@ -387,9 +397,14 @@ def _ExtractHistogramsFromXmlTree(tree, enums): ...@@ -387,9 +397,14 @@ def _ExtractHistogramsFromXmlTree(tree, enums):
return histograms, have_errors return histograms, have_errors
# Finds an <obsolete> node amongst |node|'s immediate children and returns its
# content as a string. Returns None if no such node exists.
def _GetObsoleteReason(node): def _GetObsoleteReason(node):
"""If the node's histogram is obsolete, returns a string explanation.
Otherwise, returns None.
Args:
node: A DOM Element associated with a histogram.
"""
for child in node.childNodes: for child in node.childNodes:
if child.localName == 'obsolete': if child.localName == 'obsolete':
# There can be at most 1 obsolete element per node. # There can be at most 1 obsolete element per node.
...@@ -398,7 +413,7 @@ def _GetObsoleteReason(node): ...@@ -398,7 +413,7 @@ def _GetObsoleteReason(node):
def _UpdateHistogramsWithSuffixes(tree, histograms): def _UpdateHistogramsWithSuffixes(tree, histograms):
"""Process <histogram_suffixes> tags and combine with affected histograms. """Processes <histogram_suffixes> tags and combines with affected histograms.
The histograms dictionary will be updated in-place by adding new histograms The histograms dictionary will be updated in-place by adding new histograms
created by combining histograms themselves with histogram_suffixes targeting created by combining histograms themselves with histogram_suffixes targeting
...@@ -551,7 +566,7 @@ def _UpdateHistogramsWithSuffixes(tree, histograms): ...@@ -551,7 +566,7 @@ def _UpdateHistogramsWithSuffixes(tree, histograms):
def ExtractHistogramsFromDom(tree): def ExtractHistogramsFromDom(tree):
"""Compute the histogram names and descriptions from the XML representation. """Computes the histogram names and descriptions from the XML representation.
Args: Args:
tree: A DOM tree of XML content. tree: A DOM tree of XML content.
...@@ -571,7 +586,7 @@ def ExtractHistogramsFromDom(tree): ...@@ -571,7 +586,7 @@ def ExtractHistogramsFromDom(tree):
def ExtractHistograms(filename): def ExtractHistograms(filename):
"""Load histogram definitions from a disk file. """Loads histogram definitions from a disk file.
Args: Args:
filename: a file path to load data from. filename: a file path to load data from.
......
...@@ -21,22 +21,67 @@ class ExtractHistogramsTest(unittest.TestCase): ...@@ -21,22 +21,67 @@ class ExtractHistogramsTest(unittest.TestCase):
</histograms> </histograms>
</histogram-configuration> </histogram-configuration>
""") """)
(_, have_errors) = extract_histograms._ExtractHistogramsFromXmlTree( _, have_errors = extract_histograms._ExtractHistogramsFromXmlTree(
histogram_without_summary, {}) histogram_without_summary, {})
self.assertTrue(have_errors) self.assertTrue(have_errors)
def testNewHistogramWithoutOwner(self): def testNewHistogramWithEmptyOwnerTag(self):
histogram_without_owner = xml.dom.minidom.parseString(""" histogram_with_empty_owner_tag = xml.dom.minidom.parseString("""
<histogram-configuration> <histogram-configuration>
<histograms> <histograms>
<histogram name="Test.Histogram" units="things"> <histogram name="Test.Histogram" units="things">
<owner></owner>
<summary> This is a summary </summary> <summary> This is a summary </summary>
</histogram> </histogram>
</histograms> </histograms>
</histogram-configuration> </histogram-configuration>
""") """)
(_, have_errors) = extract_histograms._ExtractHistogramsFromXmlTree( _, have_errors = extract_histograms._ExtractHistogramsFromXmlTree(
histogram_without_owner, {}) histogram_with_empty_owner_tag, {})
self.assertTrue(have_errors)
def testNewHistogramWithoutOwnerTag(self):
histogram_without_owner_tag = xml.dom.minidom.parseString("""
<histogram-configuration>
<histograms>
<histogram name="Test.Histogram" units="things">
<summary> This is a summary </summary>
</histogram>
</histograms>
</histogram-configuration>
""")
_, have_errors = extract_histograms._ExtractHistogramsFromXmlTree(
histogram_without_owner_tag, {})
self.assertTrue(have_errors)
def testNewHistogramWithCommaSeparatedOwners(self):
histogram_with_comma_separated_owners = xml.dom.minidom.parseString("""
<histogram-configuration>
<histograms>
<histogram name="Test.Histogram" units="things">
<owner>cait@chromium.org, paul@chromium.org</owner>
<summary> This is a summary </summary>
</histogram>
</histograms>
</histogram-configuration>
""")
_, have_errors = extract_histograms._ExtractHistogramsFromXmlTree(
histogram_with_comma_separated_owners, {})
self.assertTrue(have_errors)
def testNewHistogramWithInvalidOwner(self):
histogram_with_invalid_owner = xml.dom.minidom.parseString("""
<histogram-configuration>
<histograms>
<histogram name="Test.Histogram" units="things">
<owner>sarah</owner>
<summary> This is a summary </summary>
</histogram>
</histograms>
</histogram-configuration>
""")
_, have_errors = extract_histograms._ExtractHistogramsFromXmlTree(
histogram_with_invalid_owner, {})
self.assertTrue(have_errors) self.assertTrue(have_errors)
def testNewHistogramWithOwnerPlaceHolder(self): def testNewHistogramWithOwnerPlaceHolder(self):
...@@ -55,7 +100,7 @@ class ExtractHistogramsTest(unittest.TestCase): ...@@ -55,7 +100,7 @@ class ExtractHistogramsTest(unittest.TestCase):
</histograms> </histograms>
</histogram-configuration> </histogram-configuration>
""") """)
(_, have_errors) = extract_histograms._ExtractHistogramsFromXmlTree( _, have_errors = extract_histograms._ExtractHistogramsFromXmlTree(
histogram_with_owner_placeholder, {}) histogram_with_owner_placeholder, {})
self.assertFalse(have_errors) self.assertFalse(have_errors)
...@@ -71,7 +116,7 @@ class ExtractHistogramsTest(unittest.TestCase): ...@@ -71,7 +116,7 @@ class ExtractHistogramsTest(unittest.TestCase):
</histograms> </histograms>
</histogram-configuration> </histogram-configuration>
""") """)
(hists, have_errors) = extract_histograms._ExtractHistogramsFromXmlTree( hists, have_errors = extract_histograms._ExtractHistogramsFromXmlTree(
histogram_with_owner_placeholder, {}) histogram_with_owner_placeholder, {})
self.assertFalse(have_errors) self.assertFalse(have_errors)
self.assertIn('Test.Histogram', hists) self.assertIn('Test.Histogram', hists)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment