1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *
15 *  See the License for the specific language governing permissions and
16 *  limitations under the License.
17 */
18
19/**
20 * @author Vitaly A. Provodin
21 */
22
23package org.apache.harmony.jpda.tests.jdwp.share;
24
25import java.io.IOException;
26import java.util.Vector;
27
28import org.apache.harmony.jpda.tests.framework.LogWriter;
29import org.apache.harmony.jpda.tests.framework.StreamRedirector;
30import org.apache.harmony.jpda.tests.framework.TestErrorException;
31import org.apache.harmony.jpda.tests.framework.jdwp.JDWPDebuggeeWrapper;
32import org.apache.harmony.jpda.tests.share.JPDATestOptions;
33
34/**
35 * This class provides basic DebuggeeWrapper implementation based on JUnit framework,
36 * which can launch and control debuggee process.
37 */
38public class JDWPUnitDebuggeeProcessWrapper extends JDWPDebuggeeWrapper {
39
40    /**
41     * Target VM debuggee process.
42     */
43    public Process process;
44
45    protected StreamRedirector errRedir;
46    protected StreamRedirector outRedir;
47
48    /**
49     * The expected exit code for the debuggee process.
50     */
51    private int expectedExitCode = 0;
52
53    /**
54     * Creates new instance with given data.
55     *
56     * @param settings
57     *            test run options
58     * @param logWriter
59     *            where to print log messages
60     */
61    public JDWPUnitDebuggeeProcessWrapper(JPDATestOptions settings, LogWriter logWriter) {
62        super(settings, logWriter);
63    }
64
65    /**
66     * Sets the expected exit code. This is meant to be used by tests that will request target
67     * VM termination with VirtualMachine.Exit command.
68     */
69    public void setExpectedExitCode(int expectedExitCode) {
70        this.expectedExitCode = expectedExitCode;
71    }
72
73    /**
74     * Launches process and redirects output.
75     */
76    public void launchProcessAndRedirectors(String cmdLine) throws IOException {
77        logWriter.println("Launch process: " + cmdLine);
78        process = launchProcess(cmdLine);
79        logWriter.println("Launched process");
80        if (process != null) {
81            logWriter.println("Start redirectors");
82            errRedir = new StreamRedirector(process.getErrorStream(), logWriter, "STDERR");
83            errRedir.setDaemon(true);
84            errRedir.start();
85            outRedir = new StreamRedirector(process.getInputStream(), logWriter, "STDOUT");
86            outRedir.setDaemon(true);
87            outRedir.start();
88            logWriter.println("Started redirectors");
89        }
90    }
91
92    /**
93     * Waits for process to exit and closes output redirectors
94     */
95    public void finishProcessAndRedirectors() {
96        if (process != null) {
97            try {
98                logWriter.println("Waiting for process exit");
99                WaitForProcessExit(process);
100                logWriter.println("Finished process");
101            } catch (IOException e) {
102                throw new TestErrorException("IOException in waiting for process exit: ", e);
103            }
104
105            logWriter.println("Waiting for redirectors finish");
106            if (outRedir != null) {
107                outRedir.exit();
108                try {
109                    outRedir.join(settings.getTimeout());
110                } catch (InterruptedException e) {
111                    logWriter.println("InterruptedException in stopping outRedirector: " + e);
112                }
113                if (outRedir.isAlive()) {
114                    logWriter.println("WARNING: redirector not stopped: " + outRedir.getName());
115                }
116            }
117            if (errRedir != null) {
118                errRedir.exit();
119                try {
120                    errRedir.join(settings.getTimeout());
121                } catch (InterruptedException e) {
122                    logWriter.println("InterruptedException in stopping errRedirector: " + e);
123                }
124                if (errRedir.isAlive()) {
125                    logWriter.println("WARNING: redirector not stopped: " + errRedir.getName());
126                }
127            }
128            logWriter.println("Finished redirectors");
129        }
130    }
131
132    /**
133     * Launches process with given command line.
134     *
135     * @param cmdLine
136     *            command line
137     * @return associated Process object or null if not available
138     * @throws IOException
139     *             if error occurred in launching process
140     */
141    protected Process launchProcess(String cmdLine) throws IOException {
142
143    	// Runtime.exec(String) does not preserve quoted arguments
144    	// process = Runtime.getRuntime().exec(cmdLine);
145
146    	String args[] = splitCommandLine(cmdLine);
147    	process = Runtime.getRuntime().exec(args);
148        return process;
149    }
150
151    /**
152     * Splits command line into arguments preserving spaces in quoted arguments
153     * either with single and double quotes (not prefixed by '\').
154     *
155     * @param cmd
156     *            command line
157     * @return associated Process object or null if not available
158     */
159/*
160    public String[] splitCommandLine(String cmd) {
161
162        // allocate array for parsed arguments
163        int max_argc = 250;
164        Vector argv = new Vector();
165
166        // parse command line
167        int len = cmd.length();
168        if (len > 0) {
169            for (int arg = 0; arg < len;) {
170                // skip initial spaces
171                while (Character.isWhitespace(cmd.charAt(arg))) arg++;
172                // parse non-spaced or quoted argument
173                for (int p = arg; ; p++) {
174                    // check for ending separator
175                    if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
176                    	if (p > len) p = len;
177                    	String val = cmd.substring(arg, p);
178                        argv.add(val);
179                        arg = p + 1;
180                        break;
181                    }
182
183                    // check for starting quote
184                    if (cmd.charAt(p) == '\"') {
185                         char quote = cmd.charAt(p++);
186                         // skip all chars until terminating quote or end of line
187                         for (; p < len; p++) {
188                             // check for terminating quote
189                             if (cmd.charAt(p) == quote)
190                            	 break;
191                             // skip escaped quote
192                             if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
193                            	 p++;
194                         }
195                     }
196
197                    // skip escaped quote
198                    if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == '\"') {
199                    	p++;
200                    }
201                }
202            }
203        }
204
205    	logWriter.println("Splitted command line: " + argv);
206        int size = argv.size();
207        String args[] = new String[size];
208        return (String[])argv.toArray(args);
209	}
210*/
211    public String[] splitCommandLine(String cmd) {
212
213        int len = cmd.length();
214        char chars[] = new char[len];
215        Vector<String> argv = new Vector<String>();
216
217        if (len > 0) {
218            for (int arg = 0; arg < len;) {
219                // skip initial spaces
220                while (Character.isWhitespace(cmd.charAt(arg))) arg++;
221                // parse non-spaced or quoted argument
222                for (int p = arg, i = 0; ; p++) {
223                    // check for starting quote
224                    if (p < len && (cmd.charAt(p) == '\"' || cmd.charAt(p) == '\'')) {
225                         char quote = cmd.charAt(p++);
226                         // copy all chars until terminating quote or end of line
227                         for (; p < len; p++) {
228                             // check for terminating quote
229                             if (cmd.charAt(p) == quote) {
230                            	 p++;
231                            	 break;
232                             }
233                             // preserve escaped quote
234                             if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
235                            	 p++;
236                             chars[i++] = cmd.charAt(p);
237                         }
238                     }
239
240                    // check for ending separator
241                    if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
242                    	String val = new String(chars, 0, i);
243                        argv.add(val);
244                        arg = p + 1;
245                        break;
246                    }
247
248                    // preserve escaped quote
249                    if (cmd.charAt(p) == '\\' && (p+1) < len
250                    		&& (cmd.charAt(p+1) == '\"' || cmd.charAt(p+1) == '\'')) {
251                    	p++;
252                    }
253
254                    // copy current char
255                    chars[i++] = cmd.charAt(p);
256                }
257            }
258        }
259
260    	logWriter.println("Splitted command line: " + argv);
261        int size = argv.size();
262        String args[] = new String[size];
263        return argv.toArray(args);
264	}
265
266    /**
267     * Waits for launched process to exit.
268     *
269     * @param process
270     *            associated Process object or null if not available
271     * @throws IOException
272     *             if any exception occurs in waiting
273     */
274    protected void WaitForProcessExit(Process process) throws IOException {
275        ProcessWaiter thrd = new ProcessWaiter();
276        thrd.setDaemon(true);
277        thrd.start();
278        try {
279            thrd.join(settings.getTimeout());
280        } catch (InterruptedException e) {
281            throw new TestErrorException(e);
282        }
283
284        if (thrd.isAlive()) {
285            // ProcessWaiter thread is still running (after we wait until a timeout) but is
286            // waiting for the debuggee process to exit. We send an interrupt request to
287            // that thread so it receives an InterrupedException and terminates.
288            thrd.interrupt();
289        }
290
291        try {
292            int exitCode = process.exitValue();
293            logWriter.println("Finished debuggee with exit code: " + exitCode);
294            if (exitCode != expectedExitCode) {
295                throw new TestErrorException("Debuggee exited with code " + exitCode +
296                        " but we expected code " + expectedExitCode);
297            }
298        } catch (IllegalThreadStateException e) {
299            logWriter.printError("Terminate debuggee process");
300            throw new TestErrorException("Debuggee process did not finish during timeout", e);
301        } finally {
302            // dispose any resources of the process
303            process.destroy();
304        }
305    }
306
307    /**
308     * Separate thread for waiting for process exit for specified timeout.
309     */
310    class ProcessWaiter extends Thread {
311        @Override
312        public void run() {
313            try {
314                process.waitFor();
315            } catch (InterruptedException e) {
316                logWriter.println("Ignoring exception in ProcessWaiter thread interrupted: " + e);
317            }
318        }
319    }
320
321    @Override
322    public void start() {
323    }
324
325    @Override
326    public void stop() {
327    }
328}
329