diff options
Diffstat (limited to 'catapult/common/py_utils/py_utils/expectations_parser.py')
-rw-r--r-- | catapult/common/py_utils/py_utils/expectations_parser.py | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/catapult/common/py_utils/py_utils/expectations_parser.py b/catapult/common/py_utils/py_utils/expectations_parser.py new file mode 100644 index 00000000..6fa94070 --- /dev/null +++ b/catapult/common/py_utils/py_utils/expectations_parser.py @@ -0,0 +1,124 @@ +# Copyright 2017 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. + +import re + + +class ParseError(Exception): + pass + + +class Expectation(object): + def __init__(self, reason, test, conditions, results): + """Constructor for expectations. + + Args: + reason: String that indicates the reason for disabling. + test: String indicating which test is being disabled. + conditions: List of tags indicating which conditions to disable for. + Conditions are combined using logical and. Example: ['Mac', 'Debug'] + results: List of outcomes for test. Example: ['Skip', 'Pass'] + """ + assert isinstance(reason, basestring) or reason is None + self._reason = reason + assert isinstance(test, basestring) + self._test = test + assert isinstance(conditions, list) + self._conditions = conditions + assert isinstance(results, list) + self._results = results + + def __eq__(self, other): + return (self.reason == other.reason and + self.test == other.test and + self.conditions == other.conditions and + self.results == other.results) + + @property + def reason(self): + return self._reason + + @property + def test(self): + return self._test + + @property + def conditions(self): + return self._conditions + + @property + def results(self): + return self._results + + +class TestExpectationParser(object): + """Parse expectations data in TA/DA format. + + This parser covers the 'tagged' test lists format in: + bit.ly/chromium-test-list-format + + Takes raw expectations data as a string read from the TA/DA expectation file + in the format: + + # This is an example expectation file. + # + # tags: Mac Mac10.10 Mac10.11 + # tags: Win Win8 + + crbug.com/123 [ Win ] benchmark/story [ Skip ] + ... + """ + + TAG_TOKEN = '# tags:' + _MATCH_STRING = r'^(?:(crbug.com/\d+) )?' # The bug field (optional). + _MATCH_STRING += r'(?:\[ (.+) \] )?' # The label field (optional). + _MATCH_STRING += r'(\S+) ' # The test path field. + _MATCH_STRING += r'\[ ([^\[.]+) \]' # The expectation field. + _MATCH_STRING += r'(\s+#.*)?$' # End comment (optional). + MATCHER = re.compile(_MATCH_STRING) + + def __init__(self, raw_data): + self._tags = [] + self._expectations = [] + self._ParseRawExpectationData(raw_data) + + def _ParseRawExpectationData(self, raw_data): + for count, line in list(enumerate(raw_data.splitlines(), start=1)): + # Handle metadata and comments. + if line.startswith(self.TAG_TOKEN): + for word in line[len(self.TAG_TOKEN):].split(): + # Expectations must be after all tags are declared. + if self._expectations: + raise ParseError('Tag found after first expectation.') + self._tags.append(word) + elif line.startswith('#') or not line: + continue # Ignore, it is just a comment or empty. + else: + self._expectations.append( + self._ParseExpectationLine(count, line, self._tags)) + + def _ParseExpectationLine(self, line_number, line, tags): + match = self.MATCHER.match(line) + if not match: + raise ParseError( + 'Expectation has invalid syntax on line %d: %s' + % (line_number, line)) + # Unused group is optional trailing comment. + reason, raw_conditions, test, results, _ = match.groups() + conditions = [c for c in raw_conditions.split()] if raw_conditions else [] + + for c in conditions: + if c not in tags: + raise ParseError( + 'Condition %s not found in expectations tag data. Line %d' + % (c, line_number)) + return Expectation(reason, test, conditions, [r for r in results.split()]) + + @property + def expectations(self): + return self._expectations + + @property + def tags(self): + return self._tags |