ART: Checker tests for --debuggable

Checker was disabled for --debuggable because the code was not compiled
with Optimizing. Now that it is, we might want to write Checker tests
only for this mode. With this patch, CHECK-START(-ARCH)-DEBUGGABLE
tests will only be invoked on output of debuggable compilation.
Existing CHECK-START(-ARCH) tests will not be invoked.

Change-Id: I00c864f77b038af913d0d22ba7cf5655687f7c7c
diff --git a/test/537-checker-debuggable/expected.txt b/test/537-checker-debuggable/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/537-checker-debuggable/expected.txt
diff --git a/test/537-checker-debuggable/info.txt b/test/537-checker-debuggable/info.txt
new file mode 100644
index 0000000..25597d3
--- /dev/null
+++ b/test/537-checker-debuggable/info.txt
@@ -0,0 +1 @@
+Test that CHECK-START-DEBUGGABLE runs only on --debuggable code.
\ No newline at end of file
diff --git a/test/537-checker-debuggable/smali/TestCase.smali b/test/537-checker-debuggable/smali/TestCase.smali
new file mode 100644
index 0000000..8e6c7ef
--- /dev/null
+++ b/test/537-checker-debuggable/smali/TestCase.smali
@@ -0,0 +1,42 @@
+# 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 public LTestCase;
+
+.super Ljava/lang/Object;
+
+# The phi in this method has no actual uses but one environment use. It will
+# be eliminated in normal mode but kept live in debuggable mode. Test that
+# Checker runs the correct test for each compilation mode.
+
+## CHECK-START: int TestCase.deadPhi(int, int, int) ssa_builder (after)
+## CHECK-NOT:         Phi
+
+## CHECK-START-DEBUGGABLE: int TestCase.deadPhi(int, int, int) ssa_builder (after)
+## CHECK:             Phi
+
+.method public static deadPhi(III)I
+  .registers 8
+
+  move v0, p1
+  if-eqz p0, :after
+  move v0, p2
+  :after
+  # v0 = Phi [p1, p2] with no uses
+
+  invoke-static {}, Ljava/lang/System;->nanoTime()J  # create an env use
+
+  :return
+  return p2
+.end method
diff --git a/test/537-checker-debuggable/src/Main.java b/test/537-checker-debuggable/src/Main.java
new file mode 100644
index 0000000..a572648
--- /dev/null
+++ b/test/537-checker-debuggable/src/Main.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+public class Main {
+
+  // Workaround for b/18051191.
+  class InnerClass {}
+
+  public static void main(String[] args) { }
+}
diff --git a/test/run-test b/test/run-test
index a5b6e92..2892ce9 100755
--- a/test/run-test
+++ b/test/run-test
@@ -637,18 +637,24 @@
   # on a particular DEX output, keep building them with dx for now (b/19467889).
   USE_JACK="false"
 
-  if [ "$runtime" = "art" -a "$image_suffix" = "-optimizing" -a "$debuggable" = "no" ]; then
+  if [ "$runtime" = "art" -a "$image_suffix" = "-optimizing" ]; then
     # In no-prebuild mode, the compiler is only invoked if both dex2oat and
     # patchoat are available. Disable Checker otherwise (b/22552692).
     if [ "$prebuild_mode" = "yes" ] || [ "$have_patchoat" = "yes" -a "$have_dex2oat" = "yes" ]; then
       run_checker="yes"
+
       if [ "$target_mode" = "no" ]; then
         cfg_output_dir="$tmp_dir"
-        checker_arch_option="--arch=${host_arch_name^^}"
+        checker_args="--arch=${host_arch_name^^}"
       else
         cfg_output_dir="$DEX_LOCATION"
-        checker_arch_option="--arch=${target_arch_name^^}"
+        checker_args="--arch=${target_arch_name^^}"
       fi
