Home | History | Annotate | Download | only in py_utils
      1 # Copyright 2017 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 import re
      6 
      7 
      8 class ParseError(Exception):
      9   pass
     10 
     11 
     12 class Expectation(object):
     13   def __init__(self, reason, test, conditions, results):
     14     """Constructor for expectations.
     15 
     16     Args:
     17       reason: String that indicates the reason for disabling.
     18       test: String indicating which test is being disabled.
     19       conditions: List of tags indicating which conditions to disable for.
     20           Conditions are combined using logical and. Example: ['Mac', 'Debug']
     21       results: List of outcomes for test. Example: ['Skip', 'Pass']
     22     """
     23     assert isinstance(reason, basestring) or reason is None
     24     self._reason = reason
     25     assert isinstance(test, basestring)
     26     self._test = test
     27     assert isinstance(conditions, list)
     28     self._conditions = conditions
     29     assert isinstance(results, list)
     30     self._results = results
     31 
     32   def __eq__(self, other):
     33     return (self.reason == other.reason and
     34             self.test == other.test and
     35             self.conditions == other.conditions and
     36             self.results == other.results)
     37 
     38   @property
     39   def reason(self):
     40     return self._reason
     41 
     42   @property
     43   def test(self):
     44     return self._test
     45 
     46   @property
     47   def conditions(self):
     48     return self._conditions
     49 
     50   @property
     51   def results(self):
     52     return self._results
     53 
     54 
     55 class TestExpectationParser(object):
     56   """Parse expectations data in TA/DA format.
     57 
     58   This parser covers the 'tagged' test lists format in:
     59       bit.ly/chromium-test-list-format
     60 
     61   Takes raw expectations data as a string read from the TA/DA expectation file
     62   in the format:
     63 
     64     # This is an example expectation file.
     65     #
     66     # tags: Mac Mac10.10 Mac10.11
     67     # tags: Win Win8
     68 
     69     crbug.com/123 [ Win ] benchmark/story [ Skip ]
     70     ...
     71   """
     72 
     73   TAG_TOKEN = '# tags:'
     74   _MATCH_STRING = r'^(?:(crbug.com/\d+) )?'  # The bug field (optional).
     75   _MATCH_STRING += r'(?:\[ (.+) \] )?' # The label field (optional).
     76   _MATCH_STRING += r'(\S+) ' # The test path field.
     77   _MATCH_STRING += r'\[ ([^\[.]+) \]'  # The expectation field.
     78   _MATCH_STRING += r'(\s+#.*)?$' # End comment (optional).
     79   MATCHER = re.compile(_MATCH_STRING)
     80 
     81   def __init__(self, raw_data):
     82     self._tags = []
     83     self._expectations = []
     84     self._ParseRawExpectationData(raw_data)
     85 
     86   def _ParseRawExpectationData(self, raw_data):
     87     for count, line in list(enumerate(raw_data.splitlines(), start=1)):
     88       # Handle metadata and comments.
     89       if line.startswith(self.TAG_TOKEN):
     90         for word in line[len(self.TAG_TOKEN):].split():
     91           # Expectations must be after all tags are declared.
     92           if self._expectations:
     93             raise ParseError('Tag found after first expectation.')
     94           self._tags.append(word)
     95       elif line.startswith('#') or not line:
     96         continue  # Ignore, it is just a comment or empty.
     97       else:
     98         self._expectations.append(
     99             self._ParseExpectationLine(count, line, self._tags))
    100 
    101   def _ParseExpectationLine(self, line_number, line, tags):
    102     match = self.MATCHER.match(line)
    103     if not match:
    104       raise ParseError(
    105           'Expectation has invalid syntax on line %d: %s'
    106           % (line_number, line))
    107     # Unused group is optional trailing comment.
    108     reason, raw_conditions, test, results, _ = match.groups()
    109     conditions = [c for c in raw_conditions.split()] if raw_conditions else []
    110 
    111     for c in conditions:
    112       if c not in tags:
    113         raise ParseError(
    114             'Condition %s not found in expectations tag data. Line %d'
    115             % (c, line_number))
    116     return Expectation(reason, test, conditions, [r for r in results.split()])
    117 
    118   @property
    119   def expectations(self):
    120     return self._expectations
    121 
    122   @property
    123   def tags(self):
    124     return self._tags
    125