David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 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 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 15 | from collections import namedtuple |
David Brazdil | c4de943 | 2015-05-20 11:03:22 +0100 | [diff] [blame] | 16 | from common.immutables import ImmutableDict |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 17 | from common.logger import Logger |
| 18 | from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass |
| 19 | from file_format.checker.struct import CheckerFile, TestCase, TestAssertion |
David Brazdil | b34c35e | 2015-08-20 11:46:04 +0100 | [diff] [blame] | 20 | from match.line import MatchLines, EvaluateLine |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 21 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 22 | MatchScope = namedtuple("MatchScope", ["start", "end"]) |
| 23 | MatchInfo = namedtuple("MatchInfo", ["scope", "variables"]) |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 24 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 25 | class MatchFailedException(Exception): |
| 26 | def __init__(self, assertion, lineNo): |
| 27 | self.assertion = assertion |
| 28 | self.lineNo = lineNo |
| 29 | |
| 30 | def 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 | |
| 45 | def 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 Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 55 | """ |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 56 | 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 Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 62 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 63 | def 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 Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 73 | """ |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 74 | matchedLines = [] |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 75 | 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 Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 83 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 84 | def 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 Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 87 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 88 | Raises MatchFailedException if an assertion matches a line in the scope. |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 89 | """ |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 90 | 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 Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 96 | |
David Brazdil | b34c35e | 2015-08-20 11:46:04 +0100 | [diff] [blame] | 97 | def testEvalGroup(assertions, scope, variables): |
| 98 | for assertion in assertions: |
| 99 | if not EvaluateLine(assertion, variables): |
| 100 | raise MatchFailedException(assertion, scope.start) |
| 101 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 102 | def MatchTestCase(testCase, c1Pass): |
| 103 | """ Runs a test case against a C1visualizer graph dump. |
| 104 | |
| 105 | Raises MatchFailedException when an assertion cannot be satisfied. |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 106 | """ |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 107 | assert testCase.name == c1Pass.name |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 108 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 109 | matchFrom = 0 |
| 110 | variables = ImmutableDict() |
| 111 | c1Length = len(c1Pass.body) |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 112 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 113 | # NOT assertions are verified retrospectively, once the scope is known. |
| 114 | pendingNotAssertions = None |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 115 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 116 | # Prepare assertions by grouping those that are verified in the same scope. |
| 117 | # We also add None as an EOF assertion that will set scope for NOTs. |
| 118 | assertionGroups = splitIntoGroups(testCase.assertions) |
| 119 | assertionGroups.append(None) |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 120 | |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 121 | for assertionGroup in assertionGroups: |
| 122 | if assertionGroup is None: |
| 123 | # EOF marker always matches the last+1 line of c1Pass. |
| 124 | match = MatchInfo(MatchScope(c1Length, c1Length), None) |
| 125 | elif assertionGroup[0].variant == TestAssertion.Variant.Not: |
| 126 | # NOT assertions will be tested together with the next group. |
| 127 | assert not pendingNotAssertions |
| 128 | pendingNotAssertions = assertionGroup |
| 129 | continue |
| 130 | elif assertionGroup[0].variant == TestAssertion.Variant.InOrder: |
| 131 | # Single in-order assertion. Find the first line that matches. |
| 132 | assert len(assertionGroup) == 1 |
| 133 | scope = MatchScope(matchFrom, c1Length) |
| 134 | match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables) |
David Brazdil | 7114119 | 2015-05-19 18:29:40 +0100 | [diff] [blame] | 135 | elif assertionGroup[0].variant == TestAssertion.Variant.NextLine: |
| 136 | # Single next-line assertion. Test if the current line matches. |
| 137 | assert len(assertionGroup) == 1 |
| 138 | scope = MatchScope(matchFrom, matchFrom + 1) |
| 139 | match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables) |
David Brazdil | b34c35e | 2015-08-20 11:46:04 +0100 | [diff] [blame] | 140 | elif assertionGroup[0].variant == TestAssertion.Variant.DAG: |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 141 | # A group of DAG assertions. Match them all starting from the same point. |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 142 | scope = MatchScope(matchFrom, c1Length) |
| 143 | match = matchDagGroup(assertionGroup, c1Pass, scope, variables) |
David Brazdil | b34c35e | 2015-08-20 11:46:04 +0100 | [diff] [blame] | 144 | else: |
| 145 | assert assertionGroup[0].variant == TestAssertion.Variant.Eval |
| 146 | scope = MatchScope(matchFrom, c1Length) |
| 147 | testEvalGroup(assertionGroup, scope, variables) |
| 148 | continue |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 149 | |
| 150 | if pendingNotAssertions: |
| 151 | # Previous group were NOT assertions. Make sure they don't match any lines |
| 152 | # in the [matchFrom, match.start) scope. |
| 153 | scope = MatchScope(matchFrom, match.scope.start) |
| 154 | testNotGroup(pendingNotAssertions, c1Pass, scope, variables) |
| 155 | pendingNotAssertions = None |
| 156 | |
| 157 | # Update state. |
| 158 | assert matchFrom <= match.scope.end |
| 159 | matchFrom = match.scope.end + 1 |
| 160 | variables = match.variables |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 161 | |
David Brazdil | 5cc343d | 2015-10-08 11:35:32 +0100 | [diff] [blame] | 162 | def MatchFiles(checkerFile, c1File, targetArch, debuggableMode): |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 163 | for testCase in checkerFile.testCases: |
Alexandre Rames | 5e2c8d3 | 2015-08-06 14:49:28 +0100 | [diff] [blame] | 164 | if testCase.testArch not in [None, targetArch]: |
| 165 | continue |
David Brazdil | 5cc343d | 2015-10-08 11:35:32 +0100 | [diff] [blame] | 166 | if testCase.forDebuggable != debuggableMode: |
| 167 | continue |
| 168 | |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 169 | # TODO: Currently does not handle multiple occurrences of the same group |
| 170 | # name, e.g. when a pass is run multiple times. It will always try to |
| 171 | # match a check group against the first output group of the same name. |
| 172 | c1Pass = c1File.findPass(testCase.name) |
| 173 | if c1Pass is None: |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 174 | Logger.fail("Test case \"{}\" not found in the CFG file".format(testCase.name), |
Calin Juravle | a8b85b2 | 2015-05-14 17:30:21 +0100 | [diff] [blame] | 175 | testCase.fileName, testCase.startLineNo) |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 176 | |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 177 | Logger.startTest(testCase.name) |
David Brazdil | 6423cf5 | 2015-05-20 14:57:54 +0100 | [diff] [blame] | 178 | try: |
| 179 | MatchTestCase(testCase, c1Pass) |
| 180 | Logger.testPassed() |
| 181 | except MatchFailedException as e: |
| 182 | lineNo = c1Pass.startLineNo + e.lineNo |
| 183 | if e.assertion.variant == TestAssertion.Variant.Not: |
| 184 | Logger.testFailed("NOT assertion matched line {}".format(lineNo), |
| 185 | e.assertion.fileName, e.assertion.lineNo) |
| 186 | else: |
| 187 | Logger.testFailed("Assertion could not be matched starting from line {}".format(lineNo), |
| 188 | e.assertion.fileName, e.assertion.lineNo) |