ART: New types of Checker assertions
Checker now supports positive out-of-order assertions (CHECK-DAG),
which are useful for matching dependency graphs, and negative
assertions (CHECK-NOT) to test element removal.
ConstantFolding tests are rewritten using -DAG checks and Inliner
tests are added.
Change-Id: I5afb665f532b24683624b6d21ef4377cb441d731
diff --git a/compiler/optimizing/test/ConstantFolding.java b/compiler/optimizing/test/ConstantFolding.java
index 7fac5a9..92f2a77 100644
--- a/compiler/optimizing/test/ConstantFolding.java
+++ b/compiler/optimizing/test/ConstantFolding.java
@@ -22,13 +22,13 @@
*/
// CHECK-START: int ConstantFolding.IntNegation() constant_folding (before)
- // CHECK: [[Const42:i[0-9]+]] IntConstant 42
- // CHECK: [[Neg:i[0-9]+]] Neg [ [[Const42]] ]
- // CHECK: Return [ [[Neg]] ]
+ // CHECK-DAG: [[Const42:i[0-9]+]] IntConstant 42
+ // CHECK-DAG: [[Neg:i[0-9]+]] Neg [ [[Const42]] ]
+ // CHECK-DAG: Return [ [[Neg]] ]
// CHECK-START: int ConstantFolding.IntNegation() constant_folding (after)
- // CHECK: [[ConstN42:i[0-9]+]] IntConstant -42
- // CHECK: Return [ [[ConstN42]] ]
+ // CHECK-DAG: [[ConstN42:i[0-9]+]] IntConstant -42
+ // CHECK-DAG: Return [ [[ConstN42]] ]
public static int IntNegation() {
int x, y;
@@ -43,14 +43,14 @@
*/
// CHECK-START: int ConstantFolding.IntAddition1() constant_folding (before)
- // CHECK: [[Const1:i[0-9]+]] IntConstant 1
- // CHECK: [[Const2:i[0-9]+]] IntConstant 2
- // CHECK: [[Add:i[0-9]+]] Add [ [[Const1]] [[Const2]] ]
- // CHECK: Return [ [[Add]] ]
+ // CHECK-DAG: [[Const1:i[0-9]+]] IntConstant 1
+ // CHECK-DAG: [[Const2:i[0-9]+]] IntConstant 2
+ // CHECK-DAG: [[Add:i[0-9]+]] Add [ [[Const1]] [[Const2]] ]
+ // CHECK-DAG: Return [ [[Add]] ]
// CHECK-START: int ConstantFolding.IntAddition1() constant_folding (after)
- // CHECK: [[Const3:i[0-9]+]] IntConstant 3
- // CHECK: Return [ [[Const3]] ]
+ // CHECK-DAG: [[Const3:i[0-9]+]] IntConstant 3
+ // CHECK-DAG: Return [ [[Const3]] ]
public static int IntAddition1() {
int a, b, c;
@@ -66,18 +66,18 @@
*/
// CHECK-START: int ConstantFolding.IntAddition2() constant_folding (before)
- // CHECK: [[Const1:i[0-9]+]] IntConstant 1
- // CHECK: [[Const2:i[0-9]+]] IntConstant 2
- // CHECK: [[Const5:i[0-9]+]] IntConstant 5
- // CHECK: [[Const6:i[0-9]+]] IntConstant 6
- // CHECK: [[Add1:i[0-9]+]] Add [ [[Const1]] [[Const2]] ]
- // CHECK: [[Add2:i[0-9]+]] Add [ [[Const5]] [[Const6]] ]
- // CHECK: [[Add3:i[0-9]+]] Add [ [[Add1]] [[Add2]] ]
- // CHECK: Return [ [[Add3]] ]
+ // CHECK-DAG: [[Const1:i[0-9]+]] IntConstant 1
+ // CHECK-DAG: [[Const2:i[0-9]+]] IntConstant 2
+ // CHECK-DAG: [[Const5:i[0-9]+]] IntConstant 5
+ // CHECK-DAG: [[Const6:i[0-9]+]] IntConstant 6
+ // CHECK-DAG: [[Add1:i[0-9]+]] Add [ [[Const1]] [[Const2]] ]
+ // CHECK-DAG: [[Add2:i[0-9]+]] Add [ [[Const5]] [[Const6]] ]
+ // CHECK-DAG: [[Add3:i[0-9]+]] Add [ [[Add1]] [[Add2]] ]
+ // CHECK-DAG: Return [ [[Add3]] ]
// CHECK-START: int ConstantFolding.IntAddition2() constant_folding (after)
- // CHECK: [[Const14:i[0-9]+]] IntConstant 14
- // CHECK: Return [ [[Const14]] ]
+ // CHECK-DAG: [[Const14:i[0-9]+]] IntConstant 14
+ // CHECK-DAG: Return [ [[Const14]] ]
public static int IntAddition2() {
int a, b, c;
@@ -97,14 +97,14 @@
*/
// CHECK-START: int ConstantFolding.IntSubtraction() constant_folding (before)
- // CHECK: [[Const5:i[0-9]+]] IntConstant 5
- // CHECK: [[Const2:i[0-9]+]] IntConstant 2
- // CHECK: [[Sub:i[0-9]+]] Sub [ [[Const5]] [[Const2]] ]
- // CHECK: Return [ [[Sub]] ]
+ // CHECK-DAG: [[Const5:i[0-9]+]] IntConstant 5
+ // CHECK-DAG: [[Const2:i[0-9]+]] IntConstant 2
+ // CHECK-DAG: [[Sub:i[0-9]+]] Sub [ [[Const5]] [[Const2]] ]
+ // CHECK-DAG: Return [ [[Sub]] ]
// CHECK-START: int ConstantFolding.IntSubtraction() constant_folding (after)
- // CHECK: [[Const3:i[0-9]+]] IntConstant 3
- // CHECK: Return [ [[Const3]] ]
+ // CHECK-DAG: [[Const3:i[0-9]+]] IntConstant 3
+ // CHECK-DAG: Return [ [[Const3]] ]
public static int IntSubtraction() {
int a, b, c;
@@ -120,14 +120,14 @@
*/
// CHECK-START: long ConstantFolding.LongAddition() constant_folding (before)
- // CHECK: [[Const1:j[0-9]+]] LongConstant 1
- // CHECK: [[Const2:j[0-9]+]] LongConstant 2
- // CHECK: [[Add:j[0-9]+]] Add [ [[Const1]] [[Const2]] ]
- // CHECK: Return [ [[Add]] ]
+ // CHECK-DAG: [[Const1:j[0-9]+]] LongConstant 1
+ // CHECK-DAG: [[Const2:j[0-9]+]] LongConstant 2
+ // CHECK-DAG: [[Add:j[0-9]+]] Add [ [[Const1]] [[Const2]] ]
+ // CHECK-DAG: Return [ [[Add]] ]
// CHECK-START: long ConstantFolding.LongAddition() constant_folding (after)
- // CHECK: [[Const3:j[0-9]+]] LongConstant 3
- // CHECK: Return [ [[Const3]] ]
+ // CHECK-DAG: [[Const3:j[0-9]+]] LongConstant 3
+ // CHECK-DAG: Return [ [[Const3]] ]
public static long LongAddition() {
long a, b, c;
@@ -143,14 +143,14 @@
*/
// CHECK-START: long ConstantFolding.LongSubtraction() constant_folding (before)
- // CHECK: [[Const5:j[0-9]+]] LongConstant 5
- // CHECK: [[Const2:j[0-9]+]] LongConstant 2
- // CHECK: [[Sub:j[0-9]+]] Sub [ [[Const5]] [[Const2]] ]
- // CHECK: Return [ [[Sub]] ]
+ // CHECK-DAG: [[Const5:j[0-9]+]] LongConstant 5
+ // CHECK-DAG: [[Const2:j[0-9]+]] LongConstant 2
+ // CHECK-DAG: [[Sub:j[0-9]+]] Sub [ [[Const5]] [[Const2]] ]
+ // CHECK-DAG: Return [ [[Sub]] ]
// CHECK-START: long ConstantFolding.LongSubtraction() constant_folding (after)
- // CHECK: [[Const3:j[0-9]+]] LongConstant 3
- // CHECK: Return [ [[Const3]] ]
+ // CHECK-DAG: [[Const3:j[0-9]+]] LongConstant 3
+ // CHECK-DAG: Return [ [[Const3]] ]
public static long LongSubtraction() {
long a, b, c;
@@ -165,14 +165,14 @@
*/
// CHECK-START: int ConstantFolding.StaticCondition() constant_folding (before)
- // CHECK: [[Const5:i[0-9]+]] IntConstant 5
- // CHECK: [[Const2:i[0-9]+]] IntConstant 2
- // CHECK: [[Cond:z[0-9]+]] GreaterThanOrEqual [ [[Const5]] [[Const2]] ]
- // CHECK: If [ [[Cond]] ]
+ // CHECK-DAG: [[Const5:i[0-9]+]] IntConstant 5
+ // CHECK-DAG: [[Const2:i[0-9]+]] IntConstant 2
+ // CHECK-DAG: [[Cond:z[0-9]+]] GreaterThanOrEqual [ [[Const5]] [[Const2]] ]
+ // CHECK-DAG: If [ [[Cond]] ]
// CHECK-START: int ConstantFolding.StaticCondition() constant_folding (after)
- // CHECK: [[Const1:i[0-9]+]] IntConstant 1
- // CHECK: If [ [[Const1]] ]
+ // CHECK-DAG: [[Const1:i[0-9]+]] IntConstant 1
+ // CHECK-DAG: If [ [[Const1]] ]
public static int StaticCondition() {
int a, b, c;
@@ -195,18 +195,18 @@
*/
// CHECK-START: int ConstantFolding.JumpsAndConditionals(boolean) constant_folding (before)
- // CHECK: [[Const5:i[0-9]+]] IntConstant 5
- // CHECK: [[Const2:i[0-9]+]] IntConstant 2
- // CHECK: [[Add:i[0-9]+]] Add [ [[Const5]] [[Const2]] ]
- // CHECK: [[Phi:i[0-9]+]] Phi [ [[Add]] [[Sub:i[0-9]+]] ]
- // CHECK: Return [ [[Phi]] ]
- // CHECK: [[Sub]] Sub [ [[Const5]] [[Const2]] ]
+ // CHECK-DAG: [[Const2:i[0-9]+]] IntConstant 2
+ // CHECK-DAG: [[Const5:i[0-9]+]] IntConstant 5
+ // CHECK-DAG: [[Add:i[0-9]+]] Add [ [[Const5]] [[Const2]] ]
+ // CHECK-DAG: [[Sub:i[0-9]+]] Sub [ [[Const5]] [[Const2]] ]
+ // CHECK-DAG: [[Phi:i[0-9]+]] Phi [ [[Add]] [[Sub]] ]
+ // CHECK-DAG: Return [ [[Phi]] ]
// CHECK-START: int ConstantFolding.JumpsAndConditionals(boolean) constant_folding (after)
- // CHECK: [[Const7:i[0-9]+]] IntConstant 7
- // CHECK: [[Phi:i[0-9]+]] Phi [ [[Const7]] [[Const3:i[0-9]+]] ]
- // CHECK: Return [ [[Phi]] ]
- // CHECK: [[Const3]] IntConstant 3
+ // CHECK-DAG: [[Const3:i[0-9]+]] IntConstant 3
+ // CHECK-DAG: [[Const7:i[0-9]+]] IntConstant 7
+ // CHECK-DAG: [[Phi:i[0-9]+]] Phi [ [[Const7]] [[Const3]] ]
+ // CHECK-DAG: Return [ [[Phi]] ]
public static int JumpsAndConditionals(boolean cond) {
int a, b, c;
diff --git a/compiler/optimizing/test/Inliner.java b/compiler/optimizing/test/Inliner.java
new file mode 100644
index 0000000..6aa7f8d
--- /dev/null
+++ b/compiler/optimizing/test/Inliner.java
@@ -0,0 +1,186 @@
+public class Inliner {
+
+ // CHECK-START: void Inliner.InlineVoid() inliner (before)
+ // CHECK-DAG: [[Const42:i[0-9]+]] IntConstant 42
+ // CHECK-DAG: InvokeStaticOrDirect
+ // CHECK-DAG: InvokeStaticOrDirect [ [[Const42]] ]
+
+ // CHECK-START: void Inliner.InlineVoid() inliner (after)
+ // CHECK-NOT: InvokeStaticOrDirect
+
+ public static void InlineVoid() {
+ returnVoid();
+ returnVoidWithOneParameter(42);
+ }
+
+ // CHECK-START: int Inliner.InlineParameter(int) inliner (before)
+ // CHECK-DAG: [[Param:i[0-9]+]] ParameterValue
+ // CHECK-DAG: [[Result:i[0-9]+]] InvokeStaticOrDirect [ [[Param]] ]
+ // CHECK-DAG: Return [ [[Result]] ]
+
+ // CHECK-START: int Inliner.InlineParameter(int) inliner (after)
+ // CHECK-DAG: [[Param:i[0-9]+]] ParameterValue
+ // CHECK-DAG: Return [ [[Param]] ]
+
+ public static int InlineParameter(int a) {
+ return returnParameter(a);
+ }
+
+ // CHECK-START: long Inliner.InlineWideParameter(long) inliner (before)
+ // CHECK-DAG: [[Param:j[0-9]+]] ParameterValue
+ // CHECK-DAG: [[Result:j[0-9]+]] InvokeStaticOrDirect [ [[Param]] ]
+ // CHECK-DAG: Return [ [[Result]] ]
+
+ // CHECK-START: long Inliner.InlineWideParameter(long) inliner (after)
+ // CHECK-DAG: [[Param:j[0-9]+]] ParameterValue
+ // CHECK-DAG: Return [ [[Param]] ]
+
+ public static long InlineWideParameter(long a) {
+ return returnWideParameter(a);
+ }
+
+ // CHECK-START: java.lang.Object Inliner.InlineReferenceParameter(java.lang.Object) inliner (before)
+ // CHECK-DAG: [[Param:l[0-9]+]] ParameterValue
+ // CHECK-DAG: [[Result:l[0-9]+]] InvokeStaticOrDirect [ [[Param]] ]
+ // CHECK-DAG: Return [ [[Result]] ]
+
+ // CHECK-START: java.lang.Object Inliner.InlineReferenceParameter(java.lang.Object) inliner (after)
+ // CHECK-DAG: [[Param:l[0-9]+]] ParameterValue
+ // CHECK-DAG: Return [ [[Param]] ]
+
+ public static Object InlineReferenceParameter(Object o) {
+ return returnReferenceParameter(o);
+ }
+
+ // CHECK-START: int Inliner.InlineInt() inliner (before)
+ // CHECK-DAG: [[Result:i[0-9]+]] InvokeStaticOrDirect
+ // CHECK-DAG: Return [ [[Result]] ]
+
+ // CHECK-START: int Inliner.InlineInt() inliner (after)
+ // CHECK-DAG: [[Const4:i[0-9]+]] IntConstant 4
+ // CHECK-DAG: Return [ [[Const4]] ]
+
+ public static int InlineInt() {
+ return returnInt();
+ }
+
+ // CHECK-START: long Inliner.InlineWide() inliner (before)
+ // CHECK-DAG: [[Result:j[0-9]+]] InvokeStaticOrDirect
+ // CHECK-DAG: Return [ [[Result]] ]
+
+ // CHECK-START: long Inliner.InlineWide() inliner (after)
+ // CHECK-DAG: [[Const8:j[0-9]+]] LongConstant 8
+ // CHECK-DAG: Return [ [[Const8]] ]
+
+ public static long InlineWide() {
+ return returnWide();
+ }
+
+ // CHECK-START: int Inliner.InlineAdd() inliner (before)
+ // CHECK-DAG: [[Const3:i[0-9]+]] IntConstant 3
+ // CHECK-DAG: [[Const5:i[0-9]+]] IntConstant 5
+ // CHECK-DAG: [[Result:i[0-9]+]] InvokeStaticOrDirect
+ // CHECK-DAG: Return [ [[Result]] ]
+
+ // CHECK-START: int Inliner.InlineAdd() inliner (after)
+ // CHECK-DAG: [[Const3:i[0-9]+]] IntConstant 3
+ // CHECK-DAG: [[Const5:i[0-9]+]] IntConstant 5
+ // CHECK-DAG: [[Add:i[0-9]+]] Add [ [[Const3]] [[Const5]] ]
+ // CHECK-DAG: Return [ [[Add]] ]
+
+ public static int InlineAdd() {
+ return returnAdd(3, 5);
+ }
+
+ // CHECK-START: int Inliner.InlineFieldAccess() inliner (before)
+ // CHECK-DAG: [[After:i[0-9]+]] InvokeStaticOrDirect
+ // CHECK-DAG: Return [ [[After]] ]
+
+ // CHECK-START: int Inliner.InlineFieldAccess() inliner (after)
+ // CHECK-DAG: [[Const1:i[0-9]+]] IntConstant 1
+ // CHECK-DAG: [[Before:i[0-9]+]] StaticFieldGet
+ // CHECK-DAG: [[After:i[0-9]+]] Add [ [[Before]] [[Const1]] ]
+ // CHECK-DAG: StaticFieldSet [ {{l[0-9]+}} [[After]] ]
+ // CHECK-DAG: Return [ [[After]] ]
+
+ // CHECK-START: int Inliner.InlineFieldAccess() inliner (after)
+ // CHECK-NOT: InvokeStaticOrDirect
+
+ public static int InlineFieldAccess() {
+ return incCounter();
+ }
+
+ // CHECK-START: int Inliner.InlineWithControlFlow(boolean) inliner (before)
+ // CHECK-DAG: [[Const1:i[0-9]+]] IntConstant 1
+ // CHECK-DAG: [[Const3:i[0-9]+]] IntConstant 3
+ // CHECK-DAG: [[Const5:i[0-9]+]] IntConstant 5
+ // CHECK-DAG: [[Add:i[0-9]+]] InvokeStaticOrDirect [ [[Const1]] [[Const3]] ]
+ // CHECK-DAG: [[Sub:i[0-9]+]] InvokeStaticOrDirect [ [[Const5]] [[Const3]] ]
+ // CHECK-DAG: [[Phi:i[0-9]+]] Phi [ [[Add]] [[Sub]] ]
+ // CHECK-DAG: Return [ [[Phi]] ]
+
+ // CHECK-START: int Inliner.InlineWithControlFlow(boolean) inliner (after)
+ // CHECK-DAG: [[Const1:i[0-9]+]] IntConstant 1
+ // CHECK-DAG: [[Const3:i[0-9]+]] IntConstant 3
+ // CHECK-DAG: [[Const5:i[0-9]+]] IntConstant 5
+ // CHECK-DAG: [[Add:i[0-9]+]] Add [ [[Const1]] [[Const3]] ]
+ // CHECK-DAG: [[Sub:i[0-9]+]] Sub [ [[Const5]] [[Const3]] ]
+ // CHECK-DAG: [[Phi:i[0-9]+]] Phi [ [[Add]] [[Sub]] ]
+ // CHECK-DAG: Return [ [[Phi]] ]
+
+ public static int InlineWithControlFlow(boolean cond) {
+ int x, const1, const3, const5;
+ const1 = 1;
+ const3 = 3;
+ const5 = 5;
+ if (cond) {
+ x = returnAdd(const1, const3);
+ } else {
+ x = returnSub(const5, const3);
+ }
+ return x;
+ }
+
+
+ private static void returnVoid() {
+ return;
+ }
+
+ private static void returnVoidWithOneParameter(int a) {
+ return;
+ }
+
+ private static int returnParameter(int a) {
+ return a;
+ }
+
+ private static long returnWideParameter(long a) {
+ return a;
+ }
+
+ private static Object returnReferenceParameter(Object o) {
+ return o;
+ }
+
+ private static int returnInt() {
+ return 4;
+ }
+
+ private static long returnWide() {
+ return 8L;
+ }
+
+ private static int returnAdd(int a, int b) {
+ return a + b;
+ }
+
+ private static int returnSub(int a, int b) {
+ return a - b;
+ }
+
+ private static int counter = 42;
+
+ private static int incCounter() {
+ return ++counter;
+ }
+}
diff --git a/tools/checker.py b/tools/checker.py
index 82a1e6b..74c6d61 100755
--- a/tools/checker.py
+++ b/tools/checker.py
@@ -20,9 +20,9 @@
# 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 comments in the Java file which begin with prefix
-# 'CHECK' followed by a pattern that the engine attempts to match in the
-# compiler-generated output.
+# 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'
@@ -30,7 +30,23 @@
# name must exactly match one of the groups recognized in the output (they can
# be listed with the '--list-groups' command-line flag).
#
-# Check line patterns are treated as plain text rather than regular expressions
+# 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 appear 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
@@ -115,13 +131,16 @@
more regex elements. Matching against an output line is successful only
if all regex elements can be matched in the given order."""
- def __init__(self, lineContent, lineNo=-1):
- lineContent = lineContent.strip()
+ class Variant(object):
+ """Supported types of assertions."""
+ InOrder, DAG, Not = range(3)
+ def __init__(self, content, variant=Variant.InOrder, lineNo=-1):
+ self.content = content.strip()
+ self.variant = variant
self.lineNo = lineNo
- self.content = lineContent
- self.lineParts = self.__parse(lineContent)
+ self.lineParts = self.__parse(self.content)
if not self.lineParts:
raise Exception("Empty check line")
@@ -180,7 +199,11 @@
elif self.__isMatchAtStart(matchVariable):
var = line[0:matchVariable.end()]
line = line[matchVariable.end():]
- lineParts.append(CheckElement.parseVariable(var))
+ elem = CheckElement.parseVariable(var)
+ if self.variant == CheckLine.Variant.Not and elem.variant == CheckElement.Variant.VarDef:
+ raise Exception("CHECK-NOT check lines cannot define variables " +
+ "(line " + str(self.lineNo) + ")")
+ lineParts.append(elem)
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
@@ -267,44 +290,101 @@
def __headAndTail(self, list):
return list[0], list[1:]
- # The driver of matching inside a group. It simultaneously reads lines from
- # the output and check groups and attempts to match them against each other
- # in the correct order.
- def match(self, outputGroup):
- readOutputLines = 0
- lastMatch = 0
+ # 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:]
- # Check and output lines which remain to be matched.
+ # 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, lineFilter, varState):
+ matchLineNo = 0
+ for outputLine in outputLines:
+ matchLineNo += 1
+ if matchLineNo in lineFilter:
+ continue
+ newVarState = checkLine.match(outputLine, varState)
+ if newVarState is not None:
+ return matchLineNo, newVarState
+ 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, varState):
+ # If no checks are provided, skip over the entire output.
+ if not checkLines:
+ return 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, matchedLines, varState)
+ if varState is None:
+ raise Exception("Could not match line " + str(checkLine))
+ matchedLines.append(matchLineNo)
+
+ # Return new variable state and the output lines which lie outside the
+ # match locations of this independent group.
+ preceedingLines = outputLines[:min(matchedLines)-1]
+ remainingLines = outputLines[max(matchedLines):]
+ return preceedingLines, remainingLines, 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, varState):
+ for checkLine in checkLines:
+ assert checkLine.variant == CheckLine.Variant.Not
+ matchLineNo, varState = self.__findFirstMatch(checkLine, outputLines, [], varState)
+ if varState is not None:
+ raise Exception("CHECK-NOT line " + str(checkLine) + " matches output")
+
+ # 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
- varState = {}
- # Retrieve the next check line.
while checkLines:
- checkLine, checkLines = self.__headAndTail(checkLines)
- foundMatch = False
-
- # Retrieve the next output line.
- while outputLines:
- outputLine, outputLines = self.__headAndTail(outputLines)
- readOutputLines += 1
-
- # Try to match the current lines against each other. If successful,
- # save the new state of variables and continue to the next check line.
- newVarState = checkLine.match(outputLine, varState)
- if newVarState is not None:
- varState = newVarState
- lastMatch = readOutputLines
- foundMatch = True
- break
- if not foundMatch:
- raise Exception("Could not match check line \"" + checkLine.content + "\" from line " +
- str(lastMatch+1) + " of the output. [vars=" + str(varState) + "]")
-
- @staticmethod
- def parse(name, lines):
- return CheckGroup(name, list(map(lambda line: CheckLine(line), lines)))
-
+ # Extract the next sequence of location-independent checks to be matched.
+ notChecks, independentChecks, checkLines = self.__nextIndependentChecks(checkLines)
+ # Match the independent checks.
+ notOutput, outputLines, newVarState = \
+ self.__matchIndependentChecks(independentChecks, outputLines, 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, varState)
+ # Update variable state.
+ varState = newVarState
class OutputGroup(CommonEqualityMixin):
"""Represents a named part of the test output against which a check group of
@@ -378,20 +458,35 @@
return None
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:
- # Line starts with the CHECK-START keyword, start a new group
- return (None, startLine)
- else:
- # Otherwise try to parse it as a standard CHECK line. If unsuccessful,
- # _extractLine will return None and the line will be ignored.
- return (self._extractLine(self.prefix, line), 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), 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), 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), None
+
+ # Other lines are ignored.
+ return None, None
def _exceptionLineOutsideGroup(self, line, lineNo):
raise Exception("Check file line lies outside a group (line " + str(lineNo) + ")")
def _processGroup(self, name, lines):
- return CheckGroup.parse(name, lines)
+ checkLines = list(map(lambda line: CheckLine(line[0], line[1]), lines))
+ return CheckGroup(name, checkLines)
def match(self, outputFile, printInfo=False):
for checkGroup in self.groups:
diff --git a/tools/checker_test.py b/tools/checker_test.py
index f69f9e3..8947d8a 100755
--- a/tools/checker_test.py
+++ b/tools/checker_test.py
@@ -64,6 +64,9 @@
def __parsesTo(self, string, expected):
self.assertEqual(expected, self.__getRegex(self.__tryParse(string)))
+ def __tryParseNot(self, string):
+ return checker.CheckLine(string, checker.CheckLine.Variant.UnorderedNot)
+
def __parsesPattern(self, string, pattern):
line = self.__tryParse(string)
self.assertEqual(1, len(line.lineParts))
@@ -163,6 +166,9 @@
self.__parsesTo("{{abc}}{{def}}", "(abc)(def)")
self.__parsesTo("[[ABC:abc]][[DEF:def]]", "(abc)(def)")
+ def test_NoVarDefsInNotChecks(self):
+ with self.assertRaises(Exception):
+ self.__tryParseNot("[[ABC:abc]]")
class TestCheckLine_Match(unittest.TestCase):
def __matchSingle(self, checkString, outputString, varState={}):
@@ -228,9 +234,23 @@
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, checkString, outputString):
- checkGroup = checker.CheckGroup.parse("MyGroup", checkString.splitlines())
+ def __matchMulti(self, checkLines, outputString):
+ checkGroup = checker.CheckGroup("MyGroup", prepareChecks(checkLines))
outputGroup = checker.OutputGroup("MyGroup", outputString.splitlines())
return checkGroup.match(outputGroup)
@@ -271,14 +291,62 @@
### 1234 ###""");
def test_Ordering(self):
- self.__matchMulti("""foo
- bar""",
+ self.__matchMulti([("foo", CheckVariant.InOrder),
+ ("bar", CheckVariant.InOrder)],
"""foo
bar""")
- self.__notMatchMulti("""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""")
+
+ 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):
@@ -355,7 +423,7 @@
self.__parsesTo("""// CHECK-START: Example Group
// CHECK: foo
// CHECK: bar""",
- [ checker.CheckGroup.parse("Example Group", [ "foo", "bar" ]) ])
+ [ checker.CheckGroup("Example Group", prepareChecks([ "foo", "bar" ])) ])
def test_MultipleGroups(self):
self.__parsesTo("""// CHECK-START: Example Group1
@@ -364,8 +432,20 @@
// CHECK-START: Example Group2
// CHECK: abc
// CHECK: def""",
- [ checker.CheckGroup.parse("Example Group1", [ "foo", "bar" ]),
- checker.CheckGroup.parse("Example Group2", [ "abc", "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__':
unittest.main()