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 
23 package org.apache.harmony.jpda.tests.jdwp.share;
24 
25 import java.io.IOException;
26 import java.util.Vector;
27 
28 import org.apache.harmony.jpda.tests.framework.LogWriter;
29 import org.apache.harmony.jpda.tests.framework.StreamRedirector;
30 import org.apache.harmony.jpda.tests.framework.TestErrorException;
31 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPDebuggeeWrapper;
32 import 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  */
38 public 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      */
JDWPUnitDebuggeeProcessWrapper(JPDATestOptions settings, LogWriter logWriter)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      */
setExpectedExitCode(int expectedExitCode)69     public void setExpectedExitCode(int expectedExitCode) {
70         this.expectedExitCode = expectedExitCode;
71     }
72 
73     /**
74      * Launches process and redirects output.
75      */
launchProcessAndRedirectors(String cmdLine)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      */
finishProcessAndRedirectors()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      */
launchProcess(String cmdLine)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 cmdLine
156      *            command line
157      * @return associated Process object or null if not available
158      * @throws IOException
159      *             if error occurred in launching process
160      */
161 /*
162     public String[] splitCommandLine(String cmd) {
163 
164         // allocate array for parsed arguments
165         int max_argc = 250;
166         Vector argv = new Vector();
167 
168         // parse command line
169         int len = cmd.length();
170         if (len > 0) {
171             for (int arg = 0; arg < len;) {
172                 // skip initial spaces
173                 while (Character.isWhitespace(cmd.charAt(arg))) arg++;
174                 // parse non-spaced or quoted argument
175                 for (int p = arg; ; p++) {
176                     // check for ending separator
177                     if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
178                     	if (p > len) p = len;
179                     	String val = cmd.substring(arg, p);
180                         argv.add(val);
181                         arg = p + 1;
182                         break;
183                     }
184 
185                     // check for starting quote
186                     if (cmd.charAt(p) == '\"') {
187                          char quote = cmd.charAt(p++);
188                          // skip all chars until terminating quote or end of line
189                          for (; p < len; p++) {
190                              // check for terminating quote
191                              if (cmd.charAt(p) == quote)
192                             	 break;
193                              // skip escaped quote
194                              if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
195                             	 p++;
196                          }
197                      }
198 
199                     // skip escaped quote
200                     if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == '\"') {
201                     	p++;
202                     }
203                 }
204             }
205         }
206 
207     	logWriter.println("Splitted command line: " + argv);
208         int size = argv.size();
209         String args[] = new String[size];
210         return (String[])argv.toArray(args);
211 	}
212 */
splitCommandLine(String cmd)213     public String[] splitCommandLine(String cmd) {
214 
215         int len = cmd.length();
216         char chars[] = new char[len];
217         Vector<String> argv = new Vector<String>();
218 
219         if (len > 0) {
220             for (int arg = 0; arg < len;) {
221                 // skip initial spaces
222                 while (Character.isWhitespace(cmd.charAt(arg))) arg++;
223                 // parse non-spaced or quoted argument
224                 for (int p = arg, i = 0; ; p++) {
225                     // check for starting quote
226                     if (p < len && (cmd.charAt(p) == '\"' || cmd.charAt(p) == '\'')) {
227                          char quote = cmd.charAt(p++);
228                          // copy all chars until terminating quote or end of line
229                          for (; p < len; p++) {
230                              // check for terminating quote
231                              if (cmd.charAt(p) == quote) {
232                             	 p++;
233                             	 break;
234                              }
235                              // preserve escaped quote
236                              if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
237                             	 p++;
238                              chars[i++] = cmd.charAt(p);
239                          }
240                      }
241 
242                     // check for ending separator
243                     if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
244                     	String val = new String(chars, 0, i);
245                         argv.add(val);
246                         arg = p + 1;
247                         break;
248                     }
249 
250                     // preserve escaped quote
251                     if (cmd.charAt(p) == '\\' && (p+1) < len
252                     		&& (cmd.charAt(p+1) == '\"' || cmd.charAt(p+1) == '\'')) {
253                     	p++;
254                     }
255 
256                     // copy current char
257                     chars[i++] = cmd.charAt(p);
258                 }
259             }
260         }
261 
262     	logWriter.println("Splitted command line: " + argv);
263         int size = argv.size();
264         String args[] = new String[size];
265         return (String[])argv.toArray((String[])args);
266 	}
267 
268     /**
269      * Waits for launched process to exit.
270      *
271      * @param process
272      *            associated Process object or null if not available
273      * @throws IOException
274      *             if any exception occurs in waiting
275      */
WaitForProcessExit(Process process)276     protected void WaitForProcessExit(Process process) throws IOException {
277         ProcessWaiter thrd = new ProcessWaiter();
278         thrd.setDaemon(true);
279         thrd.start();
280         try {
281             thrd.join(settings.getTimeout());
282         } catch (InterruptedException e) {
283             throw new TestErrorException(e);
284         }
285 
286         if (thrd.isAlive()) {
287             // ProcessWaiter thread is still running (after we wait until a timeout) but is
288             // waiting for the debuggee process to exit. We send an interrupt request to
289             // that thread so it receives an InterrupedException and terminates.
290             thrd.interrupt();
291         }
292 
293         try {
294             int exitCode = process.exitValue();
295             logWriter.println("Finished debuggee with exit code: " + exitCode);
296             if (exitCode != expectedExitCode) {
297                 throw new TestErrorException("Debuggee exited with code " + exitCode +
298                         " but we expected code " + expectedExitCode);
299             }
300         } catch (IllegalThreadStateException e) {
301             logWriter.printError("Terminate debuggee process");
302             throw new TestErrorException("Debuggee process did not finish during timeout", e);
303         } finally {
304             // dispose any resources of the process
305             process.destroy();
306         }
307     }
308 
309     /**
310      * Separate thread for waiting for process exit for specified timeout.
311      */
312     class ProcessWaiter extends Thread {
run()313         public void run() {
314             try {
315                 process.waitFor();
316             } catch (InterruptedException e) {
317                 logWriter.println("Ignoring exception in ProcessWaiter thread interrupted: " + e);
318             }
319         }
320     }
321 
start()322     public void start() {
323     }
324 
stop()325     public void stop() {
326     }
327 }
328