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 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 */ splitCommandLine(String cmd)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 */ WaitForProcessExit(Process process)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 run()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 start()322 public void start() { 323 } 324 325 @Override stop()326 public void stop() { 327 } 328 } 329