1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import java.io.BufferedReader;
18import java.io.FileReader;
19import java.io.InputStreamReader;
20import java.util.Arrays;
21import java.util.Comparator;
22
23public class Main implements Comparator<Main> {
24  // Whether to test local unwinding.
25  private boolean testLocal;
26
27  // Unwinding another process, modelling debuggerd.
28  private boolean testRemote;
29
30  // We fork ourself to create the secondary process for remote unwinding.
31  private boolean secondary;
32
33  // Expect the symbols to contain full method signatures including parameters.
34  private boolean fullSignatures;
35
36  private boolean passed;
37
38  public Main(String[] args) throws Exception {
39      System.loadLibrary(args[0]);
40      for (String arg : args) {
41          if (arg.equals("--test-local")) {
42              testLocal = true;
43          }
44          if (arg.equals("--test-remote")) {
45              testRemote = true;
46          }
47          if (arg.equals("--secondary")) {
48              secondary = true;
49          }
50          if (arg.equals("--full-signatures")) {
51              fullSignatures = true;
52          }
53      }
54      if (!testLocal && !testRemote) {
55          System.out.println("No test selected.");
56      }
57  }
58
59  public static void main(String[] args) throws Exception {
60      new Main(args).run();
61  }
62
63  private void run() {
64      if (secondary) {
65          if (!testRemote) {
66              throw new RuntimeException("Should not be running secondary!");
67          }
68          runSecondary();
69      } else {
70          runPrimary();
71      }
72  }
73
74  private void runSecondary() {
75      foo();
76      throw new RuntimeException("Didn't expect to get back...");
77  }
78
79  private void runPrimary() {
80      // First do the in-process unwinding.
81      if (testLocal && !foo()) {
82          System.out.println("Unwinding self failed.");
83      }
84
85      if (!testRemote) {
86          // Skip the remote step.
87          return;
88      }
89
90      // Fork the secondary.
91      String[] cmdline = getCmdLine();
92      String[] secCmdLine = new String[cmdline.length + 1];
93      System.arraycopy(cmdline, 0, secCmdLine, 0, cmdline.length);
94      secCmdLine[secCmdLine.length - 1] = "--secondary";
95      Process p = exec(secCmdLine);
96
97      try {
98          int pid = getPid(p);
99          if (pid <= 0) {
100              throw new RuntimeException("Couldn't parse process");
101          }
102
103          // Wait until the forked process had time to run until its sleep phase.
104          try {
105              InputStreamReader stdout = new InputStreamReader(p.getInputStream(), "UTF-8");
106              BufferedReader lineReader = new BufferedReader(stdout);
107              while (!lineReader.readLine().contains("Going to sleep")) {
108              }
109          } catch (Exception e) {
110              throw new RuntimeException(e);
111          }
112
113          if (!unwindOtherProcess(fullSignatures, pid)) {
114              System.out.println("Unwinding other process failed.");
115          }
116      } finally {
117          // Kill the forked process if it is not already dead.
118          p.destroy();
119      }
120  }
121
122  private static Process exec(String[] args) {
123      try {
124          return Runtime.getRuntime().exec(args);
125      } catch (Exception exc) {
126          throw new RuntimeException(exc);
127      }
128  }
129
130  private static int getPid(Process p) {
131      // Could do reflection for the private pid field, but String parsing is easier.
132      String s = p.toString();
133      if (s.startsWith("Process[pid=")) {
134          return Integer.parseInt(s.substring("Process[pid=".length(), s.indexOf(",")));
135      } else {
136          return -1;
137      }
138  }
139
140  // Read /proc/self/cmdline to find the invocation command line (so we can fork another runtime).
141  private static String[] getCmdLine() {
142      try {
143          BufferedReader in = new BufferedReader(new FileReader("/proc/self/cmdline"));
144          String s = in.readLine();
145          in.close();
146          return s.split("\0");
147      } catch (Exception exc) {
148          throw new RuntimeException(exc);
149      }
150  }
151
152  public boolean foo() {
153      // Call bar via Arrays.binarySearch.
154      // This tests that we can unwind from framework code.
155      Main[] array = { this, this, this };
156      Arrays.binarySearch(array, 0, 3, this /* value */, this /* comparator */);
157      return passed;
158  }
159
160  public int compare(Main lhs, Main rhs) {
161      passed = bar(secondary);
162      // Returning "equal" ensures that we terminate search
163      // after first item and thus call bar() only once.
164      return 0;
165  }
166
167  public boolean bar(boolean b) {
168      if (b) {
169          return sleep(2, b, 1.0);
170      } else {
171          return unwindInProcess(fullSignatures, 1, b);
172      }
173  }
174
175  // Native functions. Note: to avoid deduping, they must all have different signatures.
176
177  public native boolean sleep(int i, boolean b, double dummy);
178
179  public native boolean unwindInProcess(boolean fullSignatures, int i, boolean b);
180  public native boolean unwindOtherProcess(boolean fullSignatures, int pid);
181}
182