1 /*
   2  * Copyright (c) 1998, 2006, 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 /**
  25  *
  26  */
  27 
  28 import java.io.*;
  29 import java.util.Arrays;
  30 import java.util.Properties;
  31 import java.util.StringTokenizer;
  32 
  33 /**
  34  * RMI regression test utility class that uses Runtime.exec to spawn a
  35  * java process that will run a named java class.
  36  */
  37 public class JavaVM {
  38 
  39     protected Process vm = null;
  40 
  41     private String classname = "";
  42     private String args = "";
  43     private String options = "";
  44     private OutputStream outputStream = System.out;
  45     private OutputStream errorStream = System.err;
  46     private String policyFileName = null;
  47 
  48     // This is used to shorten waiting time at startup.
  49     private volatile boolean started = false;
  50     private boolean forcesOutput = true; // default behavior
  51 
  52     private static void mesg(Object mesg) {
  53         System.err.println("JAVAVM: " + mesg.toString());
  54     }
  55 
  56     /** string name of the program execd by JavaVM */
  57     private static String javaProgram = "java";
  58 
  59     static {
  60         try {
  61             javaProgram = TestLibrary.getProperty("java.home", "") +
  62                 File.separator + "bin" + File.separator + javaProgram;
  63         } catch (SecurityException se) {
  64         }
  65     }
  66 
  67     public JavaVM(String classname) {
  68         this.classname = classname;
  69     }
  70     public JavaVM(String classname,
  71                   String options, String args) {
  72         this.classname = classname;
  73         this.options = options;
  74         this.args = args;
  75     }
  76 
  77     public JavaVM(String classname,
  78                   String options, String args,
  79                   OutputStream out, OutputStream err) {
  80         this(classname, options, args);
  81         this.outputStream = out;
  82         this.errorStream = err;
  83     }
  84 
  85     /* This constructor will instantiate a JavaVM object for which caller
  86      * can ask for forcing initial version output on child vm process
  87      * (if forcesVersionOutput is true), or letting the started vm behave freely
  88      * (when forcesVersionOutput is false).
  89      */
  90     public JavaVM(String classname,
  91                   String options, String args,
  92                   OutputStream out, OutputStream err,
  93                   boolean forcesVersionOutput) {
  94         this(classname, options, args, out, err);
  95         this.forcesOutput = forcesVersionOutput;
  96     }
  97 
  98 
  99     public void setStarted() {
 100         started = true;
 101     }
 102 
 103     // Prepends passed opts array to current options
 104     public void addOptions(String[] opts) {
 105         String newOpts = "";
 106         for (int i = 0 ; i < opts.length ; i ++) {
 107             newOpts += " " + opts[i];
 108         }
 109         newOpts += " ";
 110         options = newOpts + options;
 111     }
 112 
 113     // Prepends passed arguments array to current args
 114     public void addArguments(String[] arguments) {
 115         String newArgs = "";
 116         for (int i = 0 ; i < arguments.length ; i ++) {
 117             newArgs += " " + arguments[i];
 118         }
 119         newArgs += " ";
 120         args = newArgs + args;
 121     }
 122 
 123     public void setPolicyFile(String policyFileName) {
 124         this.policyFileName = policyFileName;
 125     }
 126 
 127     /**
 128      * This method is used for setting VM options on spawned VMs.
 129      * It returns the extra command line options required
 130      * to turn on jcov code coverage analysis.
 131      */
 132     protected static String getCodeCoverageOptions() {
 133         return TestLibrary.getExtraProperty("jcov.options","");
 134     }
 135 
 136     public void start(Runnable runnable) throws IOException {
 137         if (runnable == null) {
 138             throw new NullPointerException("Runnable cannot be null.");
 139         }
 140 
 141         start();
 142         new JavaVMCallbackHandler(runnable).start();
 143     }
 144 
 145     /**
 146      * Exec the VM as specified in this object's constructor.
 147      */
 148     public void start() throws IOException {
 149 
 150         if (vm != null) return;
 151 
 152         /*
 153          * If specified, add option for policy file
 154          */
 155         if (policyFileName != null) {
 156             String option = "-Djava.security.policy=" + policyFileName;
 157             addOptions(new String[] { option });
 158         }
 159 
 160         addOptions(new String[] { getCodeCoverageOptions() });
 161 
 162         /*
 163          * If forcesOutput is true :
 164          *  We force the new starting vm to output something so that we can know
 165          *  when it is effectively started by redirecting standard output through
 166          *  the next StreamPipe call (the vm is considered started when a first
 167          *  output has been streamed out).
 168          *  We do this by prepnding a "-showversion" option in the command line.
 169          */
 170         if (forcesOutput) {
 171             addOptions(new String[] {"-showversion"});
 172         }
 173 
 174         StringTokenizer optionsTokenizer = new StringTokenizer(options);
 175         StringTokenizer argsTokenizer = new StringTokenizer(args);
 176         int optionsCount = optionsTokenizer.countTokens();
 177         int argsCount = argsTokenizer.countTokens();
 178 
 179         String javaCommand[] = new String[optionsCount + argsCount + 2];
 180         int count = 0;
 181 
 182         javaCommand[count++] = JavaVM.javaProgram;
 183         while (optionsTokenizer.hasMoreTokens()) {
 184             javaCommand[count++] = optionsTokenizer.nextToken();
 185         }
 186         javaCommand[count++] = classname;
 187         while (argsTokenizer.hasMoreTokens()) {
 188             javaCommand[count++] = argsTokenizer.nextToken();
 189         }
 190 
 191         mesg("command = " + Arrays.asList(javaCommand).toString());
 192         System.err.println("");
 193 
 194         vm = Runtime.getRuntime().exec(javaCommand);
 195 
 196         /* output from the execed process may optionally be captured. */
 197         StreamPipe.plugTogether(this, vm.getInputStream(), this.outputStream);
 198         StreamPipe.plugTogether(this, vm.getErrorStream(), this.errorStream);
 199 
 200         try {
 201             if (forcesOutput) {
 202                 // Wait distant vm to start, by using waiting time slices of 100 ms.
 203                 // Wait at most for 2secs, after it considers the vm to be started.
 204                 final long vmStartSleepTime = 100;
 205                 final int maxTrials = 20;
 206                 int numTrials = 0;
 207                 while (!started && numTrials < maxTrials) {
 208                     numTrials++;
 209                     Thread.sleep(vmStartSleepTime);
 210                 }
 211 
 212                 // Outputs running status of distant vm
 213                 String message =
 214                     "after " + (numTrials * vmStartSleepTime) + " milliseconds";
 215                 if (started) {
 216                     mesg("distant vm process running, " + message);
 217                 }
 218                 else {
 219                     mesg("unknown running status of distant vm process, " + message);
 220                 }
 221             }
 222             else {
 223                 // Since we have no way to know if the distant vm is started,
 224                 // we just consider the vm to be started after a 2secs waiting time.
 225                 Thread.sleep(2000);
 226                 mesg("distant vm considered to be started after a waiting time of 2 secs");
 227             }
 228         } catch (InterruptedException e) {
 229             Thread.currentThread().interrupt();
 230             mesg("Thread interrupted while checking if distant vm is started. Giving up check.");
 231             mesg("Distant vm state unknown");
 232             return;
 233         }
 234     }
 235 
 236     public void destroy() {
 237         if (vm != null) {
 238             vm.destroy();
 239         }
 240         vm = null;
 241     }
 242 
 243     protected Process getVM() {
 244         return vm;
 245     }
 246 
 247     /**
 248      * Handles calling the callback.
 249      */
 250     private class JavaVMCallbackHandler extends Thread {
 251         Runnable runnable;
 252 
 253         JavaVMCallbackHandler(Runnable runnable) {
 254             this.runnable = runnable;
 255         }
 256 
 257 
 258         /**
 259          * Wait for the Process to terminate and notify the callback.
 260          */
 261         @Override
 262         public void run() {
 263             if (vm != null) {
 264                 try {
 265                     vm.waitFor();
 266                 } catch(InterruptedException ie) {
 267                     // Restore the interrupted status
 268                     Thread.currentThread().interrupt();
 269                 }
 270             }
 271 
 272             if (runnable != null) {
 273                 runnable.run();
 274             }
 275         }
 276     }
 277 }