+
+      if [ "$debuggable" = "yes" ]; then
+        checker_args="$checker_args --debuggable"
+      fi
+
       run_args="${run_args} -Xcompiler-option --dump-cfg=$cfg_output_dir/$cfg_output \
                             -Xcompiler-option -j1"
     fi
@@ -702,7 +708,7 @@
                 if [ "$target_mode" = "yes" ]; then
                   adb pull $cfg_output_dir/$cfg_output &> /dev/null
                 fi
-                "$checker" $checker_arch_option "$cfg_output" "$tmp_dir" 2>&1
+                "$checker" $checker_args "$cfg_output" "$tmp_dir" 2>&1
                 checker_exit="$?"
                 if [ "$checker_exit" = "0" ]; then
                     good="yes"
@@ -727,7 +733,7 @@
           if [ "$target_mode" = "yes" ]; then
             adb pull $cfg_output_dir/$cfg_output &> /dev/null
           fi
-          "$checker" -q $checker_arch_option "$cfg_output" "$tmp_dir" >> "$output" 2>&1
+          "$checker" -q $checker_args "$cfg_output" "$tmp_dir" >> "$output" 2>&1
         fi
         sed -e 's/[[:cntrl:]]$//g' < "$output" >"${td_expected}"
         good="yes"
@@ -768,7 +774,7 @@
             if [ "$target_mode" = "yes" ]; then
               adb pull $cfg_output_dir/$cfg_output &> /dev/null
             fi
-            "$checker" -q $checker_arch_option "$cfg_output" "$tmp_dir" >> "$output" 2>&1
+            "$checker" -q $checker_args "$cfg_output" "$tmp_dir" >> "$output" 2>&1
             checker_exit="$?"
             if [ "$checker_exit" != "0" ]; then
                 echo "checker exit status: $checker_exit" 1>&2
diff --git a/tools/checker/checker.py b/tools/checker/checker.py
index bc5e17d..2e9faba 100755
--- a/tools/checker/checker.py
+++ b/tools/checker/checker.py
@@ -36,7 +36,9 @@
   parser.add_argument("--dump-pass", dest="dump_pass", metavar="PASS",
                       help="print a compiler pass dump")
   parser.add_argument("--arch", dest="arch", choices=archs_list,
-                      help="Run the tests for the specified target architecture.")
+                      help="Run tests for the specified target architecture.")
+  parser.add_argument("--debuggable", action="store_true",
+                      help="Run tests for debuggable code.")
   parser.add_argument("-q", "--quiet", action="store_true",
                       help="print only errors")
   return parser.parse_args()
@@ -83,13 +85,13 @@
     Logger.fail("Source path \"" + path + "\" not found")
 
 
-def RunTests(checkPrefix, checkPath, outputFilename, targetArch):
+def RunTests(checkPrefix, checkPath, outputFilename, targetArch, debuggableMode):
   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, targetArch)
+    MatchFiles(checkerFile, c1File, targetArch, debuggableMode)
 
 
 if __name__ == "__main__":
@@ -103,4 +105,4 @@
   elif args.dump_pass:
     DumpPass(args.tested_file, args.dump_pass)
   else:
-    RunTests(args.check_prefix, args.source_path, args.tested_file, args.arch)
+    RunTests(args.check_prefix, args.source_path, args.tested_file, args.arch, args.debuggable)
diff --git a/tools/checker/file_format/checker/parser.py b/tools/checker/file_format/checker/parser.py
index 446302f..f199a50 100644
--- a/tools/checker/file_format/checker/parser.py
+++ b/tools/checker/file_format/checker/parser.py
@@ -22,7 +22,7 @@
 def __isCheckerLine(line):
   return line.startswith("///") or line.startswith("##")
 
