# Copyright (C) 2014 The Android Open Source Project
|
#
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
# you may not use this file except in compliance with the License.
|
# You may obtain a copy of the License at
|
#
|
# http://www.apache.org/licenses/LICENSE-2.0
|
#
|
# Unless required by applicable law or agreed to in writing, software
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# See the License for the specific language governing permissions and
|
# limitations under the License.
|
|
from collections import namedtuple
|
from common.immutables import ImmutableDict
|
from common.logger import Logger
|
from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
|
from file_format.checker.struct import CheckerFile, TestCase, TestAssertion
|
from match.line import MatchLines, EvaluateLine
|
|
MatchScope = namedtuple("MatchScope", ["start", "end"])
|
MatchInfo = namedtuple("MatchInfo", ["scope", "variables"])
|
|
class MatchFailedException(Exception):
|
def __init__(self, assertion, lineNo, variables):
|
self.assertion = assertion
|
self.lineNo = lineNo
|
self.variables = variables
|
|
def splitIntoGroups(assertions):
|
""" Breaks up a list of assertions, grouping instructions which should be
|
tested in the same scope (consecutive DAG and NOT instructions).
|
"""
|
splitAssertions = []
|
lastVariant = None
|
for assertion in assertions:
|
if (assertion.variant == lastVariant and
|
assertion.variant in [TestAssertion.Variant.DAG, TestAssertion.Variant.Not]):
|
splitAssertions[-1].append(assertion)
|
else:
|
splitAssertions.append([assertion])
|
lastVariant = assertion.variant
|
return splitAssertions
|
|
def findMatchingLine(assertion, c1Pass, scope, variables, excludeLines=[]):
|
""" Finds the first line in `c1Pass` which matches `assertion`.
|
|
Scan only lines numbered between `scope.start` and `scope.end` and not on the
|
`excludeLines` list.
|
|
Returns the index of the `c1Pass` line matching the assertion and variables
|
values after the match.
|
|
Raises MatchFailedException if no such `c1Pass` line can be found.
|
"""
|
for i in range(scope.start, scope.end):
|
if i in excludeLines: continue
|
newVariables = MatchLines(assertion, c1Pass.body[i], variables)
|
if newVariables is not None:
|
return MatchInfo(MatchScope(i, i), newVariables)
|
raise MatchFailedException(assertion, scope.start, variables)
|
|
def matchDagGroup(assertions, c1Pass, scope, variables):
|
""" Attempts to find matching `c1Pass` lines for a group of DAG assertions.
|
|
Assertions are matched in the list order and variable values propagated. Only
|
lines in `scope` are scanned and each line can only match one assertion.
|
|
Returns the range of `c1Pass` lines covered by this group (min/max of matching
|
line numbers) and the variable values after the match of the last assertion.
|
|
Raises MatchFailedException when an assertion cannot be satisfied.
|
"""
|
matchedLines = []
|
for assertion in assertions:
|
assert assertion.variant == TestAssertion.Variant.DAG
|
match = findMatchingLine(assertion, c1Pass, scope, variables, matchedLines)
|
variables = match.variables
|
assert match.scope.start == match.scope.end
|
assert match.scope.start not in matchedLines
|
matchedLines.append(match.scope.start)
|
return MatchInfo(MatchScope(min(matchedLines), max(matchedLines)), variables)
|
|
def testNotGroup(assertions, c1Pass, scope, variables):
|
""" Verifies that none of the given NOT assertions matches a line inside
|
the given `scope` of `c1Pass` lines.
|
|
Raises MatchFailedException if an assertion matches a line in the scope.
|
"""
|
for i in range(scope.start, scope.end):
|
line = c1Pass.body[i]
|
for assertion in assertions:
|
assert assertion.variant == TestAssertion.Variant.Not
|
if MatchLines(assertion, line, variables) is not None:
|
raise MatchFailedException(assertion, i, variables)
|
|
def testEvalGroup(assertions, scope, variables):
|
for assertion in assertions:
|
if not EvaluateLine(assertion, variables):
|
raise MatchFailedException(assertion, scope.start, variables)
|
|
def MatchTestCase(testCase, c1Pass):
|
""" Runs a test case against a C1visualizer graph dump.
|
|
Raises MatchFailedException when an assertion cannot be satisfied.
|
"""
|
assert testCase.name == c1Pass.name
|
|
matchFrom = 0
|
variables = ImmutableDict()
|
c1Length = len(c1Pass.body)
|
|
# NOT assertions are verified retrospectively, once the scope is known.
|
pendingNotAssertions = None
|
|
# Prepare assertions by grouping those that are verified in the same scope.
|
# We also add None as an EOF assertion that will set scope for NOTs.
|
assertionGroups = splitIntoGroups(testCase.assertions)
|
assertionGroups.append(None)
|
|
for assertionGroup in assertionGroups:
|
if assertionGroup is None:
|
# EOF marker always matches the last+1 line of c1Pass.
|
match = MatchInfo(MatchScope(c1Length, c1Length), None)
|
elif assertionGroup[0].variant == TestAssertion.Variant.Not:
|
# NOT assertions will be tested together with the next group.
|
assert not pendingNotAssertions
|
pendingNotAssertions = assertionGroup
|
continue
|
elif assertionGroup[0].variant == TestAssertion.Variant.InOrder:
|
# Single in-order assertion. Find the first line that matches.
|
assert len(assertionGroup) == 1
|
scope = MatchScope(matchFrom, c1Length)
|
match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables)
|
elif assertionGroup[0].variant == TestAssertion.Variant.NextLine:
|
# Single next-line assertion. Test if the current line matches.
|
assert len(assertionGroup) == 1
|
scope = MatchScope(matchFrom, matchFrom + 1)
|
match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables)
|
elif assertionGroup[0].variant == TestAssertion.Variant.DAG:
|
# A group of DAG assertions. Match them all starting from the same point.
|
scope = MatchScope(matchFrom, c1Length)
|
match = matchDagGroup(assertionGroup, c1Pass, scope, variables)
|
else:
|
assert assertionGroup[0].variant == TestAssertion.Variant.Eval
|
scope = MatchScope(matchFrom, c1Length)
|
testEvalGroup(assertionGroup, scope, variables)
|
continue
|
|
if pendingNotAssertions:
|
# Previous group were NOT assertions. Make sure they don't match any lines
|
# in the [matchFrom, match.start) scope.
|
scope = MatchScope(matchFrom, match.scope.start)
|
testNotGroup(pendingNotAssertions, c1Pass, scope, variables)
|
pendingNotAssertions = None
|
|
# Update state.
|
assert matchFrom <= match.scope.end
|
matchFrom = match.scope.end + 1
|
variables = match.variables
|
|
def MatchFiles(checkerFile, c1File, targetArch, debuggableMode):
|
for testCase in checkerFile.testCases:
|
if testCase.testArch not in [None, targetArch]:
|
continue
|
if testCase.forDebuggable != debuggableMode:
|
continue
|
|
# TODO: Currently does not handle multiple occurrences of the same group
|
# name, e.g. when a pass is run multiple times. It will always try to
|
# match a check group against the first output group of the same name.
|
c1Pass = c1File.findPass(testCase.name)
|
if c1Pass is None:
|
Logger.fail("Test case not found in the CFG file",
|
testCase.fileName, testCase.startLineNo, testCase.name)
|
|
Logger.startTest(testCase.name)
|
try:
|
MatchTestCase(testCase, c1Pass)
|
Logger.testPassed()
|
except MatchFailedException as e:
|
lineNo = c1Pass.startLineNo + e.lineNo
|
if e.assertion.variant == TestAssertion.Variant.Not:
|
msg = "NOT assertion matched line {}"
|
else:
|
msg = "Assertion could not be matched starting from line {}"
|
msg = msg.format(lineNo)
|
Logger.testFailed(msg, e.assertion, e.variables)
|