Add JVMTI breakpoint and SingleStep tests

Tests for functionality related to the can_generate_single_step_events
and can_generate_breakpoint_events jvmti capabilities.

Bug: 62821960
Test: ./test.py --host -j40
Test: ./test/run-test --host --jvmti-step-stress 001-HelloWorld
Test: ./test/run-test --host --jvmti-step-stress 914
Change-Id: Id2a4db5a6de3a97726f03f5e735c2bb77dafe4ca
diff --git a/test/994-breakpoint-line/expected.txt b/test/994-breakpoint-line/expected.txt
new file mode 100644
index 0000000..5899659
--- /dev/null
+++ b/test/994-breakpoint-line/expected.txt
@@ -0,0 +1,34 @@
+Breaking on line: 29 calling with arg: true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=29
+	argument was true
+Breaking on line: 29 calling with arg: false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=29
+	argument was false
+Breaking on line: 30 calling with arg: true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=30
+	argument was true
+Breaking on line: 30 calling with arg: false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=30
+	argument was false
+Breaking on line: 31 calling with arg: true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=31
+	argument was true
+Breaking on line: 31 calling with arg: false
+	argument was false
+Breaking on line: 33 calling with arg: true
+	argument was true
+Breaking on line: 33 calling with arg: false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=33
+	argument was false
+Breaking on line: 35 calling with arg: true
+	argument was true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=35
+Breaking on line: 35 calling with arg: false
+	argument was false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=35
+Breaking on line: 36 calling with arg: true
+	argument was true
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=36
+Breaking on line: 36 calling with arg: false
+	argument was false
+	Breakpoint reached: public static void art.Test994.doMultiPath(boolean) @ line=36
diff --git a/test/994-breakpoint-line/info.txt b/test/994-breakpoint-line/info.txt
new file mode 100644
index 0000000..210dea0
--- /dev/null
+++ b/test/994-breakpoint-line/info.txt
@@ -0,0 +1,5 @@
+Test basic JVMTI breakpoint functionality.
+
+This test ensures we can place breakpoints on particular lines of a method. It
+sets breakpoints on each line in turn of a function with multiple execution
+paths and then runs the function, receiving the breakpoint events.
diff --git a/test/994-breakpoint-line/run b/test/994-breakpoint-line/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/994-breakpoint-line/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/994-breakpoint-line/src/Main.java b/test/994-breakpoint-line/src/Main.java
new file mode 100644
index 0000000..39cfeb3
--- /dev/null
+++ b/test/994-breakpoint-line/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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 {
+  public static void main(String[] args) throws Exception {
+    art.Test994.run();
+  }
+}
diff --git a/test/994-breakpoint-line/src/art/Breakpoint.java b/test/994-breakpoint-line/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/994-breakpoint-line/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/994-breakpoint-line/src/art/Test994.java b/test/994-breakpoint-line/src/art/Test994.java
new file mode 100644
index 0000000..6a1c354
--- /dev/null
+++ b/test/994-breakpoint-line/src/art/Test994.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package art;
+
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+
+public class Test994 {
+  public static final Breakpoint.Manager MANAGER = new Breakpoint.Manager();
+  public static void doNothing() {}
+
+  // Method with multiple paths we can break on.
+  public static void doMultiPath(boolean bit) {
+    doNothing();
+    if (bit) {
+      System.out.println("\targument was true");
+    } else {
+      System.out.println("\targument was false");
+    }
+    doNothing();
+  }
+
+  public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
+    System.out.println(
+        "\tBreakpoint reached: " + e + " @ line=" + Breakpoint.locationToLine(e, loc));
+  }
+
+  public static void run() throws Exception {
+    // Set up breakpoints
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+    Breakpoint.startBreakpointWatch(
+        Test994.class,
+        Test994.class.getDeclaredMethod(
+            "notifyBreakpointReached", Thread.class, Executable.class, Long.TYPE),
+        Thread.currentThread());
+
+    Method multipath_method = Test994.class.getDeclaredMethod("doMultiPath", Boolean.TYPE);
+
+    Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(multipath_method);
+
+    // Make sure everything is in the same order.
+    Arrays.sort(lines);
+
+    boolean[] values = new boolean[] { true, false };
+
+    for (Breakpoint.LineNumber line : lines) {
+      MANAGER.clearAllBreakpoints();
+      MANAGER.setBreakpoint(multipath_method, line.location);
+      for (boolean arg : values) {
+        System.out.println("Breaking on line: " + line.line + " calling with arg: " + arg);
+        doMultiPath(arg);
+      }
+    }
+
+    Breakpoint.stopBreakpointWatch(Thread.currentThread());
+  }
+}