-def __extractLine(prefix, line, arch = None):
+def __extractLine(prefix, line, arch = None, debuggable = False):
   """ 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.
@@ -30,10 +30,11 @@
   rIgnoreWhitespace = r"\s*"
   rCommentSymbols = [r"///", r"##"]
   arch_specifier = r"-%s" % arch if arch is not None else r""
+  dbg_specifier = r"-DEBUGGABLE" if debuggable else r""
   regexPrefix = rIgnoreWhitespace + \
                 r"(" + r"|".join(rCommentSymbols) + r")" + \
                 rIgnoreWhitespace + \
-                prefix + arch_specifier + r":"
+                prefix + arch_specifier + dbg_specifier + r":"
 
   # The 'match' function succeeds only if the pattern is matched at the
   # beginning of the line.
@@ -56,10 +57,11 @@
 
   # Lines beginning with 'CHECK-START' start a new test case.
   # We currently only consider the architecture suffix in "CHECK-START" lines.
-  for arch in [None] + archs_list:
-    startLine = __extractLine(prefix + "-START", line, arch)
-    if startLine is not None:
-      return None, startLine, arch
+  for debuggable in [True, False]:
+    for arch in [None] + archs_list:
+      startLine = __extractLine(prefix + "-START", line, arch, debuggable)
+      if startLine is not None:
+        return None, startLine, (arch, debuggable)
 
   # Lines starting only with 'CHECK' are matched in order.
   plainLine = __extractLine(prefix, line)
@@ -167,9 +169,11 @@
   fnProcessLine = lambda line, lineNo: __processLine(line, lineNo, prefix, fileName)
   fnLineOutsideChunk = lambda line, lineNo: \
       Logger.fail("Checker line not inside a group", fileName, lineNo)
-  for caseName, caseLines, startLineNo, testArch in \
+  for caseName, caseLines, startLineNo, testData in \
       SplitStream(stream, fnProcessLine, fnLineOutsideChunk):
-    testCase = TestCase(checkerFile, caseName, startLineNo, testArch)
+    testArch = testData[0]
+    forDebuggable = testData[1]
+    testCase = TestCase(checkerFile, caseName, startLineNo, testArch, forDebuggable)
     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
index 7ee09cd..a31aa54 100644
--- a/tools/checker/file_format/checker/struct.py
+++ b/tools/checker/file_format/checker/struct.py
@@ -36,7 +36,7 @@
 
 class TestCase(PrintableMixin):
 
-  def __init__(self, parent, name, startLineNo, testArch = None):
+  def __init__(self, parent, name, startLineNo, testArch = None, forDebuggable = False):
     assert isinstance(parent, CheckerFile)
 
     self.parent = parent
@@ -44,6 +44,7 @@
     self.assertions = []
     self.startLineNo = startLineNo
     self.testArch = testArch
+    self.forDebuggable = forDebuggable
 
     if not self.name:
       Logger.fail("Test case does not have a name", self.fileName, self.startLineNo)
diff --git a/tools/checker/file_format/checker/test.py b/tools/checker/file_format/checker/test.py
index 495dabc..579c190 100644
--- a/tools/checker/file_format/checker/test.py
+++ b/tools/checker/file_format/checker/test.py
@@ -290,7 +290,7 @@
           /// CHECK-NEXT: bar
         """)
 
-class CheckerParser_ArchTests(unittest.TestCase):
+class CheckerParser_SuffixTests(unittest.TestCase):
 
   noarch_block = """
                   /// CHECK-START: Group
@@ -308,11 +308,12 @@
                   /// CHECK-DAG:   yoyo
                 """
 
+  def parse(self, checkerText):
+    return ParseCheckerStream("<test_file>", "CHECK", io.StringIO(ToUnicode(checkerText)))
+
   def test_NonArchTests(self):
     for arch in [None] + archs_list:
-      checkerFile = ParseCheckerStream("<test-file>",
-                                       "CHECK",
-                                       io.StringIO(ToUnicode(self.noarch_block)))
+      checkerFile = self.parse(self.noarch_block)
       self.assertEqual(len(checkerFile.testCases), 1)
       self.assertEqual(len(checkerFile.testCases[0].assertions), 4)
 
