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.ByteArrayOutputStream;
  25 import java.io.File;
  26 import java.io.IOException;
  27 import java.io.OutputStream;
  28 import java.util.Arrays;
  29 import java.util.StringTokenizer;
  30 import java.util.concurrent.TimeoutException;
  31 
  32 /**
  33  * RMI regression test utility class that uses Runtime.exec to spawn a
  34  * java process that will run a named java class.
  35  */
  36 public class JavaVM {
  37 
  38     static class CachedOutputStream extends OutputStream {
  39         ByteArrayOutputStream ba;
  40         OutputStream os;
  41 
  42         public CachedOutputStream(OutputStream os) {
  43             this.os = os;
  44             this.ba = new ByteArrayOutputStream();
  45         }
  46 
  47         public void write(int b) throws IOException {
  48             ba.write(b);
  49             os.write(b);
  50         }
  51     }
  52 
  53         public static final long POLLTIME_MS = 100L;
  54 
  55     protected Process vm = null;
  56 
  57     private String classname = "";
  58     private String args = "";
  59     private String options = "";
  60     private CachedOutputStream outputStream = new CachedOutputStream(System.out);
  61     private CachedOutputStream errorStream = new CachedOutputStream(System.err);
  62     private String policyFileName = null;
  63     private StreamPipe outPipe;
  64     private StreamPipe errPipe;
  65 
  66     private static void mesg(Object mesg) {
  67         System.err.println("JAVAVM: " + mesg.toString());
  68     }
  69 
  70     /** string name of the program execd by JavaVM */
  71     private static String javaProgram = "java";
  72 
  73     static {
  74         try {
  75             javaProgram = TestLibrary.getProperty("java.home", "") +
  76                 File.separator + "bin" + File.separator + javaProgram;
  77         } catch (SecurityException se) {
  78         }
  79     }
  80 
  81     public JavaVM(String classname,
  82                   String options, String args) {
  83         this.classname = classname;
  84         this.options = options;
  85         this.args = args;
  86     }
  87 
  88     public JavaVM(String classname,
  89                   String options, String args,
  90                   OutputStream out, OutputStream err) {
  91         this(classname, options, args);
  92         this.outputStream = new CachedOutputStream(out);
  93         this.errorStream = new CachedOutputStream(err);
  94     }
  95 
  96     // Prepends passed opts array to current options
  97     public void addOptions(String... opts) {
  98         String newOpts = "";
  99         for (int i = 0 ; i < opts.length ; i ++) {
 100             newOpts += " " + opts[i];
 101         }
 102         newOpts += " ";
 103         options = newOpts + options;
 104     }
 105 
 106     // Prepends passed arguments array to current args
 107     public void addArguments(String... arguments) {
 108         String newArgs = "";
 109         for (int i = 0 ; i < arguments.length ; i ++) {
 110             newArgs += " " + arguments[i];
 111         }
 112         newArgs += " ";
 113         args = newArgs + args;
 114     }
 115 
 116     public void setPolicyFile(String policyFileName) {
 117         this.policyFileName = policyFileName;
 118     }
 119 
 120     /**
 121      * This method is used for setting VM options on spawned VMs.
 122      * It returns the extra command line options required
 123      * to turn on jcov code coverage analysis.
 124      */
 125     protected static String getCodeCoverageOptions() {
 126         return TestLibrary.getExtraProperty("jcov.options","");
 127     }
 128 
 129     public boolean outputContains(String str) {
 130         return outputStream.ba.toString().indexOf(str) != -1
 131                 || errorStream.ba.toString().indexOf(str) != -1;
 132     }
 133 
 134     private String outputString() {
 135         return outputStream.ba.toString();
 136     }
 137 
 138     private String errorString() {
 139         return errorStream.ba.toString();
 140     }
 141 
 142     /**
 143      * Exec the VM as specified in this object's constructor.
 144      */
 145     public void start() throws IOException {
 146 
 147         if (vm != null)
 148             throw new IllegalStateException("JavaVM already started");
 149 
 150         /*
 151          * If specified, add option for policy file
 152          */
 153         if (policyFileName != null) {
 154             String option = "-Djava.security.policy=" + policyFileName;
 155             addOptions(new String[] { option });
 156         }
 157 
 158         addOptions(new String[] {
 159             getCodeCoverageOptions(),
 160             TestParams.testJavaOpts,
 161             TestParams.testVmOpts
 162         });
 163 
 164         StringTokenizer optionsTokenizer = new StringTokenizer(options);
 165         StringTokenizer argsTokenizer = new StringTokenizer(args);
 166         int optionsCount = optionsTokenizer.countTokens();
 167         int argsCount = argsTokenizer.countTokens();
 168 
 169         String javaCommand[] = new String[optionsCount + argsCount + 2];
 170         int count = 0;
 171 
 172         javaCommand[count++] = JavaVM.javaProgram;
 173         while (optionsTokenizer.hasMoreTokens()) {
 174             javaCommand[count++] = optionsTokenizer.nextToken();
 175         }
 176         javaCommand[count++] = classname;
 177         while (argsTokenizer.hasMoreTokens()) {
 178             javaCommand[count++] = argsTokenizer.nextToken();
 179         }
 180 
 181         mesg("command = " + Arrays.asList(javaCommand).toString());
 182 
 183         vm = Runtime.getRuntime().exec(javaCommand);
 184 
 185         /* output from the execed process may optionally be captured. */
 186         outPipe = StreamPipe.plugTogether(vm.getInputStream(), this.outputStream);
 187         errPipe = StreamPipe.plugTogether(vm.getErrorStream(), this.errorStream);
 188     }
 189 
 190     public void destroy() {
 191         if (vm != null) {
 192             vm.destroy();
 193         }
 194         vm = null;
 195     }
 196 
 197     /**
 198      * Destroys the VM, waits for it to terminate, and returns
 199      * its exit status.
 200      *
 201      * @throws IllegalStateException if the VM has already been destroyed
 202      * @throws InterruptedException if the caller is interrupted while waiting
 203      */
 204     public int terminate() throws InterruptedException {
 205         if (vm == null) {
 206             throw new IllegalStateException("JavaVM already destroyed");
 207         }
 208 
 209         vm.destroy();
 210         int status = waitFor();
 211         vm = null;
 212         return status;
 213     }
 214 
 215 
 216     /**
 217      * Waits for the subprocess to exit, joins the pipe threads to ensure that
 218      * all output is collected, and returns its exit status.
 219      */
 220     public int waitFor() throws InterruptedException {
 221         if (vm == null)
 222             throw new IllegalStateException("can't wait for JavaVM that isn't running");
 223 
 224         int status = vm.waitFor();
 225         outPipe.join();
 226         errPipe.join();
 227         return status;
 228     }
 229 
 230     /**
 231      * Causes the current thread to wait the vm process to exit, if necessary,
 232      * wait until the vm process has terminated, or the specified waiting time
 233      * elapses. Release allocated input/output after vm process has terminated.
 234      * @param timeout the maximum milliseconds to wait.
 235      * @return exit value for vm process.
 236      * @throws InterruptedException if the current thread is interrupted
 237      *         while waiting.
 238      * @throws TimeoutException if subprocess does not end after timeout
 239      *         milliseconds passed
 240      */
 241     public int waitFor(long timeout)
 242             throws InterruptedException, TimeoutException {
 243         if (vm == null)
 244             throw new IllegalStateException("can't wait for JavaVM that isn't running");
 245         long deadline = TestLibrary.computeDeadline(System.currentTimeMillis(), timeout);
 246 
 247         while (true) {
 248             try {
 249                 int status = vm.exitValue();
 250                 outPipe.join();
 251                 errPipe.join();
 252                 return status;
 253             } catch (IllegalThreadStateException ignore) { }
 254 
 255             if (System.currentTimeMillis() > deadline)
 256                 throw new TimeoutException();
 257 
 258             Thread.sleep(POLLTIME_MS);
 259         }
 260     }
 261 
 262     /**
 263      * Starts the subprocess, waits for it to exit, and returns its exit status.
 264      */
 265     public int execute() throws IOException, InterruptedException {
 266         start();
 267         return waitFor();
 268     }
 269 }