1 /*
   2  * Copyright (c) 1998, 2013, 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.File;
  25 import java.io.IOException;
  26 import java.io.OutputStream;
  27 import java.util.Arrays;
  28 import java.util.StringTokenizer;
  29 import java.util.concurrent.TimeoutException;
  30 import java.util.logging.Level;
  31 import java.util.logging.Logger;
  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     private StreamPipe outPipe;
  48     private StreamPipe errPipe;
  49 
  50     private static void mesg(Object mesg) {
  51         System.err.println("JAVAVM: " + mesg.toString());
  52     }
  53 
  54     /** string name of the program execd by JavaVM */
  55     private static String javaProgram = "java";
  56 
  57     static {
  58         try {
  59             javaProgram = TestLibrary.getProperty("java.home", "") +
  60                 File.separator + "bin" + File.separator + javaProgram;
  61         } catch (SecurityException se) {
  62         }
  63     }
  64 
  65     public JavaVM(String classname,
  66                   String options, String args) {
  67         this.classname = classname;
  68         this.options = options;
  69         this.args = args;
  70     }
  71 
  72     public JavaVM(String classname,
  73                   String options, String args,
  74                   OutputStream out, OutputStream err) {
  75         this(classname, options, args);
  76         this.outputStream = out;
  77         this.errorStream = err;
  78     }
  79 
  80     // Prepends passed opts array to current options
  81     public void addOptions(String[] opts) {
  82         String newOpts = "";
  83         for (int i = 0 ; i < opts.length ; i ++) {
  84             newOpts += " " + opts[i];
  85         }
  86         newOpts += " ";
  87         options = newOpts + options;
  88     }
  89 
  90     // Prepends passed arguments array to current args
  91     public void addArguments(String[] arguments) {
  92         String newArgs = "";
  93         for (int i = 0 ; i < arguments.length ; i ++) {
  94             newArgs += " " + arguments[i];
  95         }
  96         newArgs += " ";
  97         args = newArgs + args;
  98     }
  99 
 100     public void setPolicyFile(String policyFileName) {
 101         this.policyFileName = policyFileName;
 102     }
 103 
 104     /**
 105      * This method is used for setting VM options on spawned VMs.
 106      * It returns the extra command line options required
 107      * to turn on jcov code coverage analysis.
 108      */
 109     protected static String getCodeCoverageOptions() {
 110         return TestLibrary.getExtraProperty("jcov.options","");
 111     }
 112 
 113     /**
 114      * Exec the VM as specified in this object's constructor.
 115      */
 116     public void start() throws IOException {
 117 
 118         if (vm != null)
 119             throw new IllegalStateException("JavaVM already started");
 120 
 121         /*
 122          * If specified, add option for policy file
 123          */
 124         if (policyFileName != null) {
 125             String option = "-Djava.security.policy=" + policyFileName;
 126             addOptions(new String[] { option });
 127         }
 128 
 129         addOptions(new String[] { getCodeCoverageOptions() });
 130 
 131         StringTokenizer optionsTokenizer = new StringTokenizer(options);
 132         StringTokenizer argsTokenizer = new StringTokenizer(args);
 133         int optionsCount = optionsTokenizer.countTokens();
 134         int argsCount = argsTokenizer.countTokens();
 135 
 136         String javaCommand[] = new String[optionsCount + argsCount + 2];
 137         int count = 0;
 138 
 139         javaCommand[count++] = JavaVM.javaProgram;
 140         while (optionsTokenizer.hasMoreTokens()) {
 141             javaCommand[count++] = optionsTokenizer.nextToken();
 142         }
 143         javaCommand[count++] = classname;
 144         while (argsTokenizer.hasMoreTokens()) {
 145             javaCommand[count++] = argsTokenizer.nextToken();
 146         }
 147 
 148         mesg("command = " + Arrays.asList(javaCommand).toString());
 149 
 150         vm = Runtime.getRuntime().exec(javaCommand);
 151 
 152         /* output from the execed process may optionally be captured. */
 153         outPipe = StreamPipe.plugTogether(vm.getInputStream(), this.outputStream);
 154         errPipe = StreamPipe.plugTogether(vm.getErrorStream(), this.errorStream);
 155     }
 156 
 157     public void destroy() {
 158         if (vm != null) {
 159             vm.destroy();
 160         }
 161         vm = null;
 162     }
 163 
 164     /**
 165      * Waits for the subprocess to exit, joins the pipe threads to ensure that
 166      * all output is collected, and returns its exit status.
 167      */
 168     public int waitFor() throws InterruptedException {
 169         if (vm == null)
 170             throw new IllegalStateException("can't wait for JavaVM that isn't running");
 171 
 172         int status = vm.waitFor();
 173         outPipe.join();
 174         errPipe.join();
 175         return status;
 176     }
 177 
 178         /**
 179      * Causes the current thread to wait the subprocess to exit, if necessary,
 180      * until the subprocess represented by this Process object has terminated,
 181      * or the specified waiting time elapses.
 182      * @param timeout the maximum milliseconds to wait
 183      * @return true if the JavaVM has exited and false if the waiting time
 184      *         elapsed before the subprocess has exited.
 185      * @throws InterruptedException if the current thread is interrupted
 186      *         while waiting.
 187      * @throws TimeoutException if subprocess does not end after timeout
 188      *         milliseconds passed
 189      */
 190     public int waitFor(long timeout)
 191             throws InterruptedException, TimeoutException {
 192         long startTime = System.currentTimeMillis();
 193         long rem = timeout;
 194 
 195         do {
 196             try {
 197                 int status = vm.exitValue();
 198                 outPipe.join();
 199                 errPipe.join();
 200                 return status;
 201             } catch (IllegalThreadStateException ex) {
 202                 if (rem > 0) {
 203                     Thread.sleep(Math.min(rem, 100));
 204                 }
 205             }
 206             rem = timeout - (System.currentTimeMillis() - startTime);
 207         } while (rem > 0);
 208         new Thread(){
 209             @Override
 210             public void run() {
 211                 try {
 212                     JavaVM.this.waitFor();
 213                 } catch (InterruptedException ignore) {}
 214             }
 215         }.start();
 216         throw new TimeoutException();
 217     }
 218 
 219     /**
 220      * Starts the subprocess, waits for it to exit, and returns its exit status.
 221      */
 222     public int execute() throws IOException, InterruptedException {
 223         start();
 224         return waitFor();
 225     }
 226 }