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