1 /*
   2  * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.BufferedReader;
  25 import java.io.DataInputStream;
  26 import java.io.File;
  27 import java.io.IOException;
  28 import java.io.InputStreamReader;
  29 import java.io.OutputStream;
  30 import java.util.Arrays;
  31 import java.util.StringTokenizer;
  32 import java.util.concurrent.TimeoutException;
  33 
  34 /**
  35  * RMI regression test utility class that uses Runtime.exec to spawn a
  36  * java process that will run a named java class.
  37  */
  38 public class JavaVM {
  39 
  40     public static final long POLLTIME_MS = 100L;
  41 
  42     protected Process vm = null;
  43 
  44     private String classname = "";
  45     protected String args = "";
  46     protected String options = "";
  47     private OutputStream outputStream = System.out;
  48     private OutputStream errorStream = System.err;
  49     private String policyFileName = null;
  50     private StreamPipe outPipe;
  51     private StreamPipe errPipe;
  52 
  53     private static void mesg(Object mesg) {
  54         System.err.println("JAVAVM: " + mesg.toString());
  55     }
  56 
  57     /** string name of the program execd by JavaVM */
  58     private static String javaProgram = "java";
  59 
  60     static {
  61         try {
  62             javaProgram = TestLibrary.getProperty("java.home", "") +
  63                 File.separator + "bin" + File.separator + javaProgram;
  64         } catch (SecurityException se) {
  65         }
  66     }
  67 
  68     public JavaVM(String classname,
  69                   String options, String args) {
  70         this.classname = classname;
  71         this.options = options;
  72         this.args = args;
  73     }
  74 
  75     public JavaVM(String classname,
  76                   String options, String args,
  77                   OutputStream out, OutputStream err) {
  78         this(classname, options, args);
  79         this.outputStream = out;
  80         this.errorStream = err;
  81     }
  82 
  83     // Prepends passed opts array to current options
  84     public void addOptions(String... opts) {
  85         String newOpts = "";
  86         for (int i = 0 ; i < opts.length ; i ++) {
  87             newOpts += " " + opts[i];
  88         }
  89         newOpts += " ";
  90         options = newOpts + options;
  91     }
  92 
  93     // Prepends passed arguments array to current args
  94     public void addArguments(String... arguments) {
  95         String newArgs = "";
  96         for (int i = 0 ; i < arguments.length ; i ++) {
  97             newArgs += " " + arguments[i];
  98         }
  99         newArgs += " ";
 100         args = newArgs + args;
 101     }
 102 
 103     public void setPolicyFile(String policyFileName) {
 104         this.policyFileName = policyFileName;
 105     }
 106 
 107     /**
 108      * This method is used for setting VM options on spawned VMs.
 109      * It returns the extra command line options required
 110      * to turn on jcov code coverage analysis.
 111      */
 112     protected static String getCodeCoverageOptions() {
 113         return TestLibrary.getExtraProperty("jcov.options","");
 114     }
 115 
 116     /**
 117      * Exec the VM as specified in this object's constructor.
 118      */
 119     private void start0() throws IOException {
 120 
 121         if (vm != null)
 122             throw new IllegalStateException("JavaVM already started");
 123 
 124         /*
 125          * If specified, add option for policy file
 126          */
 127         if (policyFileName != null) {
 128             String option = "-Djava.security.policy=" + policyFileName;
 129             addOptions(new String[] { option });
 130         }
 131 
 132         addOptions(new String[] {
 133             getCodeCoverageOptions(),
 134             TestParams.testJavaOpts,
 135             TestParams.testVmOpts
 136         });
 137 
 138         StringTokenizer optionsTokenizer = new StringTokenizer(options);
 139         StringTokenizer argsTokenizer = new StringTokenizer(args);
 140         int optionsCount = optionsTokenizer.countTokens();
 141         int argsCount = argsTokenizer.countTokens();
 142 
 143         String javaCommand[] = new String[optionsCount + argsCount + 2];
 144         int count = 0;
 145 
 146         javaCommand[count++] = JavaVM.javaProgram;
 147         while (optionsTokenizer.hasMoreTokens()) {
 148             javaCommand[count++] = optionsTokenizer.nextToken();
 149         }
 150         javaCommand[count++] = classname;
 151         while (argsTokenizer.hasMoreTokens()) {
 152             javaCommand[count++] = argsTokenizer.nextToken();
 153         }
 154 
 155         mesg("command = " + Arrays.asList(javaCommand).toString());
 156 
 157         vm = Runtime.getRuntime().exec(javaCommand);
 158     }
 159 
 160     public void start() throws IOException {
 161         start0();
 162 
 163         /* output from the exec'ed process may optionally be captured. */
 164         outPipe = StreamPipe.plugTogether(vm.getInputStream(), this.outputStream);
 165         errPipe = StreamPipe.plugTogether(vm.getErrorStream(), this.errorStream);
 166     }
 167 
 168     public int startAndGetPort() throws IOException {
 169         start0();
 170 
 171         int port = -1;
 172         if (options.contains("java.nio.channels.spi.SelectorProvider=RMIDSelectorProvider")) {
 173             // Obtain the server socket channel's ephemeral port number of the
 174             // child rmid process.
 175             BufferedReader reader = new BufferedReader(
 176                     new InputStreamReader(vm.getInputStream()));
 177             String s;
 178             while ((s = reader.readLine()) != null) {
 179                 System.out.println(s);
 180                 int i = s.indexOf(RMID.EPHEMERAL_MSG);
 181                 if (i != -1) {
 182                     String v = s.substring(RMID.EPHEMERAL_MSG.length());
 183                     port = Integer.valueOf(v);
 184                     break;
 185                 }
 186             }
 187             if (port == -1) {
 188                 // something failed
 189                 reader = new BufferedReader(new InputStreamReader(vm.getErrorStream()));
 190                 while ((s = reader.readLine()) != null)
 191                     System.err.println(s);
 192             }
 193         }
 194 
 195         /* output from the exec'ed process may optionally be captured. */
 196         outPipe = StreamPipe.plugTogether(vm.getInputStream(), this.outputStream);
 197         errPipe = StreamPipe.plugTogether(vm.getErrorStream(), this.errorStream);
 198 
 199         return port;
 200     }
 201 
 202     public void destroy() {
 203         if (vm != null) {
 204             vm.destroy();
 205         }
 206         vm = null;
 207     }
 208 
 209     /**
 210      * Destroys the VM, waits for it to terminate, and returns
 211      * its exit status.
 212      *
 213      * @throws IllegalStateException if the VM has already been destroyed
 214      * @throws InterruptedException if the caller is interrupted while waiting
 215      */
 216     public int terminate() throws InterruptedException {
 217         if (vm == null) {
 218             throw new IllegalStateException("JavaVM already destroyed");
 219         }
 220 
 221         vm.destroy();
 222         int status = waitFor();
 223         vm = null;
 224         return status;
 225     }
 226 
 227 
 228     /**
 229      * Waits for the subprocess to exit, joins the pipe threads to ensure that
 230      * all output is collected, and returns its exit status.
 231      */
 232     public int waitFor() throws InterruptedException {
 233         if (vm == null)
 234             throw new IllegalStateException("can't wait for JavaVM that isn't running");
 235 
 236         int status = vm.waitFor();
 237         outPipe.join();
 238         errPipe.join();
 239         return status;
 240     }
 241 
 242     /**
 243      * Causes the current thread to wait the vm process to exit, if necessary,
 244      * wait until the vm process has terminated, or the specified waiting time
 245      * elapses. Release allocated input/output after vm process has terminated.
 246      * @param timeout the maximum milliseconds to wait.
 247      * @return exit value for vm process.
 248      * @throws InterruptedException if the current thread is interrupted
 249      *         while waiting.
 250      * @throws TimeoutException if subprocess does not end after timeout
 251      *         milliseconds passed
 252      */
 253     public int waitFor(long timeout)
 254             throws InterruptedException, TimeoutException {
 255         if (vm == null)
 256             throw new IllegalStateException("can't wait for JavaVM that isn't running");
 257         long deadline = TestLibrary.computeDeadline(System.currentTimeMillis(), timeout);
 258 
 259         while (true) {
 260             try {
 261                 int status = vm.exitValue();
 262                 outPipe.join();
 263                 errPipe.join();
 264                 return status;
 265             } catch (IllegalThreadStateException ignore) { }
 266 
 267             if (System.currentTimeMillis() > deadline)
 268                 throw new TimeoutException();
 269 
 270             Thread.sleep(POLLTIME_MS);
 271         }
 272     }
 273 
 274     /**
 275      * Starts the subprocess, waits for it to exit, and returns its exit status.
 276      */
 277     public int execute() throws IOException, InterruptedException {
 278         start();
 279         return waitFor();
 280     }
 281 }