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 | c4de943 | 2015-05-20 11:03:22 +0100 | [diff] [blame] | 15 | from common.immutables import ImmutableDict |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 16 | from common.logger import Logger |
| 17 | from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass |
| 18 | from file_format.checker.struct import CheckerFile, TestCase, TestAssertion |
| 19 | from match.line import MatchLines |
| 20 | |
| 21 | def __headAndTail(list): |
| 22 | return list[0], list[1:] |
| 23 | |
| 24 | def __splitByVariant(lines, variant): |
| 25 | """ Splits a list of check lines at index 'i' such that lines[i] is the first |
| 26 | element whose variant is not equal to the given parameter. |
| 27 | """ |
| 28 | i = 0 |
| 29 | while i < len(lines) and lines[i].variant == variant: |
| 30 | i += 1 |
| 31 | return lines[:i], lines[i:] |
| 32 | |
| 33 | def __nextIndependentChecks(checkLines): |
| 34 | """ Extracts the first sequence of check lines which are independent of each |
| 35 | other's match location, i.e. either consecutive DAG lines or a single |
| 36 | InOrder line. Any Not lines preceeding this sequence are also extracted. |
| 37 | """ |
| 38 | notChecks, checkLines = __splitByVariant(checkLines, TestAssertion.Variant.Not) |
| 39 | if not checkLines: |
| 40 | return notChecks, [], [] |
| 41 | |
| 42 | head, tail = __headAndTail(checkLines) |
| 43 | if head.variant == TestAssertion.Variant.InOrder: |
| 44 | return notChecks, [head], tail |
| 45 | else: |
| 46 | assert head.variant == TestAssertion.Variant.DAG |
| 47 | independentChecks, checkLines = __splitByVariant(checkLines, TestAssertion.Variant.DAG) |
| 48 | return notChecks, independentChecks, checkLines |
| 49 | |
| 50 | def __findFirstMatch(checkLine, outputLines, startLineNo, lineFilter, varState): |
| 51 | """ If successful, returns the line number of the first output line matching |
| 52 | the check line and the updated variable state. Otherwise returns -1 and |
| 53 | None, respectively. The 'lineFilter' parameter can be used to supply a |
| 54 | list of line numbers (counting from 1) which should be skipped. |
| 55 | """ |
| 56 | matchLineNo = startLineNo |
| 57 | for outputLine in outputLines: |
| 58 | if matchLineNo not in lineFilter: |
| 59 | newVarState = MatchLines(checkLine, outputLine, varState) |
| 60 | if newVarState is not None: |
| 61 | return matchLineNo, newVarState |
| 62 | matchLineNo += 1 |
| 63 | return -1, None |
| 64 | |
| 65 | def __matchIndependentChecks(checkLines, outputLines, startLineNo, varState): |
| 66 | """ Matches the given positive check lines against the output in order of |
| 67 | appearance. Variable state is propagated but the scope of the search |
| 68 | remains the same for all checks. Each output line can only be matched |
| 69 | once. If all check lines are matched, the resulting variable state is |
| 70 | returned together with the remaining output. The function also returns |
| 71 | output lines which appear before either of the matched lines so they can |
| 72 | be tested against Not checks. |
| 73 | """ |
| 74 | # If no checks are provided, skip over the entire output. |
| 75 | if not checkLines: |
| 76 | return outputLines, [], startLineNo + len(outputLines), varState |
| 77 | |
| 78 | # Keep track of which lines have been matched. |
| 79 | matchedLines = [] |
| 80 | |
| 81 | # Find first unused output line which matches each check line. |
| 82 | for checkLine in checkLines: |
| 83 | matchLineNo, varState = \ |
| 84 | __findFirstMatch(checkLine, outputLines, startLineNo, matchedLines, varState) |
| 85 | if varState is None: |
| 86 | Logger.testFailed("Could not match check line \"" + checkLine.originalText + "\" " + |
| 87 | "starting from output line " + str(startLineNo), |
| 88 | checkLine.fileName, checkLine.lineNo) |
| 89 | matchedLines.append(matchLineNo) |
| 90 | |
| 91 | # Return new variable state and the output lines which lie outside the |
| 92 | # match locations of this independent group. |
| 93 | minMatchLineNo = min(matchedLines) |
| 94 | maxMatchLineNo = max(matchedLines) |
| 95 | preceedingLines = outputLines[:minMatchLineNo - startLineNo] |
| 96 | remainingLines = outputLines[maxMatchLineNo - startLineNo + 1:] |
| 97 | return preceedingLines, remainingLines, maxMatchLineNo + 1, varState |
| 98 | |
| 99 | def __matchNotLines(checkLines, outputLines, startLineNo, varState): |
| 100 | """ Makes sure that the given check lines do not match any of the given output |
| 101 | lines. Variable state does not change. |
| 102 | """ |
| 103 | for checkLine in checkLines: |
| 104 | assert checkLine.variant == TestAssertion.Variant.Not |
| 105 | matchLineNo, matchVarState = \ |
| 106 | __findFirstMatch(checkLine, outputLines, startLineNo, [], varState) |
| 107 | if matchVarState is not None: |
| 108 | Logger.testFailed("CHECK-NOT line \"" + checkLine.originalText + "\" matches output line " + \ |
| 109 | str(matchLineNo), checkLine.fileName, checkLine.lineNo) |
| 110 | |
| 111 | def __matchGroups(checkGroup, outputGroup): |
| 112 | """ Matches the check lines in this group against an output group. It is |
| 113 | responsible for running the checks in the right order and scope, and |
| 114 | for propagating the variable state between the check lines. |
| 115 | """ |
David Brazdil | c4de943 | 2015-05-20 11:03:22 +0100 | [diff] [blame] | 116 | varState = ImmutableDict() |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 117 | checkLines = checkGroup.assertions |
| 118 | outputLines = outputGroup.body |
| 119 | startLineNo = outputGroup.startLineNo |
| 120 | |
| 121 | while checkLines: |
| 122 | # Extract the next sequence of location-independent checks to be matched. |
| 123 | notChecks, independentChecks, checkLines = __nextIndependentChecks(checkLines) |
| 124 | |
| 125 | # Match the independent checks. |
| 126 | notOutput, outputLines, newStartLineNo, newVarState = \ |
| 127 | __matchIndependentChecks(independentChecks, outputLines, startLineNo, varState) |
| 128 | |
| 129 | # Run the Not checks against the output lines which lie between the last |
| 130 | # two independent groups or the bounds of the output. |
| 131 | __matchNotLines(notChecks, notOutput, startLineNo, varState) |
| 132 | |
| 133 | # Update variable state. |
| 134 | startLineNo = newStartLineNo |
| 135 | varState = newVarState |
| 136 | |
| 137 | def MatchFiles(checkerFile, c1File): |
| 138 | for testCase in checkerFile.testCases: |
| 139 | # TODO: Currently does not handle multiple occurrences of the same group |
| 140 | # name, e.g. when a pass is run multiple times. It will always try to |
| 141 | # match a check group against the first output group of the same name. |
| 142 | c1Pass = c1File.findPass(testCase.name) |
| 143 | if c1Pass is None: |
| 144 | Logger.fail("Test case \"" + testCase.name + "\" not found in the C1visualizer output", |
Calin Juravle | a8b85b2 | 2015-05-14 17:30:21 +0100 | [diff] [blame] | 145 | testCase.fileName, testCase.startLineNo) |
David Brazdil | 2c27f2c | 2015-05-12 18:06:38 +0100 | [diff] [blame] | 146 | Logger.startTest(testCase.name) |
| 147 | __matchGroups(testCase, c1Pass) |
| 148 | Logger.testPassed() |