struct.py revision 711411957a433555eda4bcf8d1f05aabf04425e8
1# Copyright (C) 2014 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#   http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from common.logger import Logger
16from common.mixins import EqualityMixin, PrintableMixin
17
18import re
19
20class CheckerFile(PrintableMixin):
21
22  def __init__(self, fileName):
23    self.fileName = fileName
24    self.testCases = []
25
26  def addTestCase(self, new_test_case):
27    self.testCases.append(new_test_case)
28
29  def __eq__(self, other):
30    return isinstance(other, self.__class__) \
31       and self.testCases == other.testCases
32
33
34class TestCase(PrintableMixin):
35
36  def __init__(self, parent, name, startLineNo):
37    assert isinstance(parent, CheckerFile)
38
39    self.parent = parent
40    self.name = name
41    self.assertions = []
42    self.startLineNo = startLineNo
43
44    if not self.name:
45      Logger.fail("Test case does not have a name", self.fileName, self.startLineNo)
46
47    self.parent.addTestCase(self)
48
49  @property
50  def fileName(self):
51    return self.parent.fileName
52
53  def addAssertion(self, new_assertion):
54    if new_assertion.variant == TestAssertion.Variant.NextLine:
55      if not self.assertions or \
56         (self.assertions[-1].variant != TestAssertion.Variant.InOrder and \
57          self.assertions[-1].variant != TestAssertion.Variant.NextLine):
58        Logger.fail("A next-line assertion can only be placed after an "
59                    "in-order assertion or another next-line assertion.",
60                    new_assertion.fileName, new_assertion.lineNo)
61    self.assertions.append(new_assertion)
62
63  def __eq__(self, other):
64    return isinstance(other, self.__class__) \
65       and self.name == other.name \
66       and self.assertions == other.assertions
67
68
69class TestAssertion(PrintableMixin):
70
71  class Variant(object):
72    """Supported types of assertions."""
73    InOrder, NextLine, DAG, Not = range(4)
74
75  def __init__(self, parent, variant, originalText, lineNo):
76    assert isinstance(parent, TestCase)
77
78    self.parent = parent
79    self.variant = variant
80    self.expressions = []
81    self.lineNo = lineNo
82    self.originalText = originalText
83
84    self.parent.addAssertion(self)
85
86  @property
87  def fileName(self):
88    return self.parent.fileName
89
90  def addExpression(self, new_expression):
91    assert isinstance(new_expression, RegexExpression)
92    if self.variant == TestAssertion.Variant.Not:
93      if new_expression.variant == RegexExpression.Variant.VarDef:
94        Logger.fail("CHECK-NOT lines cannot define variables", self.fileName, self.lineNo)
95    self.expressions.append(new_expression)
96
97  def toRegex(self):
98    """ Returns a regex pattern for this entire assertion. Only used in tests. """
99    regex = ""
100    for expression in self.expressions:
101      if expression.variant == RegexExpression.Variant.Separator:
102        regex = regex + ", "
103      else:
104        regex = regex + "(" + expression.pattern + ")"
105    return regex
106
107  def __eq__(self, other):
108    return isinstance(other, self.__class__) \
109       and self.variant == other.variant \
110       and self.expressions == other.expressions
111
112
113class RegexExpression(EqualityMixin, PrintableMixin):
114
115  class Variant(object):
116    """Supported language constructs."""
117    Text, Pattern, VarRef, VarDef, Separator = range(5)
118
119  class Regex(object):
120    rName = r"([a-zA-Z][a-zA-Z0-9]*)"
121    rRegex = r"(.+?)"
122    rPatternStartSym = r"(\{\{)"
123    rPatternEndSym = r"(\}\})"
124    rVariableStartSym = r"(<<)"
125    rVariableEndSym = r"(>>)"
126    rVariableSeparator = r"(:)"
127
128    regexPattern = rPatternStartSym + rRegex + rPatternEndSym
129    regexVariableReference = rVariableStartSym + rName + rVariableEndSym
130    regexVariableDefinition = rVariableStartSym + rName + rVariableSeparator + rRegex + rVariableEndSym
131
132  def __init__(self, variant, name, pattern):
133    self.variant = variant
134    self.name = name
135    self.pattern = pattern
136
137  def __eq__(self, other):
138    return isinstance(other, self.__class__) \
139       and self.variant == other.variant \
140       and self.name == other.name \
141       and self.pattern == other.pattern
142
143  @staticmethod
144  def createSeparator():
145    return RegexExpression(RegexExpression.Variant.Separator, None, None)
146
147  @staticmethod
148  def createText(text):
149    return RegexExpression(RegexExpression.Variant.Text, None, re.escape(text))
150
151  @staticmethod
152  def createPattern(pattern):
153    return RegexExpression(RegexExpression.Variant.Pattern, None, pattern)
154
155  @staticmethod
156  def createVariableReference(name):
157    assert re.match(RegexExpression.Regex.rName, name)
158    return RegexExpression(RegexExpression.Variant.VarRef, name, None)
159
160  @staticmethod
161  def createVariableDefinition(name, pattern):
162    assert re.match(RegexExpression.Regex.rName, name)
163    return RegexExpression(RegexExpression.Variant.VarDef, name, pattern)
164