blob: b22211ab56c46e554186d1d0692ba8ecff4a4094 [file] [log] [blame]
David Brazdil2c27f2c2015-05-12 18:06:38 +01001# 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
David Brazdil6423cf52015-05-20 14:57:54 +010015from collections import namedtuple
David Brazdilc4de9432015-05-20 11:03:22 +010016from common.immutables import ImmutableDict
David Brazdil2c27f2c2015-05-12 18:06:38 +010017from common.logger import Logger
18from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
19from file_format.checker.struct import CheckerFile, TestCase, TestAssertion
20from match.line import MatchLines
21
David Brazdil6423cf52015-05-20 14:57:54 +010022MatchScope = namedtuple("MatchScope", ["start", "end"])
23MatchInfo = namedtuple("MatchInfo", ["scope", "variables"])
David Brazdil2c27f2c2015-05-12 18:06:38 +010024
David Brazdil6423cf52015-05-20 14:57:54 +010025class MatchFailedException(Exception):
26 def __init__(self, assertion, lineNo):
27 self.assertion = assertion
28 self.lineNo = lineNo
29
30def splitIntoGroups(assertions):
31 """ Breaks up a list of assertions, grouping instructions which should be
32 tested in the same scope (consecutive DAG and NOT instructions).
33 """
34 splitAssertions = []
35 lastVariant = None
36 for assertion in assertions:
37 if (assertion.variant == lastVariant and
38 assertion.variant in [TestAssertion.Variant.DAG, TestAssertion.Variant.Not]):
39 splitAssertions[-1].append(assertion)
40 else:
41 splitAssertions.append([assertion])
42 lastVariant = assertion.variant
43 return splitAssertions
44
45def findMatchingLine(assertion, c1Pass, scope, variables, excludeLines=[]):
46 """ Finds the first line in `c1Pass` which matches `assertion`.
47
48 Scan only lines numbered between `scope.start` and `scope.end` and not on the
49 `excludeLines` list.
50
51 Returns the index of the `c1Pass` line matching the assertion and variables
52 values after the match.
53
54 Raises MatchFailedException if no such `c1Pass` line can be found.
David Brazdil2c27f2c2015-05-12 18:06:38 +010055 """
David Brazdil6423cf52015-05-20 14:57:54 +010056 for i in range(scope.start, scope.end):
57 if i in excludeLines: continue
58 newVariables = MatchLines(assertion, c1Pass.body[i], variables)
59 if newVariables is not None:
60 return MatchInfo(MatchScope(i, i), newVariables)
61 raise MatchFailedException(assertion, scope.start)
David Brazdil2c27f2c2015-05-12 18:06:38 +010062
David Brazdil6423cf52015-05-20 14:57:54 +010063def matchDagGroup(assertions, c1Pass, scope, variables):
64 """ Attempts to find matching `c1Pass` lines for a group of DAG assertions.
65
66 Assertions are matched in the list order and variable values propagated. Only
67 lines in `scope` are scanned and each line can only match one assertion.
68
69 Returns the range of `c1Pass` lines covered by this group (min/max of matching
70 line numbers) and the variable values after the match of the last assertion.
71
72 Raises MatchFailedException when an assertion cannot be satisfied.
David Brazdil2c27f2c2015-05-12 18:06:38 +010073 """
David Brazdil2c27f2c2015-05-12 18:06:38 +010074 matchedLines = []
David Brazdil6423cf52015-05-20 14:57:54 +010075 for assertion in assertions:
76 assert assertion.variant == TestAssertion.Variant.DAG
77 match = findMatchingLine(assertion, c1Pass, scope, variables, matchedLines)
78 variables = match.variables
79 assert match.scope.start == match.scope.end
80 assert match.scope.start not in matchedLines
81 matchedLines.append(match.scope.start)
82 return MatchInfo(MatchScope(min(matchedLines), max(matchedLines)), variables)
David Brazdil2c27f2c2015-05-12 18:06:38 +010083
David Brazdil6423cf52015-05-20 14:57:54 +010084def testNotGroup(assertions, c1Pass, scope, variables):
85 """ Verifies that none of the given NOT assertions matches a line inside
86 the given `scope` of `c1Pass` lines.
David Brazdil2c27f2c2015-05-12 18:06:38 +010087
David Brazdil6423cf52015-05-20 14:57:54 +010088 Raises MatchFailedException if an assertion matches a line in the scope.
David Brazdil2c27f2c2015-05-12 18:06:38 +010089 """
David Brazdil6423cf52015-05-20 14:57:54 +010090 for i in range(scope.start, scope.end):
91 line = c1Pass.body[i]
92 for assertion in assertions:
93 assert assertion.variant == TestAssertion.Variant.Not
94 if MatchLines(assertion, line, variables) is not None:
95 raise MatchFailedException(assertion, i)
David Brazdil2c27f2c2015-05-12 18:06:38 +010096
David Brazdil6423cf52015-05-20 14:57:54 +010097def MatchTestCase(testCase, c1Pass):
98 """ Runs a test case against a C1visualizer graph dump.
99
100 Raises MatchFailedException when an assertion cannot be satisfied.
David Brazdil2c27f2c2015-05-12 18:06:38 +0100101 """
David Brazdil6423cf52015-05-20 14:57:54 +0100102 assert testCase.name == c1Pass.name
David Brazdil2c27f2c2015-05-12 18:06:38 +0100103
David Brazdil6423cf52015-05-20 14:57:54 +0100104 matchFrom = 0
105 variables = ImmutableDict()
106 c1Length = len(c1Pass.body)
David Brazdil2c27f2c2015-05-12 18:06:38 +0100107
David Brazdil6423cf52015-05-20 14:57:54 +0100108 # NOT assertions are verified retrospectively, once the scope is known.
109 pendingNotAssertions = None
David Brazdil2c27f2c2015-05-12 18:06:38 +0100110
David Brazdil6423cf52015-05-20 14:57:54 +0100111 # Prepare assertions by grouping those that are verified in the same scope.
112 # We also add None as an EOF assertion that will set scope for NOTs.
113 assertionGroups = splitIntoGroups(testCase.assertions)
114 assertionGroups.append(None)
David Brazdil2c27f2c2015-05-12 18:06:38 +0100115
David Brazdil6423cf52015-05-20 14:57:54 +0100116 for assertionGroup in assertionGroups:
117 if assertionGroup is None:
118 # EOF marker always matches the last+1 line of c1Pass.
119 match = MatchInfo(MatchScope(c1Length, c1Length), None)
120 elif assertionGroup[0].variant == TestAssertion.Variant.Not:
121 # NOT assertions will be tested together with the next group.
122 assert not pendingNotAssertions
123 pendingNotAssertions = assertionGroup
124 continue
125 elif assertionGroup[0].variant == TestAssertion.Variant.InOrder:
126 # Single in-order assertion. Find the first line that matches.
127 assert len(assertionGroup) == 1
128 scope = MatchScope(matchFrom, c1Length)
129 match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables)
David Brazdil71141192015-05-19 18:29:40 +0100130 elif assertionGroup[0].variant == TestAssertion.Variant.NextLine:
131 # Single next-line assertion. Test if the current line matches.
132 assert len(assertionGroup) == 1
133 scope = MatchScope(matchFrom, matchFrom + 1)
134 match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables)
David Brazdil6423cf52015-05-20 14:57:54 +0100135 else:
136 # A group of DAG assertions. Match them all starting from the same point.
137 assert assertionGroup[0].variant == TestAssertion.Variant.DAG
138 scope = MatchScope(matchFrom, c1Length)
139 match = matchDagGroup(assertionGroup, c1Pass, scope, variables)
140
141 if pendingNotAssertions:
142 # Previous group were NOT assertions. Make sure they don't match any lines
143 # in the [matchFrom, match.start) scope.
144 scope = MatchScope(matchFrom, match.scope.start)
145 testNotGroup(pendingNotAssertions, c1Pass, scope, variables)
146 pendingNotAssertions = None
147
148 # Update state.
149 assert matchFrom <= match.scope.end
150 matchFrom = match.scope.end + 1
151 variables = match.variables
David Brazdil2c27f2c2015-05-12 18:06:38 +0100152
153def MatchFiles(checkerFile, c1File):
154 for testCase in checkerFile.testCases:
155 # TODO: Currently does not handle multiple occurrences of the same group
156 # name, e.g. when a pass is run multiple times. It will always try to
157 # match a check group against the first output group of the same name.
158 c1Pass = c1File.findPass(testCase.name)
159 if c1Pass is None:
David Brazdil6423cf52015-05-20 14:57:54 +0100160 Logger.fail("Test case \"{}\" not found in the CFG file".format(testCase.name),
Calin Juravlea8b85b22015-05-14 17:30:21 +0100161 testCase.fileName, testCase.startLineNo)
David Brazdil6423cf52015-05-20 14:57:54 +0100162
David Brazdil2c27f2c2015-05-12 18:06:38 +0100163 Logger.startTest(testCase.name)
David Brazdil6423cf52015-05-20 14:57:54 +0100164 try:
165 MatchTestCase(testCase, c1Pass)
166 Logger.testPassed()
167 except MatchFailedException as e:
168 lineNo = c1Pass.startLineNo + e.lineNo
169 if e.assertion.variant == TestAssertion.Variant.Not:
170 Logger.testFailed("NOT assertion matched line {}".format(lineNo),
171 e.assertion.fileName, e.assertion.lineNo)
172 else:
173 Logger.testFailed("Assertion could not be matched starting from line {}".format(lineNo),
174 e.assertion.fileName, e.assertion.lineNo)