Revert "ART: Split Checker into smaller files"
This reverts commit a490be5dd95982f77ff1447bea9ee06604038a96.
Change-Id: Ic3b7cf172200caced9ae2f10d2f200447e6801ee
diff --git a/test/run-test b/test/run-test
index 239681f..2873a35 100755
--- a/test/run-test
+++ b/test/run-test
@@ -39,7 +39,7 @@
else
tmp_dir="${TMPDIR}/$USER/${test_dir}"
fi
-checker="${progdir}/../tools/checker/checker.py"
+checker="${progdir}/../tools/checker.py"
export JAVA="java"
export JAVAC="javac -g"
diff --git a/tools/checker.py b/tools/checker.py
new file mode 100755
index 0000000..08ad57b
--- /dev/null
+++ b/tools/checker.py
@@ -0,0 +1,778 @@
+#!/usr/bin/env python2
+#
+# 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.
+
+
+# Checker is a testing tool which compiles a given test file and compares the
+# state of the control-flow graph before and after each optimization pass
+# against a set of assertions specified alongside the tests.
+#
+# Tests are written in Java, turned into DEX and compiled with the Optimizing
+# compiler. "Check lines" are assertions formatted as comments of the Java file.
+# They begin with prefix 'CHECK' followed by a pattern that the engine attempts
+# to match in the compiler-generated output.
+#
+# Assertions are tested in groups which correspond to the individual compiler
+# passes. Each group of check lines therefore must start with a 'CHECK-START'
+# header which specifies the output group it should be tested against. The group
+# name must exactly match one of the groups recognized in the output (they can
+# be listed with the '--list-groups' command-line flag).
+#
+# Matching of check lines is carried out in the order of appearance in the
+# source file. There are three types of check lines:
+# - CHECK: Must match an output line which appears in the output group
+# later than lines matched against any preceeding checks. Output
+# lines must therefore match the check lines in the same order.
+# These are referred to as "in-order" checks in the code.
+# - CHECK-DAG: Must match an output line which appears in the output group
+# later than lines matched against any preceeding in-order checks.
+# In other words, the order of output lines does not matter
+# between consecutive DAG checks.
+# - CHECK-NOT: Must not match any output line which appears in the output group
+# later than lines matched against any preceeding checks and
+# earlier than lines matched against any subsequent checks.
+# Surrounding non-negative checks (or boundaries of the group)
+# therefore create a scope within which the assertion is verified.
+#
+# Check-line patterns are treated as plain text rather than regular expressions
+# but are whitespace agnostic.
+#
+# Actual regex patterns can be inserted enclosed in '{{' and '}}' brackets. If
+# curly brackets need to be used inside the body of the regex, they need to be
+# enclosed in round brackets. For example, the pattern '{{foo{2}}}' will parse
+# the invalid regex 'foo{2', but '{{(fo{2})}}' will match 'foo'.
+#
+# Regex patterns can be named and referenced later. A new variable is defined
+# with '[[name:regex]]' and can be referenced with '[[name]]'. Variables are
+# only valid within the scope of the defining group. Within a group they cannot
+# be redefined or used undefined.
+#
+# Example:
+# The following assertions can be placed in a Java source file:
+#
+# // CHECK-START: int MyClass.MyMethod() constant_folding (after)
+# // CHECK: [[ID:i[0-9]+]] IntConstant {{11|22}}
+# // CHECK: Return [ [[ID]] ]
+#
+# The engine will attempt to match the check lines against the output of the
+# group named on the first line. Together they verify that the CFG after
+# constant folding returns an integer constant with value either 11 or 22.
+#
+
+from __future__ import print_function
+import argparse
+import os
+import re
+import shutil
+import sys
+import tempfile
+
+class Logger(object):
+
+ class Level(object):
+ NoOutput, Error, Info = range(3)
+
+ class Color(object):
+ Default, Blue, Gray, Purple, Red = range(5)
+
+ @staticmethod
+ def terminalCode(color, out=sys.stdout):
+ if not out.isatty():
+ return ''
+ elif color == Logger.Color.Blue:
+ return '\033[94m'
+ elif color == Logger.Color.Gray:
+ return '\033[37m'
+ elif color == Logger.Color.Purple:
+ return '\033[95m'
+ elif color == Logger.Color.Red:
+ return '\033[91m'
+ else:
+ return '\033[0m'
+
+ Verbosity = Level.Info
+
+ @staticmethod
+ def log(text, level=Level.Info, color=Color.Default, newLine=True, out=sys.stdout):
+ if level <= Logger.Verbosity:
+ text = Logger.Color.terminalCode(color, out) + text + \
+ Logger.Color.terminalCode(Logger.Color.Default, out)
+ if newLine:
+ print(text, file=out)
+ else:
+ print(text, end="", file=out)
+ out.flush()
+
+ @staticmethod
+ def fail(msg, file=None, line=-1):
+ location = ""
+ if file:
+ location += file + ":"
+ if line > 0:
+ location += str(line) + ":"
+ if location:
+ location += " "
+
+ Logger.log(location, Logger.Level.Error, color=Logger.Color.Gray, newLine=False, out=sys.stderr)
+ Logger.log("error: ", Logger.Level.Error, color=Logger.Color.Red, newLine=False, out=sys.stderr)
+ Logger.log(msg, Logger.Level.Error, out=sys.stderr)
+ sys.exit(msg)
+
+ @staticmethod
+ def startTest(name):
+ Logger.log("TEST ", color=Logger.Color.Purple, newLine=False)
+ Logger.log(name + "... ", newLine=False)
+
+ @staticmethod
+ def testPassed():
+ Logger.log("PASS", color=Logger.Color.Blue)
+
+ @staticmethod
+ def testFailed(msg, file=None, line=-1):
+ Logger.log("FAIL", color=Logger.Color.Red)
+ Logger.fail(msg, file, line)
+
+class CommonEqualityMixin:
+ """Mixin for class equality as equality of the fields."""
+ def __eq__(self, other):
+ return (isinstance(other, self.__class__)
+ and self.__dict__ == other.__dict__)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ return "<%s: %s>" % (type(self).__name__, str(self.__dict__))
+
+
+class CheckElement(CommonEqualityMixin):
+ """Single element of the check line."""
+
+ class Variant(object):
+ """Supported language constructs."""
+ Text, Pattern, VarRef, VarDef, Separator = range(5)
+
+ rStartOptional = r"("
+ rEndOptional = r")?"
+
+ rName = r"([a-zA-Z][a-zA-Z0-9]*)"
+ rRegex = r"(.+?)"
+ rPatternStartSym = r"(\{\{)"
+ rPatternEndSym = r"(\}\})"
+ rVariableStartSym = r"(\[\[)"
+ rVariableEndSym = r"(\]\])"
+ rVariableSeparator = r"(:)"
+
+ regexPattern = rPatternStartSym + rRegex + rPatternEndSym
+ regexVariable = rVariableStartSym + \
+ rName + \
+ (rStartOptional + rVariableSeparator + rRegex + rEndOptional) + \
+ rVariableEndSym
+
+ def __init__(self, variant, name, pattern):
+ self.variant = variant
+ self.name = name
+ self.pattern = pattern
+
+ @staticmethod
+ def newSeparator():
+ return CheckElement(CheckElement.Variant.Separator, None, None)
+
+ @staticmethod
+ def parseText(text):
+ return CheckElement(CheckElement.Variant.Text, None, re.escape(text))
+
+ @staticmethod
+ def parsePattern(patternElem):
+ return CheckElement(CheckElement.Variant.Pattern, None, patternElem[2:-2])
+
+ @staticmethod
+ def parseVariable(varElem):
+ colonPos = varElem.find(":")
+ if colonPos == -1:
+ # Variable reference
+ name = varElem[2:-2]
+ return CheckElement(CheckElement.Variant.VarRef, name, None)
+ else:
+ # Variable definition
+ name = varElem[2:colonPos]
+ body = varElem[colonPos+1:-2]
+ return CheckElement(CheckElement.Variant.VarDef, name, body)
+
+class CheckLine(CommonEqualityMixin):
+ """Representation of a single assertion in the check file formed of one or
+ more regex elements. Matching against an output line is successful only
+ if all regex elements can be matched in the given order."""
+
+ class Variant(object):
+ """Supported types of assertions."""
+ InOrder, DAG, Not = range(3)
+
+ def __init__(self, content, variant=Variant.InOrder, fileName=None, lineNo=-1):
+ self.fileName = fileName
+ self.lineNo = lineNo
+ self.content = content.strip()
+
+ self.variant = variant
+ self.lineParts = self.__parse(self.content)
+ if not self.lineParts:
+ Logger.fail("Empty check line", self.fileName, self.lineNo)
+
+ if self.variant == CheckLine.Variant.Not:
+ for elem in self.lineParts:
+ if elem.variant == CheckElement.Variant.VarDef:
+ Logger.fail("CHECK-NOT lines cannot define variables", self.fileName, self.lineNo)
+
+ def __eq__(self, other):
+ return (isinstance(other, self.__class__) and
+ self.variant == other.variant and
+ self.lineParts == other.lineParts)
+
+ # Returns True if the given Match object was at the beginning of the line.
+ def __isMatchAtStart(self, match):
+ return (match is not None) and (match.start() == 0)
+
+ # Takes in a list of Match objects and returns the minimal start point among
+ # them. If there aren't any successful matches it returns the length of
+ # the searched string.
+ def __firstMatch(self, matches, string):
+ starts = map(lambda m: len(string) if m is None else m.start(), matches)
+ return min(starts)
+
+ # This method parses the content of a check line stripped of the initial
+ # comment symbol and the CHECK keyword.
+ def __parse(self, line):
+ lineParts = []
+ # Loop as long as there is something to parse.
+ while line:
+ # Search for the nearest occurrence of the special markers.
+ matchWhitespace = re.search(r"\s+", line)
+ matchPattern = re.search(CheckElement.regexPattern, line)
+ matchVariable = re.search(CheckElement.regexVariable, line)
+
+ # If one of the above was identified at the current position, extract them
+ # from the line, parse them and add to the list of line parts.
+ if self.__isMatchAtStart(matchWhitespace):
+ # A whitespace in the check line creates a new separator of line parts.
+ # This allows for ignored output between the previous and next parts.
+ line = line[matchWhitespace.end():]
+ lineParts.append(CheckElement.newSeparator())
+ elif self.__isMatchAtStart(matchPattern):
+ pattern = line[0:matchPattern.end()]
+ line = line[matchPattern.end():]
+ lineParts.append(CheckElement.parsePattern(pattern))
+ elif self.__isMatchAtStart(matchVariable):
+ var = line[0:matchVariable.end()]
+ line = line[matchVariable.end():]
+ lineParts.append(CheckElement.parseVariable(var))
+ else:
+ # If we're not currently looking at a special marker, this is a plain
+ # text match all the way until the first special marker (or the end
+ # of the line).
+ firstMatch = self.__firstMatch([ matchWhitespace, matchPattern, matchVariable ], line)
+ text = line[0:firstMatch]
+ line = line[firstMatch:]
+ lineParts.append(CheckElement.parseText(text))
+ return lineParts
+
+ # Returns the regex pattern to be matched in the output line. Variable
+ # references are substituted with their current values provided in the
+ # 'varState' argument.
+ # An exception is raised if a referenced variable is undefined.
+ def __generatePattern(self, linePart, varState):
+ if linePart.variant == CheckElement.Variant.VarRef:
+ try:
+ return re.escape(varState[linePart.name])
+ except KeyError:
+ Logger.testFailed("Use of undefined variable \"" + linePart.name + "\"",
+ self.fileName, self.lineNo)
+ else:
+ return linePart.pattern
+
+ def __isSeparated(self, outputLine, matchStart):
+ return (matchStart == 0) or (outputLine[matchStart - 1:matchStart].isspace())
+
+ # Attempts to match the check line against a line from the output file with
+ # the given initial variable values. It returns the new variable state if
+ # successful and None otherwise.
+ def match(self, outputLine, initialVarState):
+ # Do the full matching on a shadow copy of the variable state. If the
+ # matching fails half-way, we will not need to revert the state.
+ varState = dict(initialVarState)
+
+ matchStart = 0
+ isAfterSeparator = True
+
+ # Now try to parse all of the parts of the check line in the right order.
+ # Variable values are updated on-the-fly, meaning that a variable can
+ # be referenced immediately after its definition.
+ for part in self.lineParts:
+ if part.variant == CheckElement.Variant.Separator:
+ isAfterSeparator = True
+ continue
+
+ # Find the earliest match for this line part.
+ pattern = self.__generatePattern(part, varState)
+ while True:
+ match = re.search(pattern, outputLine[matchStart:])
+ if (match is None) or (not isAfterSeparator and not self.__isMatchAtStart(match)):
+ return None
+ matchEnd = matchStart + match.end()
+ matchStart += match.start()
+
+ # Check if this is a valid match if we expect a whitespace separator
+ # before the matched text. Otherwise loop and look for another match.
+ if not isAfterSeparator or self.__isSeparated(outputLine, matchStart):
+ break
+ else:
+ matchStart += 1
+
+ if part.variant == CheckElement.Variant.VarDef:
+ if part.name in varState:
+ Logger.testFailed("Multiple definitions of variable \"" + part.name + "\"",
+ self.fileName, self.lineNo)
+ varState[part.name] = outputLine[matchStart:matchEnd]
+
+ matchStart = matchEnd
+ isAfterSeparator = False
+
+ # All parts were successfully matched. Return the new variable state.
+ return varState
+
+
+class CheckGroup(CommonEqualityMixin):
+ """Represents a named collection of check lines which are to be matched
+ against an output group of the same name."""
+
+ def __init__(self, name, lines, fileName=None, lineNo=-1):
+ self.fileName = fileName
+ self.lineNo = lineNo
+
+ if not name:
+ Logger.fail("Check group does not have a name", self.fileName, self.lineNo)
+ if not lines:
+ Logger.fail("Check group does not have a body", self.fileName, self.lineNo)
+
+ self.name = name
+ self.lines = lines
+
+ def __eq__(self, other):
+ return (isinstance(other, self.__class__) and
+ self.name == other.name and
+ self.lines == other.lines)
+
+ def __headAndTail(self, list):
+ return list[0], list[1:]
+
+ # Splits a list of check lines at index 'i' such that lines[i] is the first
+ # element whose variant is not equal to the given parameter.
+ def __splitByVariant(self, lines, variant):
+ i = 0
+ while i < len(lines) and lines[i].variant == variant:
+ i += 1
+ return lines[:i], lines[i:]
+
+ # Extracts the first sequence of check lines which are independent of each
+ # other's match location, i.e. either consecutive DAG lines or a single
+ # InOrder line. Any Not lines preceeding this sequence are also extracted.
+ def __nextIndependentChecks(self, checkLines):
+ notChecks, checkLines = self.__splitByVariant(checkLines, CheckLine.Variant.Not)
+ if not checkLines:
+ return notChecks, [], []
+
+ head, tail = self.__headAndTail(checkLines)
+ if head.variant == CheckLine.Variant.InOrder:
+ return notChecks, [head], tail
+ else:
+ assert head.variant == CheckLine.Variant.DAG
+ independentChecks, checkLines = self.__splitByVariant(checkLines, CheckLine.Variant.DAG)
+ return notChecks, independentChecks, checkLines
+
+ # If successful, returns the line number of the first output line matching the
+ # check line and the updated variable state. Otherwise returns -1 and None,
+ # respectively. The 'lineFilter' parameter can be used to supply a list of
+ # line numbers (counting from 1) which should be skipped.
+ def __findFirstMatch(self, checkLine, outputLines, startLineNo, lineFilter, varState):
+ matchLineNo = startLineNo
+ for outputLine in outputLines:
+ if matchLineNo not in lineFilter:
+ newVarState = checkLine.match(outputLine, varState)
+ if newVarState is not None:
+ return matchLineNo, newVarState
+ matchLineNo += 1
+ return -1, None
+
+ # Matches the given positive check lines against the output in order of
+ # appearance. Variable state is propagated but the scope of the search remains
+ # the same for all checks. Each output line can only be matched once.
+ # If all check lines are matched, the resulting variable state is returned
+ # together with the remaining output. The function also returns output lines
+ # which appear before either of the matched lines so they can be tested
+ # against Not checks.
+ def __matchIndependentChecks(self, checkLines, outputLines, startLineNo, varState):
+ # If no checks are provided, skip over the entire output.
+ if not checkLines:
+ return outputLines, [], startLineNo + len(outputLines), varState
+
+ # Keep track of which lines have been matched.
+ matchedLines = []
+
+ # Find first unused output line which matches each check line.
+ for checkLine in checkLines:
+ matchLineNo, varState = \
+ self.__findFirstMatch(checkLine, outputLines, startLineNo, matchedLines, varState)
+ if varState is None:
+ Logger.testFailed("Could not match check line \"" + checkLine.content + "\" " +
+ "starting from output line " + str(startLineNo),
+ self.fileName, checkLine.lineNo)
+ matchedLines.append(matchLineNo)
+
+ # Return new variable state and the output lines which lie outside the
+ # match locations of this independent group.
+ minMatchLineNo = min(matchedLines)
+ maxMatchLineNo = max(matchedLines)
+ preceedingLines = outputLines[:minMatchLineNo - startLineNo]
+ remainingLines = outputLines[maxMatchLineNo - startLineNo + 1:]
+ return preceedingLines, remainingLines, maxMatchLineNo + 1, varState
+
+ # Makes sure that the given check lines do not match any of the given output
+ # lines. Variable state does not change.
+ def __matchNotLines(self, checkLines, outputLines, startLineNo, varState):
+ for checkLine in checkLines:
+ assert checkLine.variant == CheckLine.Variant.Not
+ matchLineNo, matchVarState = \
+ self.__findFirstMatch(checkLine, outputLines, startLineNo, [], varState)
+ if matchVarState is not None:
+ Logger.testFailed("CHECK-NOT line \"" + checkLine.content + "\" matches output line " + \
+ str(matchLineNo), self.fileName, checkLine.lineNo)
+
+ # Matches the check lines in this group against an output group. It is
+ # responsible for running the checks in the right order and scope, and
+ # for propagating the variable state between the check lines.
+ def match(self, outputGroup):
+ varState = {}
+ checkLines = self.lines
+ outputLines = outputGroup.body
+ startLineNo = outputGroup.lineNo
+
+ while checkLines:
+ # Extract the next sequence of location-independent checks to be matched.
+ notChecks, independentChecks, checkLines = self.__nextIndependentChecks(checkLines)
+
+ # Match the independent checks.
+ notOutput, outputLines, newStartLineNo, newVarState = \
+ self.__matchIndependentChecks(independentChecks, outputLines, startLineNo, varState)
+
+ # Run the Not checks against the output lines which lie between the last
+ # two independent groups or the bounds of the output.
+ self.__matchNotLines(notChecks, notOutput, startLineNo, varState)
+
+ # Update variable state.
+ startLineNo = newStartLineNo
+ varState = newVarState
+
+class OutputGroup(CommonEqualityMixin):
+ """Represents a named part of the test output against which a check group of
+ the same name is to be matched."""
+
+ def __init__(self, name, body, fileName=None, lineNo=-1):
+ if not name:
+ Logger.fail("Output group does not have a name", fileName, lineNo)
+ if not body:
+ Logger.fail("Output group does not have a body", fileName, lineNo)
+
+ self.name = name
+ self.body = body
+ self.lineNo = lineNo
+
+ def __eq__(self, other):
+ return (isinstance(other, self.__class__) and
+ self.name == other.name and
+ self.body == other.body)
+
+
+class FileSplitMixin(object):
+ """Mixin for representing text files which need to be split into smaller
+ chunks before being parsed."""
+
+ def _parseStream(self, stream):
+ lineNo = 0
+ allGroups = []
+ currentGroup = None
+
+ for line in stream:
+ lineNo += 1
+ line = line.strip()
+ if not line:
+ continue
+
+ # Let the child class process the line and return information about it.
+ # The _processLine method can modify the content of the line (or delete it
+ # entirely) and specify whether it starts a new group.
+ processedLine, newGroupName = self._processLine(line, lineNo)
+ if newGroupName is not None:
+ currentGroup = (newGroupName, [], lineNo)
+ allGroups.append(currentGroup)
+ if processedLine is not None:
+ if currentGroup is not None:
+ currentGroup[1].append(processedLine)
+ else:
+ self._exceptionLineOutsideGroup(line, lineNo)
+
+ # Finally, take the generated line groups and let the child class process
+ # each one before storing the final outcome.
+ return list(map(lambda group: self._processGroup(group[0], group[1], group[2]), allGroups))
+
+
+class CheckFile(FileSplitMixin):
+ """Collection of check groups extracted from the input test file."""
+
+ def __init__(self, prefix, checkStream, fileName=None):
+ self.fileName = fileName
+ self.prefix = prefix
+ self.groups = self._parseStream(checkStream)
+
+ # Attempts to parse a check line. The regex searches for a comment symbol
+ # followed by the CHECK keyword, given attribute and a colon at the very
+ # beginning of the line. Whitespaces are ignored.
+ def _extractLine(self, prefix, line):
+ rIgnoreWhitespace = r"\s*"
+ rCommentSymbols = [r"//", r"#"]
+ regexPrefix = rIgnoreWhitespace + \
+ r"(" + r"|".join(rCommentSymbols) + r")" + \
+ rIgnoreWhitespace + \
+ prefix + r":"
+
+ # The 'match' function succeeds only if the pattern is matched at the
+ # beginning of the line.
+ match = re.match(regexPrefix, line)
+ if match is not None:
+ return line[match.end():].strip()
+ else:
+ return None
+
+ # This function is invoked on each line of the check file and returns a pair
+ # which instructs the parser how the line should be handled. If the line is to
+ # be included in the current check group, it is returned in the first value.
+ # If the line starts a new check group, the name of the group is returned in
+ # the second value.
+ def _processLine(self, line, lineNo):
+ # Lines beginning with 'CHECK-START' start a new check group.
+ startLine = self._extractLine(self.prefix + "-START", line)
+ if startLine is not None:
+ return None, startLine
+
+ # Lines starting only with 'CHECK' are matched in order.
+ plainLine = self._extractLine(self.prefix, line)
+ if plainLine is not None:
+ return (plainLine, CheckLine.Variant.InOrder, lineNo), None
+
+ # 'CHECK-DAG' lines are no-order assertions.
+ dagLine = self._extractLine(self.prefix + "-DAG", line)
+ if dagLine is not None:
+ return (dagLine, CheckLine.Variant.DAG, lineNo), None
+
+ # 'CHECK-NOT' lines are no-order negative assertions.
+ notLine = self._extractLine(self.prefix + "-NOT", line)
+ if notLine is not None:
+ return (notLine, CheckLine.Variant.Not, lineNo), None
+
+ # Other lines are ignored.
+ return None, None
+
+ def _exceptionLineOutsideGroup(self, line, lineNo):
+ Logger.fail("Check line not inside a group", self.fileName, lineNo)
+
+ # Constructs a check group from the parser-collected check lines.
+ def _processGroup(self, name, lines, lineNo):
+ checkLines = list(map(lambda line: CheckLine(line[0], line[1], self.fileName, line[2]), lines))
+ return CheckGroup(name, checkLines, self.fileName, lineNo)
+
+ def match(self, outputFile):
+ for checkGroup in self.groups:
+ # 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.
+ outputGroup = outputFile.findGroup(checkGroup.name)
+ if outputGroup is None:
+ Logger.fail("Group \"" + checkGroup.name + "\" not found in the output",
+ self.fileName, checkGroup.lineNo)
+ Logger.startTest(checkGroup.name)
+ checkGroup.match(outputGroup)
+ Logger.testPassed()
+
+
+class OutputFile(FileSplitMixin):
+ """Representation of the output generated by the test and split into groups
+ within which the checks are performed.
+
+ C1visualizer format is parsed with a state machine which differentiates
+ between the 'compilation' and 'cfg' blocks. The former marks the beginning
+ of a method. It is parsed for the method's name but otherwise ignored. Each
+ subsequent CFG block represents one stage of the compilation pipeline and
+ is parsed into an output group named "<method name> <pass name>".
+ """
+
+ class ParsingState:
+ OutsideBlock, InsideCompilationBlock, StartingCfgBlock, InsideCfgBlock = range(4)
+
+ def __init__(self, outputStream, fileName=None):
+ self.fileName = fileName
+
+ # Initialize the state machine
+ self.lastMethodName = None
+ self.state = OutputFile.ParsingState.OutsideBlock
+ self.groups = self._parseStream(outputStream)
+
+ # This function is invoked on each line of the output file and returns a pair
+ # which instructs the parser how the line should be handled. If the line is to
+ # be included in the current group, it is returned in the first value. If the
+ # line starts a new output group, the name of the group is returned in the
+ # second value.
+ def _processLine(self, line, lineNo):
+ if self.state == OutputFile.ParsingState.StartingCfgBlock:
+ # Previous line started a new 'cfg' block which means that this one must
+ # contain the name of the pass (this is enforced by C1visualizer).
+ if re.match("name\s+\"[^\"]+\"", line):
+ # Extract the pass name, prepend it with the name of the method and
+ # return as the beginning of a new group.
+ self.state = OutputFile.ParsingState.InsideCfgBlock
+ return (None, self.lastMethodName + " " + line.split("\"")[1])
+ else:
+ Logger.fail("Expected output group name", self.fileName, lineNo)
+
+ elif self.state == OutputFile.ParsingState.InsideCfgBlock:
+ if line == "end_cfg":
+ self.state = OutputFile.ParsingState.OutsideBlock
+ return (None, None)
+ else:
+ return (line, None)
+
+ elif self.state == OutputFile.ParsingState.InsideCompilationBlock:
+ # Search for the method's name. Format: method "<name>"
+ if re.match("method\s+\"[^\"]*\"", line):
+ methodName = line.split("\"")[1].strip()
+ if not methodName:
+ Logger.fail("Empty method name in output", self.fileName, lineNo)
+ self.lastMethodName = methodName
+ elif line == "end_compilation":
+ self.state = OutputFile.ParsingState.OutsideBlock
+ return (None, None)
+
+ else:
+ assert self.state == OutputFile.ParsingState.OutsideBlock
+ if line == "begin_cfg":
+ # The line starts a new group but we'll wait until the next line from
+ # which we can extract the name of the pass.
+ if self.lastMethodName is None:
+ Logger.fail("Expected method header", self.fileName, lineNo)
+ self.state = OutputFile.ParsingState.StartingCfgBlock
+ return (None, None)
+ elif line == "begin_compilation":
+ self.state = OutputFile.ParsingState.InsideCompilationBlock
+ return (None, None)
+ else:
+ Logger.fail("Output line not inside a group", self.fileName, lineNo)
+
+ # Constructs an output group from the parser-collected output lines.
+ def _processGroup(self, name, lines, lineNo):
+ return OutputGroup(name, lines, self.fileName, lineNo + 1)
+
+ def findGroup(self, name):
+ for group in self.groups:
+ if group.name == name:
+ return group
+ return None
+
+
+def ParseArguments():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("tested_file",
+ help="text file the checks should be verified against")
+ parser.add_argument("source_path", nargs="?",
+ help="path to file/folder with checking annotations")
+ parser.add_argument("--check-prefix", dest="check_prefix", default="CHECK", metavar="PREFIX",
+ help="prefix of checks in the test files (default: CHECK)")
+ parser.add_argument("--list-groups", dest="list_groups", action="store_true",
+ help="print a list of all groups found in the tested file")
+ parser.add_argument("--dump-group", dest="dump_group", metavar="GROUP",
+ help="print the contents of an output group")
+ parser.add_argument("-q", "--quiet", action="store_true",
+ help="print only errors")
+ return parser.parse_args()
+
+
+def ListGroups(outputFilename):
+ outputFile = OutputFile(open(outputFilename, "r"))
+ for group in outputFile.groups:
+ Logger.log(group.name)
+
+
+def DumpGroup(outputFilename, groupName):
+ outputFile = OutputFile(open(outputFilename, "r"))
+ group = outputFile.findGroup(groupName)
+ if group:
+ lineNo = group.lineNo
+ maxLineNo = lineNo + len(group.body)
+ lenLineNo = len(str(maxLineNo)) + 2
+ for line in group.body:
+ Logger.log((str(lineNo) + ":").ljust(lenLineNo) + line)
+ lineNo += 1
+ else:
+ Logger.fail("Group \"" + groupName + "\" not found in the output")
+
+
+# Returns a list of files to scan for check annotations in the given path. Path
+# to a file is returned as a single-element list, directories are recursively
+# traversed and all '.java' files returned.
+def FindCheckFiles(path):
+ if not path:
+ Logger.fail("No source path provided")
+ elif os.path.isfile(path):
+ return [ path ]
+ elif os.path.isdir(path):
+ foundFiles = []
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ extension = os.path.splitext(file)[1]
+ if extension in [".java", ".smali"]:
+ foundFiles.append(os.path.join(root, file))
+ return foundFiles
+ else:
+ Logger.fail("Source path \"" + path + "\" not found")
+
+
+def RunChecks(checkPrefix, checkPath, outputFilename):
+ outputBaseName = os.path.basename(outputFilename)
+ outputFile = OutputFile(open(outputFilename, "r"), outputBaseName)
+
+ for checkFilename in FindCheckFiles(checkPath):
+ checkBaseName = os.path.basename(checkFilename)
+ checkFile = CheckFile(checkPrefix, open(checkFilename, "r"), checkBaseName)
+ checkFile.match(outputFile)
+
+
+if __name__ == "__main__":
+ args = ParseArguments()
+
+ if args.quiet:
+ Logger.Verbosity = Logger.Level.Error
+
+ if args.list_groups:
+ ListGroups(args.tested_file)
+ elif args.dump_group:
+ DumpGroup(args.tested_file, args.dump_group)
+ else:
+ RunChecks(args.check_prefix, args.source_path, args.tested_file)
diff --git a/tools/checker/README b/tools/checker/README
deleted file mode 100644
index 9b23ae9..0000000
--- a/tools/checker/README
+++ /dev/null
@@ -1,54 +0,0 @@
-Checker is a testing tool which compiles a given test file and compares the
-state of the control-flow graph before and after each optimization pass
-against a set of assertions specified alongside the tests.
-
-Tests are written in Java, turned into DEX and compiled with the Optimizing
-compiler. "Check lines" are assertions formatted as comments of the Java file.
-They begin with prefix 'CHECK' followed by a pattern that the engine attempts
-to match in the compiler-generated output.
-
-Assertions are tested in groups which correspond to the individual compiler
-passes. Each group of check lines therefore must start with a 'CHECK-START'
-header which specifies the output group it should be tested against. The group
-name must exactly match one of the groups recognized in the output (they can
-be listed with the '--list-groups' command-line flag).
-
-Matching of check lines is carried out in the order of appearance in the
-source file. There are three types of check lines:
- - CHECK: Must match an output line which appears in the output group
- later than lines matched against any preceeding checks. Output
- lines must therefore match the check lines in the same order.
- These are referred to as "in-order" checks in the code.
- - CHECK-DAG: Must match an output line which appears in the output group
- later than lines matched against any preceeding in-order checks.
- In other words, the order of output lines does not matter
- between consecutive DAG checks.
- - CHECK-NOT: Must not match any output line which appears in the output group
- later than lines matched against any preceeding checks and
- earlier than lines matched against any subsequent checks.
- Surrounding non-negative checks (or boundaries of the group)
- therefore create a scope within which the assertion is verified.
-
-Check-line patterns are treated as plain text rather than regular expressions
-but are whitespace agnostic.
-
-Actual regex patterns can be inserted enclosed in '{{' and '}}' brackets. If
-curly brackets need to be used inside the body of the regex, they need to be
-enclosed in round brackets. For example, the pattern '{{foo{2}}}' will parse
-the invalid regex 'foo{2', but '{{(fo{2})}}' will match 'foo'.
-
-Regex patterns can be named and referenced later. A new variable is defined
-with '[[name:regex]]' and can be referenced with '[[name]]'. Variables are
-only valid within the scope of the defining group. Within a group they cannot
-be redefined or used undefined.
-
-Example:
- The following assertions can be placed in a Java source file:
-
- // CHECK-START: int MyClass.MyMethod() constant_folding (after)
- // CHECK: [[ID:i\d+]] IntConstant {{11|22}}
- // CHECK: Return [ [[ID]] ]
-
- The engine will attempt to match the check lines against the output of the
- group named on the first line. Together they verify that the CFG after
- constant folding returns an integer constant with value either 11 or 22.
diff --git a/tools/checker/checker.py b/tools/checker/checker.py
deleted file mode 100755
index d6c3059..0000000
--- a/tools/checker/checker.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python2
-#
-# Copyright (C) 2015 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.
-
-import argparse
-import os
-
-from common.logger import Logger
-from file_format.c1visualizer.parser import ParseC1visualizerStream
-from file_format.checker.parser import ParseCheckerStream
-from match.file import MatchFiles
-
-def ParseArguments():
- parser = argparse.ArgumentParser()
- parser.add_argument("tested_file",
- help="text file the checks should be verified against")
- parser.add_argument("source_path", nargs="?",
- help="path to file/folder with checking annotations")
- parser.add_argument("--check-prefix", dest="check_prefix", default="CHECK", metavar="PREFIX",
- help="prefix of checks in the test files (default: CHECK)")
- parser.add_argument("--list-passes", dest="list_passes", action="store_true",
- help="print a list of all passes found in the tested file")
- parser.add_argument("--dump-pass", dest="dump_pass", metavar="PASS",
- help="print a compiler pass dump")
- parser.add_argument("-q", "--quiet", action="store_true",
- help="print only errors")
- return parser.parse_args()
-
-
-def ListPasses(outputFilename):
- c1File = ParseC1visualizerStream(os.path.basename(outputFilename), open(outputFilename, "r"))
- for compiler_pass in c1File.passes:
- Logger.log(compiler_pass.name)
-
-
-def DumpPass(outputFilename, passName):
- c1File = ParseC1visualizerStream(os.path.basename(outputFilename), open(outputFilename, "r"))
- compiler_pass = c1File.findPass(passName)
- if compiler_pass:
- maxLineNo = compiler_pass.startLineNo + len(compiler_pass.body)
- lenLineNo = len(str(maxLineNo)) + 2
- curLineNo = compiler_pass.startLineNo
- for line in compiler_pass.body:
- Logger.log((str(curLineNo) + ":").ljust(lenLineNo) + line)
- curLineNo += 1
- else:
- Logger.fail("Pass \"" + passName + "\" not found in the output")
-
-
-def FindCheckerFiles(path):
- """ Returns a list of files to scan for check annotations in the given path.
- Path to a file is returned as a single-element list, directories are
- recursively traversed and all '.java' files returned.
- """
- if not path:
- Logger.fail("No source path provided")
- elif os.path.isfile(path):
- return [ path ]
- elif os.path.isdir(path):
- foundFiles = []
- for root, dirs, files in os.walk(path):
- for file in files:
- extension = os.path.splitext(file)[1]
- if extension in [".java", ".smali"]:
- foundFiles.append(os.path.join(root, file))
- return foundFiles
- else:
- Logger.fail("Source path \"" + path + "\" not found")
-
-
-def RunTests(checkPrefix, checkPath, outputFilename):
- c1File = ParseC1visualizerStream(os.path.basename(outputFilename), open(outputFilename, "r"))
- for checkFilename in FindCheckerFiles(checkPath):
- checkerFile = ParseCheckerStream(os.path.basename(checkFilename),
- checkPrefix,
- open(checkFilename, "r"))
- MatchFiles(checkerFile, c1File)
-
-
-if __name__ == "__main__":
- args = ParseArguments()
-
- if args.quiet:
- Logger.Verbosity = Logger.Level.Error
-
- if args.list_passes:
- ListPasses(args.tested_file)
- elif args.dump_pass:
- DumpPass(args.tested_file, args.dump_pass)
- else:
- RunTests(args.check_prefix, args.source_path, args.tested_file)
diff --git a/tools/checker/common/__init__.py b/tools/checker/common/__init__.py
deleted file mode 100644
index 33ef6de..0000000
--- a/tools/checker/common/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright (C) 2015 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.
diff --git a/tools/checker/common/logger.py b/tools/checker/common/logger.py
deleted file mode 100644
index 6f71f78..0000000
--- a/tools/checker/common/logger.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# Copyright (C) 2015 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 __future__ import print_function
-import sys
-
-class Logger(object):
-
- class Level(object):
- NoOutput, Error, Info = range(3)
-
- class Color(object):
- Default, Blue, Gray, Purple, Red = range(5)
-
- @staticmethod
- def terminalCode(color, out=sys.stdout):
- if not out.isatty():
- return ''
- elif color == Logger.Color.Blue:
- return '\033[94m'
- elif color == Logger.Color.Gray:
- return '\033[37m'
- elif color == Logger.Color.Purple:
- return '\033[95m'
- elif color == Logger.Color.Red:
- return '\033[91m'
- else:
- return '\033[0m'
-
- Verbosity = Level.Info
-
- @staticmethod
- def log(text, level=Level.Info, color=Color.Default, newLine=True, out=sys.stdout):
- if level <= Logger.Verbosity:
- text = Logger.Color.terminalCode(color, out) + text + \
- Logger.Color.terminalCode(Logger.Color.Default, out)
- if newLine:
- print(text, file=out)
- else:
- print(text, end="", file=out)
- out.flush()
-
- @staticmethod
- def fail(msg, file=None, line=-1):
- location = ""
- if file:
- location += file + ":"
- if line > 0:
- location += str(line) + ":"
- if location:
- location += " "
-
- Logger.log(location, Logger.Level.Error, color=Logger.Color.Gray, newLine=False, out=sys.stderr)
- Logger.log("error: ", Logger.Level.Error, color=Logger.Color.Red, newLine=False, out=sys.stderr)
- Logger.log(msg, Logger.Level.Error, out=sys.stderr)
- sys.exit(msg)
-
- @staticmethod
- def startTest(name):
- Logger.log("TEST ", color=Logger.Color.Purple, newLine=False)
- Logger.log(name + "... ", newLine=False)
-
- @staticmethod
- def testPassed():
- Logger.log("PASS", color=Logger.Color.Blue)
-
- @staticmethod
- def testFailed(msg, file=None, line=-1):
- Logger.log("FAIL", color=Logger.Color.Red)
- Logger.fail(msg, file, line)
diff --git a/tools/checker/common/mixins.py b/tools/checker/common/mixins.py
deleted file mode 100644
index f44e46d..0000000
--- a/tools/checker/common/mixins.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (C) 2015 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.
-
-class EqualityMixin:
- """ Object equality via equality of dictionaries. """
-
- def __eq__(self, other):
- return isinstance(other, self.__class__) \
- and self.__dict__ == other.__dict__
-
-class PrintableMixin:
- """ Prints object as name-dictionary pair. """
-
- def __repr__(self):
- return "<%s: %s>" % (type(self).__name__, str(self.__dict__))
diff --git a/tools/checker/common/testing.py b/tools/checker/common/testing.py
deleted file mode 100644
index 2014afe..0000000
--- a/tools/checker/common/testing.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (C) 2015 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.
-
-def ToUnicode(string):
- """ Converts a string into Unicode.
-
- This is a delegate function for the built-in `unicode`. It checks if the input
- is not `None`, because `unicode` turns it into an actual "None" string.
- """
- assert string is not None
- return unicode(string)
diff --git a/tools/checker/file_format/__init__.py b/tools/checker/file_format/__init__.py
deleted file mode 100644
index 33ef6de..0000000
--- a/tools/checker/file_format/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright (C) 2015 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.
diff --git a/tools/checker/file_format/c1visualizer/__init__.py b/tools/checker/file_format/c1visualizer/__init__.py
deleted file mode 100644
index 33ef6de..0000000
--- a/tools/checker/file_format/c1visualizer/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright (C) 2015 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.
diff --git a/tools/checker/file_format/c1visualizer/parser.py b/tools/checker/file_format/c1visualizer/parser.py
deleted file mode 100644
index f34161b..0000000
--- a/tools/checker/file_format/c1visualizer/parser.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# Copyright (C) 2015 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 common.logger import Logger
-from file_format.common import SplitStream
-from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
-
-import re
-
-class C1ParserState:
- OutsideBlock, InsideCompilationBlock, StartingCfgBlock, InsideCfgBlock = range(4)
-
- def __init__(self):
- self.currentState = C1ParserState.OutsideBlock
- self.lastMethodName = None
-
-def __parseC1Line(line, lineNo, state, fileName):
- """ This function is invoked on each line of the output file and returns
- a pair which instructs the parser how the line should be handled. If the
- line is to be included in the current group, it is returned in the first
- value. If the line starts a new output group, the name of the group is
- returned in the second value.
- """
- if state.currentState == C1ParserState.StartingCfgBlock:
- # Previous line started a new 'cfg' block which means that this one must
- # contain the name of the pass (this is enforced by C1visualizer).
- if re.match("name\s+\"[^\"]+\"", line):
- # Extract the pass name, prepend it with the name of the method and
- # return as the beginning of a new group.
- state.currentState = C1ParserState.InsideCfgBlock
- return (None, state.lastMethodName + " " + line.split("\"")[1])
- else:
- Logger.fail("Expected output group name", fileName, lineNo)
-
- elif state.currentState == C1ParserState.InsideCfgBlock:
- if line == "end_cfg":
- state.currentState = C1ParserState.OutsideBlock
- return (None, None)
- else:
- return (line, None)
-
- elif state.currentState == C1ParserState.InsideCompilationBlock:
- # Search for the method's name. Format: method "<name>"
- if re.match("method\s+\"[^\"]*\"", line):
- methodName = line.split("\"")[1].strip()
- if not methodName:
- Logger.fail("Empty method name in output", fileName, lineNo)
- state.lastMethodName = methodName
- elif line == "end_compilation":
- state.currentState = C1ParserState.OutsideBlock
- return (None, None)
-
- else:
- assert state.currentState == C1ParserState.OutsideBlock
- if line == "begin_cfg":
- # The line starts a new group but we'll wait until the next line from
- # which we can extract the name of the pass.
- if state.lastMethodName is None:
- Logger.fail("Expected method header", fileName, lineNo)
- state.currentState = C1ParserState.StartingCfgBlock
- return (None, None)
- elif line == "begin_compilation":
- state.currentState = C1ParserState.InsideCompilationBlock
- return (None, None)
- else:
- Logger.fail("C1visualizer line not inside a group", fileName, lineNo)
-
-def ParseC1visualizerStream(fileName, stream):
- c1File = C1visualizerFile(fileName)
- state = C1ParserState()
- fnProcessLine = lambda line, lineNo: __parseC1Line(line, lineNo, state, fileName)
- fnLineOutsideChunk = lambda line, lineNo: \
- Logger.fail("C1visualizer line not inside a group", fileName, lineNo)
- for passName, passLines, startLineNo in SplitStream(stream, fnProcessLine, fnLineOutsideChunk):
- C1visualizerPass(c1File, passName, passLines, startLineNo + 1)
- return c1File
diff --git a/tools/checker/file_format/c1visualizer/struct.py b/tools/checker/file_format/c1visualizer/struct.py
deleted file mode 100644
index 0462765..0000000
--- a/tools/checker/file_format/c1visualizer/struct.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright (C) 2015 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 common.logger import Logger
-from common.mixins import PrintableMixin
-
-class C1visualizerFile(PrintableMixin):
-
- def __init__(self, fileName):
- self.fileName = fileName
- self.passes = []
-
- def addPass(self, new_pass):
- self.passes.append(new_pass)
-
- def findPass(self, name):
- for entry in self.passes:
- if entry.name == name:
- return entry
- return None
-
- def __eq__(self, other):
- return isinstance(other, self.__class__) \
- and self.passes == other.passes
-
-
-class C1visualizerPass(PrintableMixin):
-
- def __init__(self, parent, name, body, startLineNo):
- self.parent = parent
- self.name = name
- self.body = body
- self.startLineNo = startLineNo
-
- if not self.name:
- Logger.fail("C1visualizer pass does not have a name", self.fileName, self.startLineNo)
- if not self.body:
- Logger.fail("C1visualizer pass does not have a body", self.fileName, self.startLineNo)
-
- self.parent.addPass(self)
-
- @property
- def fileName(self):
- return self.parent.fileName
-
- def __eq__(self, other):
- return isinstance(other, self.__class__) \
- and self.name == other.name \
- and self.body == other.body
diff --git a/tools/checker/file_format/c1visualizer/test.py b/tools/checker/file_format/c1visualizer/test.py
deleted file mode 100644
index 812a4cf..0000000
--- a/tools/checker/file_format/c1visualizer/test.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/env python2
-#
-# 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 common.testing import ToUnicode
-from file_format.c1visualizer.parser import ParseC1visualizerStream
-from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
-
-import io
-import unittest
-
-class C1visualizerParser_Test(unittest.TestCase):
-
- def createFile(self, passList):
- """ Creates an instance of CheckerFile from provided info.
-
- Data format: [ ( <case-name>, [ ( <text>, <assert-variant> ), ... ] ), ... ]
- """
- c1File = C1visualizerFile("<c1_file>")
- for passEntry in passList:
- passName = passEntry[0]
- passBody = passEntry[1]
- c1Pass = C1visualizerPass(c1File, passName, passBody, 0)
- return c1File
-
- def assertParsesTo(self, c1Text, expectedData):
- expectedFile = self.createFile(expectedData)
- actualFile = ParseC1visualizerStream("<c1_file>", io.StringIO(ToUnicode(c1Text)))
- return self.assertEqual(expectedFile, actualFile)
-
- def test_EmptyFile(self):
- self.assertParsesTo("", [])
-
- def test_SingleGroup(self):
- self.assertParsesTo(
- """
- begin_compilation
- method "MyMethod"
- end_compilation
- begin_cfg
- name "pass1"
- foo
- bar
- end_cfg
- """,
- [ ( "MyMethod pass1", [ "foo", "bar" ] ) ])
-
- def test_MultipleGroups(self):
- self.assertParsesTo(
- """
- begin_compilation
- name "xyz1"
- method "MyMethod1"
- date 1234
- end_compilation
- begin_cfg
- name "pass1"
- foo
- bar
- end_cfg
- begin_cfg
- name "pass2"
- abc
- def
- end_cfg
- """,
- [ ( "MyMethod1 pass1", [ "foo", "bar" ] ),
- ( "MyMethod1 pass2", [ "abc", "def" ] ) ])
- self.assertParsesTo(
- """
- begin_compilation
- name "xyz1"
- method "MyMethod1"
- date 1234
- end_compilation
- begin_cfg
- name "pass1"
- foo
- bar
- end_cfg
- begin_compilation
- name "xyz2"
- method "MyMethod2"
- date 5678
- end_compilation
- begin_cfg
- name "pass2"
- abc
- def
- end_cfg
- """,
- [ ( "MyMethod1 pass1", [ "foo", "bar" ] ),
- ( "MyMethod2 pass2", [ "abc", "def" ] ) ])
diff --git a/tools/checker/file_format/checker/__init__.py b/tools/checker/file_format/checker/__init__.py
deleted file mode 100644
index 33ef6de..0000000
--- a/tools/checker/file_format/checker/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright (C) 2015 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.
diff --git a/tools/checker/file_format/checker/parser.py b/tools/checker/file_format/checker/parser.py
deleted file mode 100644
index 93fa093..0000000
--- a/tools/checker/file_format/checker/parser.py
+++ /dev/null
@@ -1,142 +0,0 @@
-# Copyright (C) 2015 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 file_format.common import SplitStream
-from file_format.checker.struct import CheckerFile, TestCase, TestAssertion, RegexExpression
-
-import re
-
-def __extractLine(prefix, line):
- """ Attempts to parse a check line. The regex searches for a comment symbol
- followed by the CHECK keyword, given attribute and a colon at the very
- beginning of the line. Whitespaces are ignored.
- """
- rIgnoreWhitespace = r"\s*"
- rCommentSymbols = [r"//", r"#"]
- regexPrefix = rIgnoreWhitespace + \
- r"(" + r"|".join(rCommentSymbols) + r")" + \
- rIgnoreWhitespace + \
- prefix + r":"
-
- # The 'match' function succeeds only if the pattern is matched at the
- # beginning of the line.
- match = re.match(regexPrefix, line)
- if match is not None:
- return line[match.end():].strip()
- else:
- return None
-
-def __processLine(line, lineNo, prefix):
- """ This function is invoked on each line of the check file and returns a pair
- which instructs the parser how the line should be handled. If the line is
- to be included in the current check group, it is returned in the first
- value. If the line starts a new check group, the name of the group is
- returned in the second value.
- """
- # Lines beginning with 'CHECK-START' start a new test case.
- startLine = __extractLine(prefix + "-START", line)
- if startLine is not None:
- return None, startLine
-
- # Lines starting only with 'CHECK' are matched in order.
- plainLine = __extractLine(prefix, line)
- if plainLine is not None:
- return (plainLine, TestAssertion.Variant.InOrder, lineNo), None
-
- # 'CHECK-DAG' lines are no-order assertions.
- dagLine = __extractLine(prefix + "-DAG", line)
- if dagLine is not None:
- return (dagLine, TestAssertion.Variant.DAG, lineNo), None
-
- # 'CHECK-NOT' lines are no-order negative assertions.
- notLine = __extractLine(prefix + "-NOT", line)
- if notLine is not None:
- return (notLine, TestAssertion.Variant.Not, lineNo), None
-
- # Other lines are ignored.
- return None, None
-
-def __isMatchAtStart(match):
- """ Tests if the given Match occurred at the beginning of the line. """
- return (match is not None) and (match.start() == 0)
-
-def __firstMatch(matches, string):
- """ Takes in a list of Match objects and returns the minimal start point among
- them. If there aren't any successful matches it returns the length of
- the searched string.
- """
- starts = map(lambda m: len(string) if m is None else m.start(), matches)
- return min(starts)
-
-def ParseCheckerAssertion(parent, line, variant, lineNo):
- """ This method parses the content of a check line stripped of the initial
- comment symbol and the CHECK keyword.
- """
- assertion = TestAssertion(parent, variant, line, lineNo)
- # Loop as long as there is something to parse.
- while line:
- # Search for the nearest occurrence of the special markers.
- matchWhitespace = re.search(r"\s+", line)
- matchPattern = re.search(RegexExpression.Regex.regexPattern, line)
- matchVariableReference = re.search(RegexExpression.Regex.regexVariableReference, line)
- matchVariableDefinition = re.search(RegexExpression.Regex.regexVariableDefinition, line)
-
- # If one of the above was identified at the current position, extract them
- # from the line, parse them and add to the list of line parts.
- if __isMatchAtStart(matchWhitespace):
- # A whitespace in the check line creates a new separator of line parts.
- # This allows for ignored output between the previous and next parts.
- line = line[matchWhitespace.end():]
- assertion.addExpression(RegexExpression.createSeparator())
- elif __isMatchAtStart(matchPattern):
- pattern = line[0:matchPattern.end()]
- pattern = pattern[2:-2]
- line = line[matchPattern.end():]
- assertion.addExpression(RegexExpression.createPattern(pattern))
- elif __isMatchAtStart(matchVariableReference):
- var = line[0:matchVariableReference.end()]
- line = line[matchVariableReference.end():]
- name = var[2:-2]
- assertion.addExpression(RegexExpression.createVariableReference(name))
- elif __isMatchAtStart(matchVariableDefinition):
- var = line[0:matchVariableDefinition.end()]
- line = line[matchVariableDefinition.end():]
- colonPos = var.find(":")
- name = var[2:colonPos]
- body = var[colonPos+1:-2]
- assertion.addExpression(RegexExpression.createVariableDefinition(name, body))
- else:
- # If we're not currently looking at a special marker, this is a plain
- # text match all the way until the first special marker (or the end
- # of the line).
- firstMatch = __firstMatch([ matchWhitespace,
- matchPattern,
- matchVariableReference,
- matchVariableDefinition ],
- line)
- text = line[0:firstMatch]
- line = line[firstMatch:]
- assertion.addExpression(RegexExpression.createText(text))
- return assertion
-
-def ParseCheckerStream(fileName, prefix, stream):
- checkerFile = CheckerFile(fileName)
- fnProcessLine = lambda line, lineNo: __processLine(line, lineNo, prefix)
- fnLineOutsideChunk = lambda line, lineNo: \
- Logger.fail("C1visualizer line not inside a group", fileName, lineNo)
- for caseName, caseLines, startLineNo in SplitStream(stream, fnProcessLine, fnLineOutsideChunk):
- testCase = TestCase(checkerFile, caseName, startLineNo)
- for caseLine in caseLines:
- ParseCheckerAssertion(testCase, caseLine[0], caseLine[1], caseLine[2])
- return checkerFile
diff --git a/tools/checker/file_format/checker/struct.py b/tools/checker/file_format/checker/struct.py
deleted file mode 100644
index d5cdc3b..0000000
--- a/tools/checker/file_format/checker/struct.py
+++ /dev/null
@@ -1,156 +0,0 @@
-# Copyright (C) 2015 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 common.logger import Logger
-from common.mixins import EqualityMixin, PrintableMixin
-
-import re
-
-class CheckerFile(PrintableMixin):
-
- def __init__(self, fileName):
- self.fileName = fileName
- self.testCases = []
-
- def addTestCase(self, new_test_case):
- self.testCases.append(new_test_case)
-
- def __eq__(self, other):
- return isinstance(other, self.__class__) \
- and self.testCases == other.testCases
-
-
-class TestCase(PrintableMixin):
-
- def __init__(self, parent, name, startLineNo):
- assert isinstance(parent, CheckerFile)
-
- self.parent = parent
- self.name = name
- self.assertions = []
- self.startLineNo = startLineNo
-
- if not self.name:
- Logger.fail("Test case does not have a name", self.parent.fileName, self.startLineNo)
-
- self.parent.addTestCase(self)
-
- @property
- def fileName(self):
- return self.parent.fileName
-
- def addAssertion(self, new_assertion):
- self.assertions.append(new_assertion)
-
- def __eq__(self, other):
- return isinstance(other, self.__class__) \
- and self.name == other.name \
- and self.assertions == other.assertions
-
-
-class TestAssertion(PrintableMixin):
-
- class Variant(object):
- """Supported types of assertions."""
- InOrder, DAG, Not = range(3)
-
- def __init__(self, parent, variant, originalText, lineNo):
- assert isinstance(parent, TestCase)
-
- self.parent = parent
- self.variant = variant
- self.expressions = []
- self.lineNo = lineNo
- self.originalText = originalText
-
- self.parent.addAssertion(self)
-
- @property
- def fileName(self):
- return self.parent.fileName
-
- def addExpression(self, new_expression):
- assert isinstance(new_expression, RegexExpression)
- if self.variant == TestAssertion.Variant.Not:
- if new_expression.variant == RegexExpression.Variant.VarDef:
- Logger.fail("CHECK-NOT lines cannot define variables", self.fileName, self.lineNo)
- self.expressions.append(new_expression)
-
- def toRegex(self):
- """ Returns a regex pattern for this entire assertion. Only used in tests. """
- regex = ""
- for expression in self.expressions:
- if expression.variant == RegexExpression.Variant.Separator:
- regex = regex + ", "
- else:
- regex = regex + "(" + expression.pattern + ")"
- return regex
-
- def __eq__(self, other):
- return isinstance(other, self.__class__) \
- and self.variant == other.variant \
- and self.expressions == other.expressions
-
-
-class RegexExpression(EqualityMixin, PrintableMixin):
-
- class Variant(object):
- """Supported language constructs."""
- Text, Pattern, VarRef, VarDef, Separator = range(5)
-
- class Regex(object):
- rName = r"([a-zA-Z][a-zA-Z0-9]*)"
- rRegex = r"(.+?)"
- rPatternStartSym = r"(\{\{)"
- rPatternEndSym = r"(\}\})"
- rVariableStartSym = r"(\[\[)"
- rVariableEndSym = r"(\]\])"
- rVariableSeparator = r"(:)"
-
- regexPattern = rPatternStartSym + rRegex + rPatternEndSym
- regexVariableReference = rVariableStartSym + rName + rVariableEndSym
- regexVariableDefinition = rVariableStartSym + rName + rVariableSeparator + rRegex + rVariableEndSym
-
- def __init__(self, variant, name, pattern):
- self.variant = variant
- self.name = name
- self.pattern = pattern
-
- def __eq__(self, other):
- return isinstance(other, self.__class__) \
- and self.variant == other.variant \
- and self.name == other.name \
- and self.pattern == other.pattern
-
- @staticmethod
- def createSeparator():
- return RegexExpression(RegexExpression.Variant.Separator, None, None)
-
- @staticmethod
- def createText(text):
- return RegexExpression(RegexExpression.Variant.Text, None, re.escape(text))
-
- @staticmethod
- def createPattern(pattern):
- return RegexExpression(RegexExpression.Variant.Pattern, None, pattern)
-
- @staticmethod
- def createVariableReference(name):
- assert re.match(RegexExpression.Regex.rName, name)
- return RegexExpression(RegexExpression.Variant.VarRef, name, None)
-
- @staticmethod
- def createVariableDefinition(name, pattern):
- assert re.match(RegexExpression.Regex.rName, name)
- return RegexExpression(RegexExpression.Variant.VarDef, name, pattern)
diff --git a/tools/checker/file_format/checker/test.py b/tools/checker/file_format/checker/test.py
deleted file mode 100644
index 167c888..0000000
--- a/tools/checker/file_format/checker/test.py
+++ /dev/null
@@ -1,238 +0,0 @@
-#!/usr/bin/env python2
-#
-# 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 common.testing import ToUnicode
-from file_format.checker.parser import ParseCheckerStream
-from file_format.checker.struct import CheckerFile, TestCase, TestAssertion, RegexExpression
-
-import io
-import unittest
-
-CheckerException = SystemExit
-
-class CheckerParser_PrefixTest(unittest.TestCase):
-
- def tryParse(self, string):
- checkerText = u"// CHECK-START: pass\n" + ToUnicode(string)
- checkFile = ParseCheckerStream("<test-file>", "CHECK", io.StringIO(checkerText))
- self.assertEqual(len(checkFile.testCases), 1)
- testCase = checkFile.testCases[0]
- return len(testCase.assertions) != 0
-
- def test_InvalidFormat(self):
- self.assertFalse(self.tryParse("CHECK"))
- self.assertFalse(self.tryParse(":CHECK"))
- self.assertFalse(self.tryParse("CHECK:"))
- self.assertFalse(self.tryParse("//CHECK"))
- self.assertFalse(self.tryParse("#CHECK"))
-
- self.assertTrue(self.tryParse("//CHECK:foo"))
- self.assertTrue(self.tryParse("#CHECK:bar"))
-
- def test_InvalidLabel(self):
- self.assertFalse(self.tryParse("//ACHECK:foo"))
- self.assertFalse(self.tryParse("#ACHECK:foo"))
-
- def test_NotFirstOnTheLine(self):
- self.assertFalse(self.tryParse("A// CHECK: foo"))
- self.assertFalse(self.tryParse("A # CHECK: foo"))
- self.assertFalse(self.tryParse("// // CHECK: foo"))
- self.assertFalse(self.tryParse("# # CHECK: foo"))
-
- def test_WhitespaceAgnostic(self):
- self.assertTrue(self.tryParse(" //CHECK: foo"))
- self.assertTrue(self.tryParse("// CHECK: foo"))
- self.assertTrue(self.tryParse(" //CHECK: foo"))
- self.assertTrue(self.tryParse("// CHECK: foo"))
-
-
-class CheckerParser_RegexExpressionTest(unittest.TestCase):
-
- def parseAssertion(self, string, variant=""):
- checkerText = u"// CHECK-START: pass\n// CHECK" + ToUnicode(variant) + u": " + ToUnicode(string)
- checkerFile = ParseCheckerStream("<test-file>", "CHECK", io.StringIO(checkerText))
- self.assertEqual(len(checkerFile.testCases), 1)
- testCase = checkerFile.testCases[0]
- self.assertEqual(len(testCase.assertions), 1)
- return testCase.assertions[0]
-
- def parseExpression(self, string):
- line = self.parseAssertion(string)
- self.assertEqual(1, len(line.expressions))
- return line.expressions[0]
-
- def assertEqualsRegex(self, string, expected):
- self.assertEqual(expected, self.parseAssertion(string).toRegex())
-
- def assertEqualsText(self, string, text):
- self.assertEqual(self.parseExpression(string), RegexExpression.createText(text))
-
- def assertEqualsPattern(self, string, pattern):
- self.assertEqual(self.parseExpression(string), RegexExpression.createPattern(pattern))
-
- def assertEqualsVarRef(self, string, name):
- self.assertEqual(self.parseExpression(string), RegexExpression.createVariableReference(name))
-
- def assertEqualsVarDef(self, string, name, pattern):
- self.assertEqual(self.parseExpression(string),
- RegexExpression.createVariableDefinition(name, pattern))
-
- def assertVariantNotEqual(self, string, variant):
- self.assertNotEqual(variant, self.parseExpression(string).variant)
-
- # Test that individual parts of the line are recognized
-
- def test_TextOnly(self):
- self.assertEqualsText("foo", "foo")
- self.assertEqualsText(" foo ", "foo")
- self.assertEqualsRegex("f$o^o", "(f\$o\^o)")
-
- def test_PatternOnly(self):
- self.assertEqualsPattern("{{a?b.c}}", "a?b.c")
-
- def test_VarRefOnly(self):
- self.assertEqualsVarRef("[[ABC]]", "ABC")
-
- def test_VarDefOnly(self):
- self.assertEqualsVarDef("[[ABC:a?b.c]]", "ABC", "a?b.c")
-
- def test_TextWithWhitespace(self):
- self.assertEqualsRegex("foo bar", "(foo), (bar)")
- self.assertEqualsRegex("foo bar", "(foo), (bar)")
-
- def test_TextWithRegex(self):
- self.assertEqualsRegex("foo{{abc}}bar", "(foo)(abc)(bar)")
-
- def test_TextWithVar(self):
- self.assertEqualsRegex("foo[[ABC:abc]]bar", "(foo)(abc)(bar)")
-
- def test_PlainWithRegexAndWhitespaces(self):
- self.assertEqualsRegex("foo {{abc}}bar", "(foo), (abc)(bar)")
- self.assertEqualsRegex("foo{{abc}} bar", "(foo)(abc), (bar)")
- self.assertEqualsRegex("foo {{abc}} bar", "(foo), (abc), (bar)")
-
- def test_PlainWithVarAndWhitespaces(self):
- self.assertEqualsRegex("foo [[ABC:abc]]bar", "(foo), (abc)(bar)")
- self.assertEqualsRegex("foo[[ABC:abc]] bar", "(foo)(abc), (bar)")
- self.assertEqualsRegex("foo [[ABC:abc]] bar", "(foo), (abc), (bar)")
-
- def test_AllKinds(self):
- self.assertEqualsRegex("foo [[ABC:abc]]{{def}}bar", "(foo), (abc)(def)(bar)")
- self.assertEqualsRegex("foo[[ABC:abc]] {{def}}bar", "(foo)(abc), (def)(bar)")
- self.assertEqualsRegex("foo [[ABC:abc]] {{def}} bar", "(foo), (abc), (def), (bar)")
-
- # # Test that variables and patterns are parsed correctly
-
- def test_ValidPattern(self):
- self.assertEqualsPattern("{{abc}}", "abc")
- self.assertEqualsPattern("{{a[b]c}}", "a[b]c")
- self.assertEqualsPattern("{{(a{bc})}}", "(a{bc})")
-
- def test_ValidRef(self):
- self.assertEqualsVarRef("[[ABC]]", "ABC")
- self.assertEqualsVarRef("[[A1BC2]]", "A1BC2")
-
- def test_ValidDef(self):
- self.assertEqualsVarDef("[[ABC:abc]]", "ABC", "abc")
- self.assertEqualsVarDef("[[ABC:ab:c]]", "ABC", "ab:c")
- self.assertEqualsVarDef("[[ABC:a[b]c]]", "ABC", "a[b]c")
- self.assertEqualsVarDef("[[ABC:(a[bc])]]", "ABC", "(a[bc])")
-
- def test_Empty(self):
- self.assertVariantNotEqual("{{}}", RegexExpression.Variant.Pattern)
- self.assertVariantNotEqual("[[]]", RegexExpression.Variant.VarRef)
- self.assertVariantNotEqual("[[:]]", RegexExpression.Variant.VarDef)
-
- def test_InvalidVarName(self):
- self.assertVariantNotEqual("[[0ABC]]", RegexExpression.Variant.VarRef)
- self.assertVariantNotEqual("[[AB=C]]", RegexExpression.Variant.VarRef)
- self.assertVariantNotEqual("[[ABC=]]", RegexExpression.Variant.VarRef)
- self.assertVariantNotEqual("[[0ABC:abc]]", RegexExpression.Variant.VarDef)
- self.assertVariantNotEqual("[[AB=C:abc]]", RegexExpression.Variant.VarDef)
- self.assertVariantNotEqual("[[ABC=:abc]]", RegexExpression.Variant.VarDef)
-
- def test_BodyMatchNotGreedy(self):
- self.assertEqualsRegex("{{abc}}{{def}}", "(abc)(def)")
- self.assertEqualsRegex("[[ABC:abc]][[DEF:def]]", "(abc)(def)")
-
- def test_NoVarDefsInNotChecks(self):
- with self.assertRaises(CheckerException):
- self.parseAssertion("[[ABC:abc]]", "-NOT")
-
-
-class CheckerParser_FileLayoutTest(unittest.TestCase):
-
- # Creates an instance of CheckerFile from provided info.
- # Data format: [ ( <case-name>, [ ( <text>, <assert-variant> ), ... ] ), ... ]
- def createFile(self, caseList):
- testFile = CheckerFile("<test_file>")
- for caseEntry in caseList:
- caseName = caseEntry[0]
- testCase = TestCase(testFile, caseName, 0)
- assertionList = caseEntry[1]
- for assertionEntry in assertionList:
- content = assertionEntry[0]
- variant = assertionEntry[1]
- assertion = TestAssertion(testCase, variant, content, 0)
- assertion.addExpression(RegexExpression.createText(content))
- return testFile
-
- def assertParsesTo(self, checkerText, expectedData):
- expectedFile = self.createFile(expectedData)
- actualFile = ParseCheckerStream("<test_file>", "CHECK", io.StringIO(ToUnicode(checkerText)))
- return self.assertEqual(expectedFile, actualFile)
-
- def test_EmptyFile(self):
- self.assertParsesTo("", [])
-
- def test_SingleGroup(self):
- self.assertParsesTo(
- """
- // CHECK-START: Example Group
- // CHECK: foo
- // CHECK: bar
- """,
- [ ( "Example Group", [ ("foo", TestAssertion.Variant.InOrder),
- ("bar", TestAssertion.Variant.InOrder) ] ) ])
-
- def test_MultipleGroups(self):
- self.assertParsesTo(
- """
- // CHECK-START: Example Group1
- // CHECK: foo
- // CHECK: bar
- // CHECK-START: Example Group2
- // CHECK: abc
- // CHECK: def
- """,
- [ ( "Example Group1", [ ("foo", TestAssertion.Variant.InOrder),
- ("bar", TestAssertion.Variant.InOrder) ] ),
- ( "Example Group2", [ ("abc", TestAssertion.Variant.InOrder),
- ("def", TestAssertion.Variant.InOrder) ] ) ])
-
- def test_AssertionVariants(self):
- self.assertParsesTo(
- """
- // CHECK-START: Example Group
- // CHECK: foo
- // CHECK-NOT: bar
- // CHECK-DAG: abc
- // CHECK-DAG: def
- """,
- [ ( "Example Group", [ ("foo", TestAssertion.Variant.InOrder),
- ("bar", TestAssertion.Variant.Not),
- ("abc", TestAssertion.Variant.DAG),
- ("def", TestAssertion.Variant.DAG) ] ) ])
diff --git a/tools/checker/match/__init__.py b/tools/checker/match/__init__.py
deleted file mode 100644
index 33ef6de..0000000
--- a/tools/checker/match/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright (C) 2015 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.
diff --git a/tools/checker/match/file.py b/tools/checker/match/file.py
deleted file mode 100644
index d787fe5..0000000
--- a/tools/checker/match/file.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# Copyright (C) 2015 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 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
-
-def __headAndTail(list):
- return list[0], list[1:]
-
-def __splitByVariant(lines, variant):
- """ Splits a list of check lines at index 'i' such that lines[i] is the first
- element whose variant is not equal to the given parameter.
- """
- i = 0
- while i < len(lines) and lines[i].variant == variant:
- i += 1
- return lines[:i], lines[i:]
-
-def __nextIndependentChecks(checkLines):
- """ Extracts the first sequence of check lines which are independent of each
- other's match location, i.e. either consecutive DAG lines or a single
- InOrder line. Any Not lines preceeding this sequence are also extracted.
- """
- notChecks, checkLines = __splitByVariant(checkLines, TestAssertion.Variant.Not)
- if not checkLines:
- return notChecks, [], []
-
- head, tail = __headAndTail(checkLines)
- if head.variant == TestAssertion.Variant.InOrder:
- return notChecks, [head], tail
- else:
- assert head.variant == TestAssertion.Variant.DAG
- independentChecks, checkLines = __splitByVariant(checkLines, TestAssertion.Variant.DAG)
- return notChecks, independentChecks, checkLines
-
-def __findFirstMatch(checkLine, outputLines, startLineNo, lineFilter, varState):
- """ If successful, returns the line number of the first output line matching
- the check line and the updated variable state. Otherwise returns -1 and
- None, respectively. The 'lineFilter' parameter can be used to supply a
- list of line numbers (counting from 1) which should be skipped.
- """
- matchLineNo = startLineNo
- for outputLine in outputLines:
- if matchLineNo not in lineFilter:
- newVarState = MatchLines(checkLine, outputLine, varState)
- if newVarState is not None:
- return matchLineNo, newVarState
- matchLineNo += 1
- return -1, None
-
-def __matchIndependentChecks(checkLines, outputLines, startLineNo, varState):
- """ Matches the given positive check lines against the output in order of
- appearance. Variable state is propagated but the scope of the search
- remains the same for all checks. Each output line can only be matched
- once. If all check lines are matched, the resulting variable state is
- returned together with the remaining output. The function also returns
- output lines which appear before either of the matched lines so they can
- be tested against Not checks.
- """
- # If no checks are provided, skip over the entire output.
- if not checkLines:
- return outputLines, [], startLineNo + len(outputLines), varState
-
- # Keep track of which lines have been matched.
- matchedLines = []
-
- # Find first unused output line which matches each check line.
- for checkLine in checkLines:
- matchLineNo, varState = \
- __findFirstMatch(checkLine, outputLines, startLineNo, matchedLines, varState)
- if varState is None:
- Logger.testFailed("Could not match check line \"" + checkLine.originalText + "\" " +
- "starting from output line " + str(startLineNo),
- checkLine.fileName, checkLine.lineNo)
- matchedLines.append(matchLineNo)
-
- # Return new variable state and the output lines which lie outside the
- # match locations of this independent group.
- minMatchLineNo = min(matchedLines)
- maxMatchLineNo = max(matchedLines)
- preceedingLines = outputLines[:minMatchLineNo - startLineNo]
- remainingLines = outputLines[maxMatchLineNo - startLineNo + 1:]
- return preceedingLines, remainingLines, maxMatchLineNo + 1, varState
-
-def __matchNotLines(checkLines, outputLines, startLineNo, varState):
- """ Makes sure that the given check lines do not match any of the given output
- lines. Variable state does not change.
- """
- for checkLine in checkLines:
- assert checkLine.variant == TestAssertion.Variant.Not
- matchLineNo, matchVarState = \
- __findFirstMatch(checkLine, outputLines, startLineNo, [], varState)
- if matchVarState is not None:
- Logger.testFailed("CHECK-NOT line \"" + checkLine.originalText + "\" matches output line " + \
- str(matchLineNo), checkLine.fileName, checkLine.lineNo)
-
-def __matchGroups(checkGroup, outputGroup):
- """ Matches the check lines in this group against an output group. It is
- responsible for running the checks in the right order and scope, and
- for propagating the variable state between the check lines.
- """
- varState = {}
- checkLines = checkGroup.assertions
- outputLines = outputGroup.body
- startLineNo = outputGroup.startLineNo
-
- while checkLines:
- # Extract the next sequence of location-independent checks to be matched.
- notChecks, independentChecks, checkLines = __nextIndependentChecks(checkLines)
-
- # Match the independent checks.
- notOutput, outputLines, newStartLineNo, newVarState = \
- __matchIndependentChecks(independentChecks, outputLines, startLineNo, varState)
-
- # Run the Not checks against the output lines which lie between the last
- # two independent groups or the bounds of the output.
- __matchNotLines(notChecks, notOutput, startLineNo, varState)
-
- # Update variable state.
- startLineNo = newStartLineNo
- varState = newVarState
-
-def MatchFiles(checkerFile, c1File):
- for testCase in checkerFile.testCases:
- # 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 \"" + testCase.name + "\" not found in the C1visualizer output",
- testCase.fileName, testCase.lineNo)
- Logger.startTest(testCase.name)
- __matchGroups(testCase, c1Pass)
- Logger.testPassed()
diff --git a/tools/checker/match/line.py b/tools/checker/match/line.py
deleted file mode 100644
index eb1ab82..0000000
--- a/tools/checker/match/line.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright (C) 2015 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 common.logger import Logger
-from file_format.checker.struct import TestAssertion, RegexExpression
-
-import re
-
-def __isMatchAtStart(match):
- """ Tests if the given Match occurred at the beginning of the line. """
- return (match is not None) and (match.start() == 0)
-
-def __generatePattern(checkLine, linePart, varState):
- """ Returns the regex pattern to be matched in the output line. Variable
- references are substituted with their current values provided in the
- 'varState' argument.
-
- An exception is raised if a referenced variable is undefined.
- """
- if linePart.variant == RegexExpression.Variant.VarRef:
- try:
- return re.escape(varState[linePart.name])
- except KeyError:
- Logger.testFailed("Use of undefined variable \"" + linePart.name + "\"",
- checkLine.fileName, checkLine.lineNo)
- else:
- return linePart.pattern
-
-def __isSeparated(outputLine, matchStart):
- return (matchStart == 0) or (outputLine[matchStart - 1:matchStart].isspace())
-
-def MatchLines(checkLine, outputLine, initialVarState):
- """ Attempts to match the check line against a line from the output file with
- the given initial variable values. It returns the new variable state if
- successful and None otherwise.
- """
- # Do the full matching on a shadow copy of the variable state. If the
- # matching fails half-way, we will not need to revert the state.
- varState = dict(initialVarState)
-
- matchStart = 0
- isAfterSeparator = True
-
- # Now try to parse all of the parts of the check line in the right order.
- # Variable values are updated on-the-fly, meaning that a variable can
- # be referenced immediately after its definition.
- for part in checkLine.expressions:
- if part.variant == RegexExpression.Variant.Separator:
- isAfterSeparator = True
- continue
-
- # Find the earliest match for this line part.
- pattern = __generatePattern(checkLine, part, varState)
- while True:
- match = re.search(pattern, outputLine[matchStart:])
- if (match is None) or (not isAfterSeparator and not __isMatchAtStart(match)):
- return None
- matchEnd = matchStart + match.end()
- matchStart += match.start()
-
- # Check if this is a valid match if we expect a whitespace separator
- # before the matched text. Otherwise loop and look for another match.
- if not isAfterSeparator or __isSeparated(outputLine, matchStart):
- break
- else:
- matchStart += 1
-
- if part.variant == RegexExpression.Variant.VarDef:
- if part.name in varState:
- Logger.testFailed("Multiple definitions of variable \"" + part.name + "\"",
- checkLine.fileName, checkLine.lineNo)
- varState[part.name] = outputLine[matchStart:matchEnd]
-
- matchStart = matchEnd
- isAfterSeparator = False
-
- # All parts were successfully matched. Return the new variable state.
- return varState
diff --git a/tools/checker/match/test.py b/tools/checker/match/test.py
deleted file mode 100644
index 976a87f..0000000
--- a/tools/checker/match/test.py
+++ /dev/null
@@ -1,326 +0,0 @@
-# Copyright (C) 2015 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 common.testing import ToUnicode
-from file_format.c1visualizer.parser import ParseC1visualizerStream
-from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
-from file_format.checker.parser import ParseCheckerStream, ParseCheckerAssertion
-from file_format.checker.struct import CheckerFile, TestCase, TestAssertion, RegexExpression
-from match.file import MatchFiles
-from match.line import MatchLines
-
-import io
-import unittest
-
-CheckerException = SystemExit
-
-class MatchLines_Test(unittest.TestCase):
-
- def createTestAssertion(self, checkerString):
- checkerFile = CheckerFile("<checker-file>")
- testCase = TestCase(checkerFile, "TestMethod TestPass", 0)
- return ParseCheckerAssertion(testCase, checkerString, TestAssertion.Variant.InOrder, 0)
-
- def tryMatch(self, checkerString, c1String, varState={}):
- return MatchLines(self.createTestAssertion(checkerString), ToUnicode(c1String), varState)
-
- def matches(self, checkerString, c1String, varState={}):
- return self.tryMatch(checkerString, c1String, varState) is not None
-
- def test_TextAndWhitespace(self):
- self.assertTrue(self.matches("foo", "foo"))
- self.assertTrue(self.matches("foo", " foo "))
- self.assertTrue(self.matches("foo", "foo bar"))
- self.assertFalse(self.matches("foo", "XfooX"))
- self.assertFalse(self.matches("foo", "zoo"))
-
- self.assertTrue(self.matches("foo bar", "foo bar"))
- self.assertTrue(self.matches("foo bar", "abc foo bar def"))
- self.assertTrue(self.matches("foo bar", "foo foo bar bar"))
-
- self.assertTrue(self.matches("foo bar", "foo X bar"))
- self.assertFalse(self.matches("foo bar", "foo Xbar"))
-
- def test_Pattern(self):
- self.assertTrue(self.matches("foo{{A|B}}bar", "fooAbar"))
- self.assertTrue(self.matches("foo{{A|B}}bar", "fooBbar"))
- self.assertFalse(self.matches("foo{{A|B}}bar", "fooCbar"))
-
- def test_VariableReference(self):
- self.assertTrue(self.matches("foo[[X]]bar", "foobar", {"X": ""}))
- self.assertTrue(self.matches("foo[[X]]bar", "fooAbar", {"X": "A"}))
- self.assertTrue(self.matches("foo[[X]]bar", "fooBbar", {"X": "B"}))
- self.assertFalse(self.matches("foo[[X]]bar", "foobar", {"X": "A"}))
- self.assertFalse(self.matches("foo[[X]]bar", "foo bar", {"X": "A"}))
- with self.assertRaises(CheckerException):
- self.assertTrue(self.matches("foo[[X]]bar", "foobar", {}))
-
- def test_VariableDefinition(self):
- self.assertTrue(self.matches("foo[[X:A|B]]bar", "fooAbar"))
- self.assertTrue(self.matches("foo[[X:A|B]]bar", "fooBbar"))
- self.assertFalse(self.matches("foo[[X:A|B]]bar", "fooCbar"))
-
- env = self.tryMatch("foo[[X:A.*B]]bar", "fooABbar", {})
- self.assertEqual(env, {"X": "AB"})
- env = self.tryMatch("foo[[X:A.*B]]bar", "fooAxxBbar", {})
- self.assertEqual(env, {"X": "AxxB"})
-
- self.assertTrue(self.matches("foo[[X:A|B]]bar[[X]]baz", "fooAbarAbaz"))
- self.assertTrue(self.matches("foo[[X:A|B]]bar[[X]]baz", "fooBbarBbaz"))
- self.assertFalse(self.matches("foo[[X:A|B]]bar[[X]]baz", "fooAbarBbaz"))
-
- def test_NoVariableRedefinition(self):
- with self.assertRaises(CheckerException):
- self.matches("[[X:...]][[X]][[X:...]][[X]]", "foofoobarbar")
-
- def test_EnvNotChangedOnPartialMatch(self):
- env = {"Y": "foo"}
- self.assertFalse(self.matches("[[X:A]]bar", "Abaz", env))
- self.assertFalse("X" in env.keys())
-
- def test_VariableContentEscaped(self):
- self.assertTrue(self.matches("[[X:..]]foo[[X]]", ".*foo.*"))
- self.assertFalse(self.matches("[[X:..]]foo[[X]]", ".*fooAAAA"))
-
-
-class MatchFiles_Test(unittest.TestCase):
-
- def matches(self, checkerString, c1String):
- checkerString = \
- """
- // CHECK-START: MyMethod MyPass
- """ + checkerString
- c1String = \
- """
- begin_compilation
- name "MyMethod"
- method "MyMethod"
- date 1234
- end_compilation
- begin_cfg
- name "MyPass"
- """ + c1String + \
- """
- end_cfg
- """
- checkerFile = ParseCheckerStream("<test-file>", "CHECK", io.StringIO(ToUnicode(checkerString)))
- c1File = ParseC1visualizerStream("<c1-file>", io.StringIO(ToUnicode(c1String)))
- try:
- MatchFiles(checkerFile, c1File)
- return True
- except CheckerException:
- return False
-
- def test_Text(self):
- self.assertTrue(self.matches( "// CHECK: foo bar", "foo bar"))
- self.assertFalse(self.matches("// CHECK: foo bar", "abc def"))
-
- def test_Pattern(self):
- self.assertTrue(self.matches( "// CHECK: abc {{de.}}", "abc de#"))
- self.assertFalse(self.matches("// CHECK: abc {{de.}}", "abc d#f"))
-
- def test_Variables(self):
- self.assertTrue(self.matches(
- """
- // CHECK: foo[[X:.]]bar
- // CHECK: abc[[X]]def
- """,
- """
- foo bar
- abc def
- """))
- self.assertTrue(self.matches(
- """
- // CHECK: foo[[X:([0-9]+)]]bar
- // CHECK: abc[[X]]def
- // CHECK: ### [[X]] ###
- """,
- """
- foo1234bar
- abc1234def
- ### 1234 ###
- """))
- self.assertFalse(self.matches(
- """
- // CHECK: foo[[X:([0-9]+)]]bar
- // CHECK: abc[[X]]def
- """,
- """
- foo1234bar
- abc1235def
- """))
-
- def test_InOrderAssertions(self):
- self.assertTrue(self.matches(
- """
- // CHECK: foo
- // CHECK: bar
- """,
- """
- foo
- bar
- """))
- self.assertFalse(self.matches(
- """
- // CHECK: foo
- // CHECK: bar
- """,
- """
- bar
- foo
- """))
-
- def test_DagAssertions(self):
- self.assertTrue(self.matches(
- """
- // CHECK-DAG: foo
- // CHECK-DAG: bar
- """,
- """
- foo
- bar
- """))
- self.assertTrue(self.matches(
- """
- // CHECK-DAG: foo
- // CHECK-DAG: bar
- """,
- """
- bar
- foo
- """))
-
- def test_DagAssertionsScope(self):
- self.assertTrue(self.matches(
- """
- // CHECK: foo
- // CHECK-DAG: abc
- // CHECK-DAG: def
- // CHECK: bar
- """,
- """
- foo
- def
- abc
- bar
- """))
- self.assertFalse(self.matches(
- """
- // CHECK: foo
- // CHECK-DAG: abc
- // CHECK-DAG: def
- // CHECK: bar
- """,
- """
- foo
- abc
- bar
- def
- """))
- self.assertFalse(self.matches(
- """
- // CHECK: foo
- // CHECK-DAG: abc
- // CHECK-DAG: def
- // CHECK: bar
- """,
- """
- foo
- def
- bar
- abc
- """))
-
- def test_NotAssertions(self):
- self.assertTrue(self.matches(
- """
- // CHECK-NOT: foo
- """,
- """
- abc
- def
- """))
- self.assertFalse(self.matches(
- """
- // CHECK-NOT: foo
- """,
- """
- abc foo
- def
- """))
- self.assertFalse(self.matches(
- """
- // CHECK-NOT: foo
- // CHECK-NOT: bar
- """,
- """
- abc
- def bar
- """))
-
- def test_NotAssertionsScope(self):
- self.assertTrue(self.matches(
- """
- // CHECK: abc
- // CHECK-NOT: foo
- // CHECK: def
- """,
- """
- abc
- def
- """))
- self.assertTrue(self.matches(
- """
- // CHECK: abc
- // CHECK-NOT: foo
- // CHECK: def
- """,
- """
- abc
- def
- foo
- """))
- self.assertFalse(self.matches(
- """
- // CHECK: abc
- // CHECK-NOT: foo
- // CHECK: def
- """,
- """
- abc
- foo
- def
- """))
-
- def test_LineOnlyMatchesOnce(self):
- self.assertTrue(self.matches(
- """
- // CHECK-DAG: foo
- // CHECK-DAG: foo
- """,
- """
- foo
- abc
- foo
- """))
- self.assertFalse(self.matches(
- """
- // CHECK-DAG: foo
- // CHECK-DAG: foo
- """,
- """
- foo
- abc
- bar
- """))
diff --git a/tools/checker/run_unit_tests.py b/tools/checker/run_unit_tests.py
deleted file mode 100755
index 01708db..0000000
--- a/tools/checker/run_unit_tests.py
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env python2
-#
-# 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 common.logger import Logger
-from file_format.c1visualizer.test import C1visualizerParser_Test
-from file_format.checker.test import CheckerParser_PrefixTest, \
- CheckerParser_RegexExpressionTest, \
- CheckerParser_FileLayoutTest
-from match.test import MatchLines_Test, \
- MatchFiles_Test
-
-import unittest
-
-if __name__ == '__main__':
- Logger.Verbosity = Logger.Level.NoOutput
- unittest.main(verbosity=2)
diff --git a/tools/checker_test.py b/tools/checker_test.py
new file mode 100755
index 0000000..667ca90
--- /dev/null
+++ b/tools/checker_test.py
@@ -0,0 +1,474 @@
+#!/usr/bin/env python2
+#
+# 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.
+
+# This is a test file which exercises all feautres supported by the domain-
+# specific markup language implemented by Checker.
+
+import checker
+import io
+import unittest
+
+# The parent type of exception expected to be thrown by Checker during tests.
+# It must be specific enough to not cover exceptions thrown due to actual flaws
+# in Checker.
+CheckerException = SystemExit
+
+
+class TestCheckFile_PrefixExtraction(unittest.TestCase):
+ def __tryParse(self, string):
+ checkFile = checker.CheckFile(None, [])
+ return checkFile._extractLine("CHECK", string)
+
+ def test_InvalidFormat(self):
+ self.assertIsNone(self.__tryParse("CHECK"))
+ self.assertIsNone(self.__tryParse(":CHECK"))
+ self.assertIsNone(self.__tryParse("CHECK:"))
+ self.assertIsNone(self.__tryParse("//CHECK"))
+ self.assertIsNone(self.__tryParse("#CHECK"))
+
+ self.assertIsNotNone(self.__tryParse("//CHECK:foo"))
+ self.assertIsNotNone(self.__tryParse("#CHECK:bar"))
+
+ def test_InvalidLabel(self):
+ self.assertIsNone(self.__tryParse("//ACHECK:foo"))
+ self.assertIsNone(self.__tryParse("#ACHECK:foo"))
+
+ def test_NotFirstOnTheLine(self):
+ self.assertIsNone(self.__tryParse("A// CHECK: foo"))
+ self.assertIsNone(self.__tryParse("A # CHECK: foo"))
+ self.assertIsNone(self.__tryParse("// // CHECK: foo"))
+ self.assertIsNone(self.__tryParse("# # CHECK: foo"))
+
+ def test_WhitespaceAgnostic(self):
+ self.assertIsNotNone(self.__tryParse(" //CHECK: foo"))
+ self.assertIsNotNone(self.__tryParse("// CHECK: foo"))
+ self.assertIsNotNone(self.__tryParse(" //CHECK: foo"))
+ self.assertIsNotNone(self.__tryParse("// CHECK: foo"))
+
+
+class TestCheckLine_Parse(unittest.TestCase):
+ def __getPartPattern(self, linePart):
+ if linePart.variant == checker.CheckElement.Variant.Separator:
+ return "\s+"
+ else:
+ return linePart.pattern
+
+ def __getRegex(self, checkLine):
+ return "".join(map(lambda x: "(" + self.__getPartPattern(x) + ")", checkLine.lineParts))
+
+ def __tryParse(self, string):
+ return checker.CheckLine(string)
+
+ def __parsesTo(self, string, expected):
+ self.assertEqual(expected, self.__getRegex(self.__tryParse(string)))
+
+ def __tryParseNot(self, string):
+ return checker.CheckLine(string, checker.CheckLine.Variant.Not)
+
+ def __parsesPattern(self, string, pattern):
+ line = self.__tryParse(string)
+ self.assertEqual(1, len(line.lineParts))
+ self.assertEqual(checker.CheckElement.Variant.Pattern, line.lineParts[0].variant)
+ self.assertEqual(pattern, line.lineParts[0].pattern)
+
+ def __parsesVarRef(self, string, name):
+ line = self.__tryParse(string)
+ self.assertEqual(1, len(line.lineParts))
+ self.assertEqual(checker.CheckElement.Variant.VarRef, line.lineParts[0].variant)
+ self.assertEqual(name, line.lineParts[0].name)
+
+ def __parsesVarDef(self, string, name, body):
+ line = self.__tryParse(string)
+ self.assertEqual(1, len(line.lineParts))
+ self.assertEqual(checker.CheckElement.Variant.VarDef, line.lineParts[0].variant)
+ self.assertEqual(name, line.lineParts[0].name)
+ self.assertEqual(body, line.lineParts[0].pattern)
+
+ def __doesNotParse(self, string, partType):
+ line = self.__tryParse(string)
+ self.assertEqual(1, len(line.lineParts))
+ self.assertNotEqual(partType, line.lineParts[0].variant)
+
+ # Test that individual parts of the line are recognized
+
+ def test_TextOnly(self):
+ self.__parsesTo("foo", "(foo)")
+ self.__parsesTo(" foo ", "(foo)")
+ self.__parsesTo("f$o^o", "(f\$o\^o)")
+
+ def test_TextWithWhitespace(self):
+ self.__parsesTo("foo bar", "(foo)(\s+)(bar)")
+ self.__parsesTo("foo bar", "(foo)(\s+)(bar)")
+
+ def test_RegexOnly(self):
+ self.__parsesPattern("{{a?b.c}}", "a?b.c")
+
+ def test_VarRefOnly(self):
+ self.__parsesVarRef("[[ABC]]", "ABC")
+
+ def test_VarDefOnly(self):
+ self.__parsesVarDef("[[ABC:a?b.c]]", "ABC", "a?b.c")
+
+ def test_TextWithRegex(self):
+ self.__parsesTo("foo{{abc}}bar", "(foo)(abc)(bar)")
+
+ def test_TextWithVar(self):
+ self.__parsesTo("foo[[ABC:abc]]bar", "(foo)(abc)(bar)")
+
+ def test_PlainWithRegexAndWhitespaces(self):
+ self.__parsesTo("foo {{abc}}bar", "(foo)(\s+)(abc)(bar)")
+ self.__parsesTo("foo{{abc}} bar", "(foo)(abc)(\s+)(bar)")
+ self.__parsesTo("foo {{abc}} bar", "(foo)(\s+)(abc)(\s+)(bar)")
+
+ def test_PlainWithVarAndWhitespaces(self):
+ self.__parsesTo("foo [[ABC:abc]]bar", "(foo)(\s+)(abc)(bar)")
+ self.__parsesTo("foo[[ABC:abc]] bar", "(foo)(abc)(\s+)(bar)")
+ self.__parsesTo("foo [[ABC:abc]] bar", "(foo)(\s+)(abc)(\s+)(bar)")
+
+ def test_AllKinds(self):
+ self.__parsesTo("foo [[ABC:abc]]{{def}}bar", "(foo)(\s+)(abc)(def)(bar)")
+ self.__parsesTo("foo[[ABC:abc]] {{def}}bar", "(foo)(abc)(\s+)(def)(bar)")
+ self.__parsesTo("foo [[ABC:abc]] {{def}} bar", "(foo)(\s+)(abc)(\s+)(def)(\s+)(bar)")
+
+ # Test that variables and patterns are parsed correctly
+
+ def test_ValidPattern(self):
+ self.__parsesPattern("{{abc}}", "abc")
+ self.__parsesPattern("{{a[b]c}}", "a[b]c")
+ self.__parsesPattern("{{(a{bc})}}", "(a{bc})")
+
+ def test_ValidRef(self):
+ self.__parsesVarRef("[[ABC]]", "ABC")
+ self.__parsesVarRef("[[A1BC2]]", "A1BC2")
+
+ def test_ValidDef(self):
+ self.__parsesVarDef("[[ABC:abc]]", "ABC", "abc")
+ self.__parsesVarDef("[[ABC:ab:c]]", "ABC", "ab:c")
+ self.__parsesVarDef("[[ABC:a[b]c]]", "ABC", "a[b]c")
+ self.__parsesVarDef("[[ABC:(a[bc])]]", "ABC", "(a[bc])")
+
+ def test_Empty(self):
+ self.__doesNotParse("{{}}", checker.CheckElement.Variant.Pattern)
+ self.__doesNotParse("[[]]", checker.CheckElement.Variant.VarRef)
+ self.__doesNotParse("[[:]]", checker.CheckElement.Variant.VarDef)
+
+ def test_InvalidVarName(self):
+ self.__doesNotParse("[[0ABC]]", checker.CheckElement.Variant.VarRef)
+ self.__doesNotParse("[[AB=C]]", checker.CheckElement.Variant.VarRef)
+ self.__doesNotParse("[[ABC=]]", checker.CheckElement.Variant.VarRef)
+ self.__doesNotParse("[[0ABC:abc]]", checker.CheckElement.Variant.VarDef)
+ self.__doesNotParse("[[AB=C:abc]]", checker.CheckElement.Variant.VarDef)
+ self.__doesNotParse("[[ABC=:abc]]", checker.CheckElement.Variant.VarDef)
+
+ def test_BodyMatchNotGreedy(self):
+ self.__parsesTo("{{abc}}{{def}}", "(abc)(def)")
+ self.__parsesTo("[[ABC:abc]][[DEF:def]]", "(abc)(def)")
+
+ def test_NoVarDefsInNotChecks(self):
+ with self.assertRaises(CheckerException):
+ self.__tryParseNot("[[ABC:abc]]")
+
+class TestCheckLine_Match(unittest.TestCase):
+ def __matchSingle(self, checkString, outputString, varState={}):
+ checkLine = checker.CheckLine(checkString)
+ newVarState = checkLine.match(outputString, varState)
+ self.assertIsNotNone(newVarState)
+ return newVarState
+
+ def __notMatchSingle(self, checkString, outputString, varState={}):
+ checkLine = checker.CheckLine(checkString)
+ self.assertIsNone(checkLine.match(outputString, varState))
+
+ def test_TextAndWhitespace(self):
+ self.__matchSingle("foo", "foo")
+ self.__matchSingle("foo", " foo ")
+ self.__matchSingle("foo", "foo bar")
+ self.__notMatchSingle("foo", "XfooX")
+ self.__notMatchSingle("foo", "zoo")
+
+ self.__matchSingle("foo bar", "foo bar")
+ self.__matchSingle("foo bar", "abc foo bar def")
+ self.__matchSingle("foo bar", "foo foo bar bar")
+
+ self.__matchSingle("foo bar", "foo X bar")
+ self.__notMatchSingle("foo bar", "foo Xbar")
+
+ def test_Pattern(self):
+ self.__matchSingle("foo{{A|B}}bar", "fooAbar")
+ self.__matchSingle("foo{{A|B}}bar", "fooBbar")
+ self.__notMatchSingle("foo{{A|B}}bar", "fooCbar")
+
+ def test_VariableReference(self):
+ self.__matchSingle("foo[[X]]bar", "foobar", {"X": ""})
+ self.__matchSingle("foo[[X]]bar", "fooAbar", {"X": "A"})
+ self.__matchSingle("foo[[X]]bar", "fooBbar", {"X": "B"})
+ self.__notMatchSingle("foo[[X]]bar", "foobar", {"X": "A"})
+ self.__notMatchSingle("foo[[X]]bar", "foo bar", {"X": "A"})
+ with self.assertRaises(CheckerException):
+ self.__matchSingle("foo[[X]]bar", "foobar", {})
+
+ def test_VariableDefinition(self):
+ self.__matchSingle("foo[[X:A|B]]bar", "fooAbar")
+ self.__matchSingle("foo[[X:A|B]]bar", "fooBbar")
+ self.__notMatchSingle("foo[[X:A|B]]bar", "fooCbar")
+
+ env = self.__matchSingle("foo[[X:A.*B]]bar", "fooABbar", {})
+ self.assertEqual(env, {"X": "AB"})
+ env = self.__matchSingle("foo[[X:A.*B]]bar", "fooAxxBbar", {})
+ self.assertEqual(env, {"X": "AxxB"})
+
+ self.__matchSingle("foo[[X:A|B]]bar[[X]]baz", "fooAbarAbaz")
+ self.__matchSingle("foo[[X:A|B]]bar[[X]]baz", "fooBbarBbaz")
+ self.__notMatchSingle("foo[[X:A|B]]bar[[X]]baz", "fooAbarBbaz")
+
+ def test_NoVariableRedefinition(self):
+ with self.assertRaises(CheckerException):
+ self.__matchSingle("[[X:...]][[X]][[X:...]][[X]]", "foofoobarbar")
+
+ def test_EnvNotChangedOnPartialMatch(self):
+ env = {"Y": "foo"}
+ self.__notMatchSingle("[[X:A]]bar", "Abaz", env)
+ self.assertFalse("X" in env.keys())
+
+ def test_VariableContentEscaped(self):
+ self.__matchSingle("[[X:..]]foo[[X]]", ".*foo.*")
+ self.__notMatchSingle("[[X:..]]foo[[X]]", ".*fooAAAA")
+
+
+CheckVariant = checker.CheckLine.Variant
+
+def prepareSingleCheck(line):
+ if isinstance(line, str):
+ return checker.CheckLine(line)
+ else:
+ return checker.CheckLine(line[0], line[1])
+
+def prepareChecks(lines):
+ if isinstance(lines, str):
+ lines = lines.splitlines()
+ return list(map(lambda line: prepareSingleCheck(line), lines))
+
+
+class TestCheckGroup_Match(unittest.TestCase):
+ def __matchMulti(self, checkLines, outputString):
+ checkGroup = checker.CheckGroup("MyGroup", prepareChecks(checkLines))
+ outputGroup = checker.OutputGroup("MyGroup", outputString.splitlines())
+ return checkGroup.match(outputGroup)
+
+ def __notMatchMulti(self, checkString, outputString):
+ with self.assertRaises(CheckerException):
+ self.__matchMulti(checkString, outputString)
+
+ def test_TextAndPattern(self):
+ self.__matchMulti("""foo bar
+ abc {{def}}""",
+ """foo bar
+ abc def""");
+ self.__matchMulti("""foo bar
+ abc {{de.}}""",
+ """=======
+ foo bar
+ =======
+ abc de#
+ =======""");
+ self.__notMatchMulti("""//XYZ: foo bar
+ //XYZ: abc {{def}}""",
+ """=======
+ foo bar
+ =======
+ abc de#
+ =======""");
+
+ def test_Variables(self):
+ self.__matchMulti("""foo[[X:.]]bar
+ abc[[X]]def""",
+ """foo bar
+ abc def""");
+ self.__matchMulti("""foo[[X:([0-9]+)]]bar
+ abc[[X]]def
+ ### [[X]] ###""",
+ """foo1234bar
+ abc1234def
+ ### 1234 ###""");
+
+ def test_Ordering(self):
+ self.__matchMulti([("foo", CheckVariant.InOrder),
+ ("bar", CheckVariant.InOrder)],
+ """foo
+ bar""")
+ self.__notMatchMulti([("foo", CheckVariant.InOrder),
+ ("bar", CheckVariant.InOrder)],
+ """bar
+ foo""")
+ self.__matchMulti([("abc", CheckVariant.DAG),
+ ("def", CheckVariant.DAG)],
+ """abc
+ def""")
+ self.__matchMulti([("abc", CheckVariant.DAG),
+ ("def", CheckVariant.DAG)],
+ """def
+ abc""")
+ self.__matchMulti([("foo", CheckVariant.InOrder),
+ ("abc", CheckVariant.DAG),
+ ("def", CheckVariant.DAG),
+ ("bar", CheckVariant.InOrder)],
+ """foo
+ def
+ abc
+ bar""")
+ self.__notMatchMulti([("foo", CheckVariant.InOrder),
+ ("abc", CheckVariant.DAG),
+ ("def", CheckVariant.DAG),
+ ("bar", CheckVariant.InOrder)],
+ """foo
+ abc
+ bar""")
+ self.__notMatchMulti([("foo", CheckVariant.InOrder),
+ ("abc", CheckVariant.DAG),
+ ("def", CheckVariant.DAG),
+ ("bar", CheckVariant.InOrder)],
+ """foo
+ def
+ bar""")
+
+ def test_NotAssertions(self):
+ self.__matchMulti([("foo", CheckVariant.Not)],
+ """abc
+ def""")
+ self.__notMatchMulti([("foo", CheckVariant.Not)],
+ """abc foo
+ def""")
+ self.__notMatchMulti([("foo", CheckVariant.Not),
+ ("bar", CheckVariant.Not)],
+ """abc
+ def bar""")
+
+ def test_LineOnlyMatchesOnce(self):
+ self.__matchMulti([("foo", CheckVariant.DAG),
+ ("foo", CheckVariant.DAG)],
+ """foo
+ foo""")
+ self.__notMatchMulti([("foo", CheckVariant.DAG),
+ ("foo", CheckVariant.DAG)],
+ """foo
+ bar""")
+
+class TestOutputFile_Parse(unittest.TestCase):
+ def __parsesTo(self, string, expected):
+ if isinstance(string, str):
+ string = unicode(string)
+ outputStream = io.StringIO(string)
+ return self.assertEqual(checker.OutputFile(outputStream).groups, expected)
+
+ def test_NoInput(self):
+ self.__parsesTo(None, [])
+ self.__parsesTo("", [])
+
+ def test_SingleGroup(self):
+ self.__parsesTo("""begin_compilation
+ method "MyMethod"
+ end_compilation
+ begin_cfg
+ name "pass1"
+ foo
+ bar
+ end_cfg""",
+ [ checker.OutputGroup("MyMethod pass1", [ "foo", "bar" ]) ])
+
+ def test_MultipleGroups(self):
+ self.__parsesTo("""begin_compilation
+ name "xyz1"
+ method "MyMethod1"
+ date 1234
+ end_compilation
+ begin_cfg
+ name "pass1"
+ foo
+ bar
+ end_cfg
+ begin_cfg
+ name "pass2"
+ abc
+ def
+ end_cfg""",
+ [ checker.OutputGroup("MyMethod1 pass1", [ "foo", "bar" ]),
+ checker.OutputGroup("MyMethod1 pass2", [ "abc", "def" ]) ])
+
+ self.__parsesTo("""begin_compilation
+ name "xyz1"
+ method "MyMethod1"
+ date 1234
+ end_compilation
+ begin_cfg
+ name "pass1"
+ foo
+ bar
+ end_cfg
+ begin_compilation
+ name "xyz2"
+ method "MyMethod2"
+ date 5678
+ end_compilation
+ begin_cfg
+ name "pass2"
+ abc
+ def
+ end_cfg""",
+ [ checker.OutputGroup("MyMethod1 pass1", [ "foo", "bar" ]),
+ checker.OutputGroup("MyMethod2 pass2", [ "abc", "def" ]) ])
+
+class TestCheckFile_Parse(unittest.TestCase):
+ def __parsesTo(self, string, expected):
+ if isinstance(string, str):
+ string = unicode(string)
+ checkStream = io.StringIO(string)
+ return self.assertEqual(checker.CheckFile("CHECK", checkStream).groups, expected)
+
+ def test_NoInput(self):
+ self.__parsesTo(None, [])
+ self.__parsesTo("", [])
+
+ def test_SingleGroup(self):
+ self.__parsesTo("""// CHECK-START: Example Group
+ // CHECK: foo
+ // CHECK: bar""",
+ [ checker.CheckGroup("Example Group", prepareChecks([ "foo", "bar" ])) ])
+
+ def test_MultipleGroups(self):
+ self.__parsesTo("""// CHECK-START: Example Group1
+ // CHECK: foo
+ // CHECK: bar
+ // CHECK-START: Example Group2
+ // CHECK: abc
+ // CHECK: def""",
+ [ checker.CheckGroup("Example Group1", prepareChecks([ "foo", "bar" ])),
+ checker.CheckGroup("Example Group2", prepareChecks([ "abc", "def" ])) ])
+
+ def test_CheckVariants(self):
+ self.__parsesTo("""// CHECK-START: Example Group
+ // CHECK: foo
+ // CHECK-NOT: bar
+ // CHECK-DAG: abc
+ // CHECK-DAG: def""",
+ [ checker.CheckGroup("Example Group",
+ prepareChecks([ ("foo", CheckVariant.InOrder),
+ ("bar", CheckVariant.Not),
+ ("abc", CheckVariant.DAG),
+ ("def", CheckVariant.DAG) ])) ])
+
+if __name__ == '__main__':
+ checker.Logger.Verbosity = checker.Logger.Level.NoOutput
+ unittest.main()