@@ -320,9 +321,7 @@
     for targetArch in archs_list:
       for testArch in [a for a in archs_list if a != targetArch]:
         checkerText = self.arch_block.format(test_arch = testArch)
-        checkerFile = ParseCheckerStream("<test-file>",
-                                         "CHECK",
-                                         io.StringIO(ToUnicode(checkerText)))
+        checkerFile = self.parse(checkerText)
         self.assertEqual(len(checkerFile.testCases), 1)
         self.assertEqual(len(checkerFile.testCasesForArch(testArch)), 1)
         self.assertEqual(len(checkerFile.testCasesForArch(targetArch)), 0)
@@ -330,13 +329,42 @@
   def test_Arch(self):
     for arch in archs_list:
       checkerText = self.arch_block.format(test_arch = arch)
-      checkerFile = ParseCheckerStream("<test-file>",
-                                       "CHECK",
-                                       io.StringIO(ToUnicode(checkerText)))
+      checkerFile = self.parse(checkerText)
       self.assertEqual(len(checkerFile.testCases), 1)
       self.assertEqual(len(checkerFile.testCasesForArch(arch)), 1)
       self.assertEqual(len(checkerFile.testCases[0].assertions), 4)
 
+  def test_NoDebugAndArch(self):
+    testCase = self.parse("""
+        /// CHECK-START: Group
+        /// CHECK: foo
+        """).testCases[0]
+    self.assertFalse(testCase.forDebuggable)
+    self.assertEqual(testCase.testArch, None)
+
+  def test_SetDebugNoArch(self):
+    testCase = self.parse("""
+        /// CHECK-START-DEBUGGABLE: Group
+        /// CHECK: foo
+        """).testCases[0]
+    self.assertTrue(testCase.forDebuggable)
+    self.assertEqual(testCase.testArch, None)
+
+  def test_NoDebugSetArch(self):
+    testCase = self.parse("""
+        /// CHECK-START-ARM: Group
+        /// CHECK: foo
+        """).testCases[0]
+    self.assertFalse(testCase.forDebuggable)
+    self.assertEqual(testCase.testArch, "ARM")
+
+  def test_SetDebugAndArch(self):
+    testCase = self.parse("""
+        /// CHECK-START-ARM-DEBUGGABLE: Group
+        /// CHECK: foo
+        """).testCases[0]
+    self.assertTrue(testCase.forDebuggable)
+    self.assertEqual(testCase.testArch, "ARM")
 
 class CheckerParser_EvalTests(unittest.TestCase):
   def parseTestCase(self, string):
diff --git a/tools/checker/match/file.py b/tools/checker/match/file.py
index 6601a1e..3ded074 100644
--- a/tools/checker/match/file.py
+++ b/tools/checker/match/file.py
@@ -159,10 +159,13 @@
     matchFrom = match.scope.end + 1
     variables = match.variables
 
-def MatchFiles(checkerFile, c1File, targetArch):
+def MatchFiles(checkerFile, c1File, targetArch, debuggableMode):
   for testCase in checkerFile.testCases:
     if testCase.testArch not in [None, targetArch]:
       continue
+    if testCase.forDebuggable != debuggableMode:
+      continue
+
     # TODO: Currently does not handle multiple occurrences of the same group
     # name, e.g. when a pass is run multiple times. It will always try to
     # match a check group against the first output group of the same name.
diff --git a/tools/checker/run_unit_tests.py b/tools/checker/run_unit_tests.py
index 2e8f208..a0d274d 100755
--- a/tools/checker/run_unit_tests.py
+++ b/tools/checker/run_unit_tests.py
@@ -19,7 +19,7 @@
 from file_format.checker.test      import CheckerParser_PrefixTest, \
                                           CheckerParser_TestExpressionTest, \
                                           CheckerParser_FileLayoutTest, \
-                                          CheckerParser_ArchTests, \
+                                          CheckerParser_SuffixTests, \
                                           CheckerParser_EvalTests
 from match.test                    import MatchLines_Test, \
                                           MatchFiles_Test