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