content: /*
   * 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.
   */
  
  import java.io.BufferedReader;
  import java.io.FileReader;
  import java.io.InputStreamReader;
  import java.util.Arrays;
  import java.util.Comparator;
  
  public class Main implements Comparator<Main> {
    // Whether to test local unwinding.
    private boolean testLocal;
  
    // Unwinding another process, modelling debuggerd.
    private boolean testRemote;
  
    // We fork ourself to create the secondary process for remote unwinding.
    private boolean secondary;
  
    // Expect the symbols to contain full method signatures including parameters.
    private boolean fullSignatures;
  
    private boolean passed;
  
    public Main(String[] args) throws Exception {
        System.loadLibrary(args[0]);
        for (String arg : args) {
            if (arg.equals("--test-local")) {
                testLocal = true;
            }
            if (arg.equals("--test-remote")) {
                testRemote = true;
            }
            if (arg.equals("--secondary")) {
                secondary = true;
            }
            if (arg.equals("--full-signatures")) {
                fullSignatures = true;
            }
        }
        if (!testLocal && !testRemote) {
            System.out.println("No test selected.");
        }
    }
  
    public static void main(String[] args) throws Exception {
        new Main(args).run();
    }
  
    private void run() {
        if (secondary) {
            if (!testRemote) {
                throw new RuntimeException("Should not be running secondary!");
            }
            runSecondary();
        } else {
            runPrimary();
        }
    }
  
    private void runSecondary() {
        foo();
        throw new RuntimeException("Didn't expect to get back...");
    }
  
    private void runPrimary() {
        // First do the in-process unwinding.
        if (testLocal && !foo()) {
            System.out.println("Unwinding self failed.");
        }
  
        if (!testRemote) {
            // Skip the remote step.
            return;
        }
  
        // Fork the secondary.
        String[] cmdline = getCmdLine();
        String[] secCmdLine = new String[cmdline.length + 1];
        System.arraycopy(cmdline, 0, secCmdLine, 0, cmdline.length);
        secCmdLine[secCmdLine.length - 1] = "--secondary";
        Process p = exec(secCmdLine);
  
        try {
            int pid = getPid(p);
            if (pid <= 0) {
                throw new RuntimeException("Couldn't parse process");
            }
  
            // Wait until the forked process had time to run until its sleep phase.
            try {
                InputStreamReader stdout = new InputStreamReader(p.getInputStream(), "UTF-8");
                BufferedReader lineReader = new BufferedReader(stdout);
                while (!lineReader.readLine().contains("Going to sleep")) {
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
  
            if (!unwindOtherProcess(fullSignatures, pid)) {
                System.out.println("Unwinding other process failed.");
            }
        } finally {
            // Kill the forked process if it is not already dead.
            p.destroy();
        }
    }
  
    private static Process exec(String[] args) {
        try {
            return Runtime.getRuntime().exec(args);
        } catch (Exception exc) {
            throw new RuntimeException(exc);
        }
    }
  
    private static int getPid(Process p) {
        // Could do reflection for the private pid field, but String parsing is easier.
        String s = p.toString();
        if (s.startsWith("Process[pid=")) {
            return Integer.parseInt(s.substring("Process[pid=".length(), s.indexOf(",")));
        } else {
            return -1;
        }
    }
  
    // Read /proc/self/cmdline to find the invocation command line (so we can fork another runtime).
    private static String[] getCmdLine() {
        try {
            BufferedReader in = new BufferedReader(new FileReader("/proc/self/cmdline"));
            String s = in.readLine();
            in.close();
            return s.split("\0");
        } catch (Exception exc) {
            throw new RuntimeException(exc);
        }
    }
  
    public boolean foo() {
        // Call bar via Arrays.binarySearch.
        // This tests that we can unwind from framework code.
        Main[] array = { this, this, this };
        Arrays.binarySearch(array, 0, 3, this /* value */, this /* comparator */);
        return passed;
    }
  
    public int compare(Main lhs, Main rhs) {
        passed = bar(secondary);
        // Returning "equal" ensures that we terminate search
        // after first item and thus call bar() only once.
        return 0;
    }
  
    public boolean bar(boolean b) {
        if (b) {
            return sleep(2, b, 1.0);
        } else {
            return unwindInProcess(fullSignatures, 1, b);
        }
    }
  
    // Native functions. Note: to avoid deduping, they must all have different signatures.
  
    public native boolean sleep(int i, boolean b, double dummy);
  
    public native boolean unwindInProcess(boolean fullSignatures, int i, boolean b);
    public native boolean unwindOtherProcess(boolean fullSignatures, int pid);
  }
  