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
24import java.io.BufferedReader;
25import java.io.ByteArrayOutputStream;
26import java.io.DataInputStream;
27import java.io.File;
28import java.io.IOException;
29import java.io.InputStreamReader;
30import java.io.OutputStream;
31import java.util.Arrays;
32import java.util.StringTokenizer;
33import 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 */
39public class JavaVM {
40
41    static class CachedOutputStream extends OutputStream {
42        ByteArrayOutputStream ba;
43        OutputStream os;
44
45        public CachedOutputStream(OutputStream os) {
46            this.os = os;
47            this.ba = new ByteArrayOutputStream();
48        }
49
50        public void write(int b) throws IOException {
51            ba.write(b);
52            os.write(b);
53        }
54
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
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
89    public JavaVM(String classname,
90                  String options, String args) {
91        this.classname = classname;
92        this.options = options;
93        this.args = args;
94    }
95
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
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
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
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     */
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     */
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
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
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
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     */
237    public int exitValue() {
238        return vm.exitValue();
239    }
240
241    /**
242     * Destroy the vm process, and do necessary cleanup.
243     */
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     */
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     */
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     */
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     */
316    public int execute() throws IOException, InterruptedException {
317        start();
318        return waitFor();
319    }
320}
321