1 /*
2  * Copyright (c) 1998, 2014, 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 
31 /**
32  * RMI regression test utility class that uses Runtime.exec to spawn a
33  * java process that will run a named java class.
34  */
35 public class JavaVM {
36 
37     public static final long POLLTIME_MS = 100L;
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 
mesg(Object mesg)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 
JavaVM(String classname)65     public JavaVM(String classname) {
66         this.classname = classname;
67     }
JavaVM(String classname, String options, String args)68     public JavaVM(String classname,
69                   String options, String args) {
70         this.classname = classname;
71         this.options = options;
72         this.args = args;
73     }
74 
JavaVM(String classname, String options, String args, OutputStream out, OutputStream err)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
addOptions(String[] opts)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
addArguments(String[] arguments)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 
setPolicyFile(String policyFileName)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      */
getCodeCoverageOptions()112     protected static String getCodeCoverageOptions() {
113         return TestLibrary.getExtraProperty("jcov.options","");
114     }
115 
start(Runnable runnable)116     public void start(Runnable runnable) throws IOException {
117         if (runnable == null) {
118             throw new NullPointerException("Runnable cannot be null.");
119         }
120 
121         start();
122         new JavaVMCallbackHandler(runnable).start();
123     }
124 
125     /**
126      * Exec the VM as specified in this object's constructor.
127      */
start()128     public void start() throws IOException {
129 
130         if (vm != null)
131             throw new IllegalStateException("JavaVM already started");
132 
133         /*
134          * If specified, add option for policy file
135          */
136         if (policyFileName != null) {
137             String option = "-Djava.security.policy=" + policyFileName;
138             addOptions(new String[] { option });
139         }
140 
141         addOptions(new String[] {
142             getCodeCoverageOptions(),
143             TestParams.testJavaOpts,
144             TestParams.testVmOpts
145         });
146 
147         StringTokenizer optionsTokenizer = new StringTokenizer(options);
148         StringTokenizer argsTokenizer = new StringTokenizer(args);
149         int optionsCount = optionsTokenizer.countTokens();
150         int argsCount = argsTokenizer.countTokens();
151 
152         String javaCommand[] = new String[optionsCount + argsCount + 2];
153         int count = 0;
154 
155         javaCommand[count++] = JavaVM.javaProgram;
156         while (optionsTokenizer.hasMoreTokens()) {
157             javaCommand[count++] = optionsTokenizer.nextToken();
158         }
159         javaCommand[count++] = classname;
160         while (argsTokenizer.hasMoreTokens()) {
161             javaCommand[count++] = argsTokenizer.nextToken();
162         }
163 
164         mesg("command = " + Arrays.asList(javaCommand).toString());
165 
166         vm = Runtime.getRuntime().exec(javaCommand);
167 
168         /* output from the execed process may optionally be captured. */
169         outPipe = StreamPipe.plugTogether(vm.getInputStream(), this.outputStream);
170         errPipe = StreamPipe.plugTogether(vm.getErrorStream(), this.errorStream);
171     }
172 
destroy()173     public void destroy() {
174         if (vm != null) {
175             vm.destroy();
176         }
177         vm = null;
178     }
179 
180     /**
181      * Waits for the subprocess to exit, joins the pipe threads to ensure that
182      * all output is collected, and returns its exit status.
183      */
waitFor()184     public int waitFor() throws InterruptedException {
185         if (vm == null)
186             throw new IllegalStateException("can't wait for JavaVM that hasn't started");
187 
188         int status = vm.waitFor();
189         outPipe.join();
190         errPipe.join();
191         return status;
192     }
193 
194     /**
195      * Causes the current thread to wait the vm process to exit, if necessary,
196      * wait until the vm process has terminated, or the specified waiting time
197      * elapses. Release allocated input/output after vm process has terminated.
198      * @param timeout the maximum milliseconds to wait.
199      * @return exit value for vm process.
200      * @throws InterruptedException if the current thread is interrupted
201      *         while waiting.
202      * @throws TimeoutException if subprocess does not end after timeout
203      *         milliseconds passed
204      */
waitFor(long timeout)205     public int waitFor(long timeout)
206             throws InterruptedException, TimeoutException {
207         if (vm == null)
208             throw new IllegalStateException("can't wait for JavaVM that isn't running");
209         long deadline = System.currentTimeMillis() + timeout;
210 
211         while (true) {
212             try {
213                 int status = vm.exitValue();
214                 outPipe.join();
215                 errPipe.join();
216                 return status;
217             } catch (IllegalThreadStateException ignore) { }
218 
219             if (System.currentTimeMillis() > deadline)
220                 throw new TimeoutException();
221 
222             Thread.sleep(POLLTIME_MS);
223         }
224     }
225 
226     /**
227      * Starts the subprocess, waits for it to exit, and returns its exit status.
228      */
execute()229     public int execute() throws IOException, InterruptedException {
230         start();
231         return waitFor();
232     }
233 
234     /**
235      * Handles calling the callback.
236      */
237     private class JavaVMCallbackHandler extends Thread {
238         Runnable runnable;
239 
JavaVMCallbackHandler(Runnable runnable)240         JavaVMCallbackHandler(Runnable runnable) {
241             this.runnable = runnable;
242         }
243 
244 
245         /**
246          * Wait for the Process to terminate and notify the callback.
247          */
248         @Override
run()249         public void run() {
250             if (vm != null) {
251                 try {
252                     vm.waitFor();
253                 } catch(InterruptedException ie) {
254                     // Restore the interrupted status
255                     Thread.currentThread().interrupt();
256                 }
257             }
258 
259             if (runnable != null) {
260                 runnable.run();
261             }
262         }
263     }
264 }
265