ART: Implement next-line assertions in Checker

Some tests require verifying an exact sequence of lines in the graph
dump. This was already possible by inserting 'CHECK-NOT: {{.*}}'
between the individual lines, but hardly a convenient way of doing so.
This patch introduces a new 'CHECK-NEXT' kind of assertions that
replaces the old method and will become useful for testing assembly.

Change-Id: I1bb951707bda44320166dc7ef828866a6957a113
diff --git a/test/476-checker-ctor-memory-barrier/src/Main.java b/test/476-checker-ctor-memory-barrier/src/Main.java
index 75cb1d7..f24dc4a 100644
--- a/test/476-checker-ctor-memory-barrier/src/Main.java
+++ b/test/476-checker-ctor-memory-barrier/src/Main.java
@@ -27,9 +27,8 @@
   public ClassWithFinals obj;
 
   // CHECK-START: void ClassWithFinals.<init>(boolean) register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
   public ClassWithFinals(boolean cond) {
     x = 0;
     if (cond) {
@@ -39,18 +38,16 @@
   }
 
   // CHECK-START: void ClassWithFinals.<init>() register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
   public ClassWithFinals() {
     x = 0;
   }
 
   // CHECK-START: void ClassWithFinals.<init>(int) register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
   public ClassWithFinals(int x) {
     // This should have two barriers:
     //   - one for the constructor
@@ -62,33 +59,32 @@
 
 class InheritFromClassWithFinals extends ClassWithFinals {
   // CHECK-START: void InheritFromClassWithFinals.<init>() register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
 
   // CHECK-START: void InheritFromClassWithFinals.<init>() register (after)
-  // CHECK-NOT: InvokeStaticOrDirect
+  // CHECK-NOT:  InvokeStaticOrDirect
   public InheritFromClassWithFinals() {
     // Should inline the super constructor.
   }
 
   // CHECK-START: void InheritFromClassWithFinals.<init>(boolean) register (after)
-  // CHECK:     InvokeStaticOrDirect
+  // CHECK:      InvokeStaticOrDirect
 
   // CHECK-START: void InheritFromClassWithFinals.<init>(boolean) register (after)
-  // CHECK-NOT: MemoryBarrier kind:StoreStore
+  // CHECK-NOT:  MemoryBarrier kind:StoreStore
   public InheritFromClassWithFinals(boolean cond) {
     super(cond);
     // should not inline the super constructor
   }
 
   // CHECK-START: void InheritFromClassWithFinals.<init>(int) register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK:      ReturnVoid
 
   // CHECK-START: void InheritFromClassWithFinals.<init>(int) register (after)
-  // CHECK-NOT: InvokeStaticOrDirect
+  // CHECK-NOT:  InvokeStaticOrDirect
   public InheritFromClassWithFinals(int unused) {
     // Should inline the super constructor and insert a memory barrier.
 
@@ -101,9 +97,8 @@
   final int y;
 
   // CHECK-START: void HaveFinalsAndInheritFromClassWithFinals.<init>() register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
 
   // CHECK-START: void HaveFinalsAndInheritFromClassWithFinals.<init>() register (after)
   // CHECK-NOT: InvokeStaticOrDirect
@@ -113,10 +108,9 @@
   }
 
   // CHECK-START: void HaveFinalsAndInheritFromClassWithFinals.<init>(boolean) register (after)
-  // CHECK:     InvokeStaticOrDirect
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      InvokeStaticOrDirect
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
   public HaveFinalsAndInheritFromClassWithFinals(boolean cond) {
     super(cond);
     // should not inline the super constructor
@@ -124,14 +118,13 @@
   }
 
   // CHECK-START: void HaveFinalsAndInheritFromClassWithFinals.<init>(int) register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
 
   // CHECK-START: void HaveFinalsAndInheritFromClassWithFinals.<init>(int) register (after)
-  // CHECK-NOT: InvokeStaticOrDirect
+  // CHECK-NOT:  InvokeStaticOrDirect
   public HaveFinalsAndInheritFromClassWithFinals(int unused) {
     // Should inline the super constructor and keep just one memory barrier.
     y = 0;
@@ -146,55 +139,51 @@
 public class Main {
 
   // CHECK-START: ClassWithFinals Main.noInlineNoConstructorBarrier() register (after)
-  // CHECK:     InvokeStaticOrDirect
+  // CHECK:      InvokeStaticOrDirect
 
   // CHECK-START: ClassWithFinals Main.noInlineNoConstructorBarrier() register (after)
-  // CHECK-NOT: MemoryBarrier kind:StoreStore
+  // CHECK-NOT:  MemoryBarrier kind:StoreStore
   public static ClassWithFinals noInlineNoConstructorBarrier() {
     return new ClassWithFinals(false);
   }
 
   // CHECK-START: void Main.inlineNew() register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
 
   // CHECK-START: void Main.inlineNew() register (after)
-  // CHECK-NOT: InvokeStaticOrDirect
+  // CHECK-NOT:  InvokeStaticOrDirect
   public static void inlineNew() {
     new ClassWithFinals();
   }
 
   // CHECK-START: void Main.inlineNew1() register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
 
   // CHECK-START: void Main.inlineNew1() register (after)
-  // CHECK-NOT: InvokeStaticOrDirect
+  // CHECK-NOT:  InvokeStaticOrDirect
   public static void inlineNew1() {
     new InheritFromClassWithFinals();
   }
 
   // CHECK-START: void Main.inlineNew2() register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
 
   // CHECK-START: void Main.inlineNew2() register (after)
-  // CHECK-NOT: InvokeStaticOrDirect
+  // CHECK-NOT:  InvokeStaticOrDirect
   public static void inlineNew2() {
     new HaveFinalsAndInheritFromClassWithFinals();
   }
 
   // CHECK-START: void Main.inlineNew3() register (after)
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK:     MemoryBarrier kind:StoreStore
-  // CHECK-NOT: {{.*}}
-  // CHECK:     ReturnVoid
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK:      MemoryBarrier kind:StoreStore
+  // CHECK-NEXT: ReturnVoid
 
   // CHECK-START: void Main.inlineNew3() register (after)
-  // CHECK-NOT: InvokeStaticOrDirect
+  // CHECK-NOT:  InvokeStaticOrDirect
   public static void inlineNew3() {
     new HaveFinalsAndInheritFromClassWithFinals();
     new HaveFinalsAndInheritFromClassWithFinals();
diff --git a/tools/checker/file_format/checker/parser.py b/tools/checker/file_format/checker/parser.py
index d7a38da..4eed391 100644
--- a/tools/checker/file_format/checker/parser.py
+++ b/tools/checker/file_format/checker/parser.py
@@ -54,6 +54,11 @@
   if plainLine is not None:
     return (plainLine, TestAssertion.Variant.InOrder, lineNo), None
 
+  # 'CHECK-NEXT' lines are in-order but must match the very next line.
+  nextLine = __extractLine(prefix + "-NEXT", line)
+  if nextLine is not None:
+    return (nextLine, TestAssertion.Variant.NextLine, lineNo), None
+
   # 'CHECK-DAG' lines are no-order assertions.
   dagLine = __extractLine(prefix + "-DAG", line)
   if dagLine is not None:
diff --git a/tools/checker/file_format/checker/struct.py b/tools/checker/file_format/checker/struct.py
index 381c92b..6a54142 100644
--- a/tools/checker/file_format/checker/struct.py
+++ b/tools/checker/file_format/checker/struct.py
@@ -42,7 +42,7 @@
     self.startLineNo = startLineNo
 
     if not self.name:
-      Logger.fail("Test case does not have a name", self.parent.fileName, self.startLineNo)
+      Logger.fail("Test case does not have a name", self.fileName, self.startLineNo)
 
     self.parent.addTestCase(self)
 
@@ -51,6 +51,13 @@
     return self.parent.fileName
 
   def addAssertion(self, new_assertion):
+    if new_assertion.variant == TestAssertion.Variant.NextLine:
+      if not self.assertions or \
+         (self.assertions[-1].variant != TestAssertion.Variant.InOrder and \
+          self.assertions[-1].variant != TestAssertion.Variant.NextLine):
+        Logger.fail("A next-line assertion can only be placed after an "
+                    "in-order assertion or another next-line assertion.",
+                    new_assertion.fileName, new_assertion.lineNo)
     self.assertions.append(new_assertion)
 
   def __eq__(self, other):
@@ -63,7 +70,7 @@
 
   class Variant(object):
     """Supported types of assertions."""
-    InOrder, DAG, Not = range(3)
+    InOrder, NextLine, DAG, Not = range(4)
 
   def __init__(self, parent, variant, originalText, lineNo):
     assert isinstance(parent, TestCase)
diff --git a/tools/checker/file_format/checker/test.py b/tools/checker/file_format/checker/test.py
index 475e8c3..453deed 100644
--- a/tools/checker/file_format/checker/test.py
+++ b/tools/checker/file_format/checker/test.py
@@ -192,9 +192,12 @@
 
   def assertParsesTo(self, checkerText, expectedData):
     expectedFile = self.createFile(expectedData)
-    actualFile = ParseCheckerStream("<test_file>", "CHECK", io.StringIO(ToUnicode(checkerText)))
+    actualFile = self.parse(checkerText)
     return self.assertEqual(expectedFile, actualFile)
 
+  def parse(self, checkerText):
+    return ParseCheckerStream("<test_file>", "CHECK", io.StringIO(ToUnicode(checkerText)))
+
   def test_EmptyFile(self):
     self.assertParsesTo("", [])
 
@@ -227,12 +230,40 @@
     self.assertParsesTo(
       """
         // CHECK-START: Example Group
-        // CHECK:     foo
-        // CHECK-NOT: bar
-        // CHECK-DAG: abc
-        // CHECK-DAG: def
+        // CHECK:      foo1
+        // CHECK:      foo2
+        // CHECK-NEXT: foo3
+        // CHECK-NEXT: foo4
+        // CHECK-NOT:  bar
+        // CHECK-DAG:  abc
+        // CHECK-DAG:  def
       """,
-      [ ( "Example Group", [ ("foo", TestAssertion.Variant.InOrder),
+      [ ( "Example Group", [ ("foo1", TestAssertion.Variant.InOrder),
+                             ("foo2", TestAssertion.Variant.InOrder),
+                             ("foo3", TestAssertion.Variant.NextLine),
+                             ("foo4", TestAssertion.Variant.NextLine),
                              ("bar", TestAssertion.Variant.Not),
                              ("abc", TestAssertion.Variant.DAG),
                              ("def", TestAssertion.Variant.DAG) ] ) ])
+
+  def test_MisplacedNext(self):
+    with self.assertRaises(CheckerException):
+      self.parse(
+        """
+          // CHECK-START: Example Group
+          // CHECK-DAG:  foo
+          // CHECK-NEXT: bar
+        """)
+    with self.assertRaises(CheckerException):
+      self.parse(
+        """
+          // CHECK-START: Example Group
+          // CHECK-NOT:  foo
+          // CHECK-NEXT: bar
+        """)
+    with self.assertRaises(CheckerException):
+      self.parse(
+        """
+          // CHECK-START: Example Group
+          // CHECK-NEXT: bar
+        """)
diff --git a/tools/checker/match/file.py b/tools/checker/match/file.py
index 6cff2bf..b22211a 100644
--- a/tools/checker/match/file.py
+++ b/tools/checker/match/file.py
@@ -127,6 +127,11 @@
       assert len(assertionGroup) == 1
       scope = MatchScope(matchFrom, c1Length)
       match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables)
+    elif assertionGroup[0].variant == TestAssertion.Variant.NextLine:
+      # Single next-line assertion. Test if the current line matches.
+      assert len(assertionGroup) == 1
+      scope = MatchScope(matchFrom, matchFrom + 1)
+      match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables)
     else:
       # A group of DAG assertions. Match them all starting from the same point.
       assert assertionGroup[0].variant == TestAssertion.Variant.DAG
diff --git a/tools/checker/match/test.py b/tools/checker/match/test.py
index e4dd784..348c1d2 100644
--- a/tools/checker/match/test.py
+++ b/tools/checker/match/test.py
@@ -195,6 +195,54 @@
       foo
     """)
 
+  def test_NextLineAssertions(self):
+    self.assertMatches(
+    """
+      // CHECK:      foo
+      // CHECK-NEXT: bar
+      // CHECK-NEXT: abc
+      // CHECK:      def
+    """,
+    """
+      foo
+      bar
+      abc
+      def
+    """)
+    self.assertMatches(
+    """
+      // CHECK:      foo
+      // CHECK-NEXT: bar
+      // CHECK:      def
+    """,
+    """
+      foo
+      bar
+      abc
+      def
+    """)
+    self.assertDoesNotMatch(
+    """
+      // CHECK:      foo
+      // CHECK-NEXT: bar
+    """,
+    """
+      foo
+      abc
+      bar
+    """)
+
+    self.assertDoesNotMatch(
+    """
+      // CHECK:      foo
+      // CHECK-NEXT: bar
+    """,
+    """
+      bar
+      foo
+      abc
+    """)
+
   def test_DagAssertions(self):
     self.assertMatches(
     """