Mercurial > hg > release > icedtea6-1.2
view test/jtreg/com/sun/javatest/lib/ProcessCommand.java @ 866:f42fc832db86
Add missing (ignored) lib/ files. Add test report and classes dirs to ignore.
2008-05-19 Mark Wielaard <mark@klomp.org>
* test/jtreg/com/sun/javatest/lib/*.java: Added.
* .hgignore: Add test/hotspot, test/jdk, test/langtools and
test/jtreg/classes
author | Mark Wielaard <mark@klomp.org> |
---|---|
date | Mon, 19 May 2008 10:33:03 +0200 |
parents | |
children |
line wrap: on
line source
/* * $Id$ * * Copyright 1996-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.javatest.lib; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.util.Hashtable; import com.sun.javatest.Command; import com.sun.javatest.Status; import com.sun.javatest.util.StringArray; /** * A Command to execute an arbitrary OS command. **/ public class ProcessCommand extends Command { /** * A stand-alone entry point for this command. An instance of this * command is created, and its <code>run</code> method invoked, * passing in the command line args and <code>System.out</code> and * <code>System.err</code> as the two streams. * @param args command line arguments for this command. * @see #run */ public static void main(String[] args) { PrintWriter log = new PrintWriter(new OutputStreamWriter(System.err)); PrintWriter ref = new PrintWriter(new OutputStreamWriter(System.out)); Status s; try { Command cmd = new ProcessCommand(); s = cmd.run(args, log, ref); } finally { log.flush(); ref.flush(); } s.exit(); } /** * Set a status to be returned for a specific exit code, overwriting any * previous setting for this exit code. If the default status has not yet * been initialized, it is set to Status.error("unrecognized exit code"). * * @param exitCode The process exit code for which to assign a status. * @param status The status to associate with the exit code. */ public void setStatusForExit(int exitCode, Status status) { if (statusTable == null) { statusTable = new Hashtable(); if (defaultStatus == null) defaultStatus = Status.error("unrecognized exit code"); } statusTable.put(new Integer(exitCode), status); } /** * Set the default status to be returned for all exit codes. * This will not affect any values for specific exit codes that * may have been set with setStatusForExit. If this method is * not called, the default value will be Status.failed (for * backwards compatability) unless setStatusForExit has been * called, which sets the default value to Status.error. * * @param status The default status to use when a specific status * has not been set for a particular process exit code. */ public void setDefaultStatus(Status status) { if (statusTable == null) statusTable = new Hashtable(); defaultStatus = status; } /** * Set the directory in which to execute the process. * Use null to indicate the default directory. * @param dir the directory in which to execute the process. * @see #getExecDir */ public void setExecDir(File dir) { execDir = dir; } /** * Get the directory in which to execute the process, * or null if none set. * @return the directory in which to execute the process. * @see #setExecDir */ public File getExecDir() { return execDir; } /** * Internal routine to assist argument decoding prior to calling * setStatusForExit or setDefaultStatus */ private void setStatus(String exitSpec, Status status) { // for now, we just support "default" and <integer> // in principle we could support ranges and lists too if (exitSpec.equals("default")) setDefaultStatus(status); else setStatusForExit(Integer.parseInt(exitSpec), status); } /** * Run the given command. * @param args An array of strings composed of * <em>command-options</em>, * <em>environment-variables</em>, * <em>command</em>, * <em>command-arguments</em>. * <br> * * The <em>command-options</em> are an optional * set of options, each beginning with `-', to be * used by this object. * The options are * <dt> * <dt>-v * <dd> verbose mode * <dt>-pass|-fail|-error <i>exit-code</i> <i>string</i> * <dd> set the status to be returned for the given * exit code to one of * Status.passed/Status.failed/Status.error. * <i>exit-code</i> can be either an integer or "default". * <i>string</i> the message string provided in the * status object. * <dt>-execDir <i>execDir</i> * <dd> set the directory in which to execute the command. * </dl> * <br> * * The <em>environment-variables</em> are an * optional list of environment variable to be supplied * to the command. They should be in the form * <em>NAME</em><code>=</code><em>VALUE</em>. * <br> * * The <em>command</em> identifies the command to * be executed. This name will be platform specific. * <br> * * The <em>command-arguments</em> are an optional * list of strings to be passed to the command to be * executed. * @param log A stream for logging output. * @param ref A stream for reference output, if the test requires it. * If not, it can be used as an additional logging stream. * * @return The result of the method is obtained by calling * <code>getStatus</code> after the command completes. * The default behaviour is to use the explicit or default * status given in the arguments, or via the API. If none * have been set up, then the following values are used: * <code>Status.passed("exit code 0")</code> * if the command exited with exit status 0, or * <code>Status.failed("exit code " + exitCode)</code> * otherwise. <code>getStatus</code> may be overridden * to provide different behavior. * * @see #getStatus * **/ public Status run(String[] args, PrintWriter log, PrintWriter ref) { // analyse options int i = 0; for (; i < args.length && args[i].startsWith("-"); i++) { if (args[i].equals("-v")) verbose = true; else if (args[i].equals("-execDir") && i+1 < args.length) { execDir = new File(args[++i]); } else if (args[i].equals("-pass") && i+2 < args.length) { setStatus(args[++i], Status.passed(args[++i])); } else if (args[i].equals("-fail") && i+2 < args.length) { setStatus(args[++i], Status.failed(args[++i])); } else if (args[i].equals("-error") && i+2 < args.length) { setStatus(args[++i], Status.error(args[++i])); } else if (args[i].equals("-end")) { // -end is supported for the improbable event someone wants an // env var or command beginning with - i++; // because the for-loop won't get a chance to do it break; } else return Status.error("Unrecognized option: " + args[i]); } // get environment variables for command int cmdEnvStart = i; while (i < args.length && (args[i].indexOf('=') != -1)) i++; String[] cmdEnv = new String[i - cmdEnvStart]; System.arraycopy(args, cmdEnvStart, cmdEnv, 0, cmdEnv.length); // get command name if (i == args.length) return Status.error("no command specified for " + getClass().getName()); String[] cmd = new String[args.length - i]; System.arraycopy(args, i, cmd, 0, cmd.length); return exec(cmd, cmdEnv, log, ref); } /** * Exceute a command, bypassing the standard argument decoding of 'run'. * @param cmd The command to be executed * @param cmdEnv The environment to be passed to the command * @param log A stream for logging output. * @param ref A stream for reference output, if the test requires it. * If not, it can be used as an additional logging stream. * @return The result of the method is obtained by calling * <code>getStatus</code> after the command completes. * @see #run * @see #getStatus */ public Status exec(String[] cmd, String[] cmdEnv, PrintWriter log, PrintWriter ref) { Process p = null; Status s = null; try { // The following is a workaround for a JDK problem ... if the cmdEnv // is empty, JDK assumes this means to inherit the parent environment. // (There is a separate call which more reasonably means that.) // So, to prevent the parent process' environment being inherited // we set the command environment to a dummy entry which will hopefully // not cause any problems for either the Runtime machinery or the // child process. if (cmdEnv != null && cmdEnv.length == 0) { String[] envWithDummyEntry = {/*empty*/"="/*empty*/}; cmdEnv=envWithDummyEntry; } if (verbose) { log.println("Command is: " + StringArray.join(cmd)); if (cmdEnv == null) { log.println("Command environment is inherited from parent process"); } else if (cmdEnv.length == 0) { log.println("Command environment is empty"); } else { log.println("Command environment is:"); for (int i = 0; i < cmdEnv.length; i++) log.println(cmdEnv[i]); } if (execDir != null) log.println("Execution directory is " + execDir); } Runtime r = Runtime.getRuntime(); p = (execDir == null ? r.exec(cmd, cmdEnv) : r.exec(cmd, cmdEnv, execDir)); Reader in = new InputStreamReader(p.getInputStream()); // output stream from process StreamCopier refConnector = new StreamCopier(in, ref); refConnector.start(); Reader err = new InputStreamReader(p.getErrorStream()); StreamCopier logConnector = new StreamCopier(err, log); logConnector.start(); OutputStream out = p.getOutputStream(); // input stream to process if (out != null) out.close(); // wait for the stream copiers to complete (which may be interrupted by the // timeout thread refConnector.waitUntilDone(); logConnector.waitUntilDone(); // wait for the process to complete; // WARNING: in JDK1.0.2 this does not appear to be interruptible, which is // why we waited for the stream copiers to complete first ... because they are // interruptible. int exitCode = p.waitFor(); //if (verbose > 0) // log.report("command exited, exit=" + exitCode); in.close(); err.close(); return getStatus(exitCode, logConnector.exitStatus()); } catch (InterruptedException e) { if (p != null) p.destroy(); String msg = "Program `" + cmd[0] + "' interrupted! (timed out?)"; s = (useFailedOnException ? Status.failed(msg) : Status.error(msg)); } catch (IOException e) { String msg = "Error invoking program `" + cmd[0] + "': " + e; s = (useFailedOnException ? Status.failed(msg) : Status.error(msg)); } return s; } /** * Generate a status for the command, based upon the command's exit code * and a status that may have been passed from the command by using * <code>status.exit()</code>. * * @param exitCode The exit code from the command that was executed. * @param logStatus If the command that was executed was a test program * and exited by calling <code>status.exit()</code>, * then logStatus will be set to `status'. Otherwise, * it will be null. The value of the status is passed * from the command by writing it as the last line to * stdout before exiting the process. If it is not * received as the last line, the value will be lost. * @return Unless overridden, the default is * <code>Status.passed("exit code 0")</code> * if the command exited with exit code 0, or * <code>Status.failed("exit code " + exitCode)</code> * otherwise. **/ protected Status getStatus(int exitCode, Status logStatus) { if (logStatus != null) return logStatus; else if (statusTable != null) { Status s = (Status)(statusTable.get(new Integer(exitCode))); return (s == null ? defaultStatus.augment("exit code: " + exitCode) : s); } else if (exitCode == 0) return Status.passed("exit code 0"); else return Status.failed("exit code " + exitCode); } private boolean verbose; /** * A thread to copy an input stream to an output stream */ class StreamCopier extends Thread { /** * Create one. * @param from the stream to copy from * @param out the log to copy to */ StreamCopier(Reader from, PrintWriter to) { super(Thread.currentThread().getName() + "_StreamCopier_" + (serial++)); in = new BufferedReader(from); out = to; lastStatusLine = null; } /** * Set the thread going. */ public void run() { //System.out.println("Copying stream"); try { String line; while ((line = in.readLine()) != null) { out.println(line); // take care lastLine doesn't get set to null at EOF // and ignore trailing newlines if (line.startsWith(Status.EXIT_PREFIX)) lastStatusLine = line; } } catch (IOException e) { } //System.out.println("Stream copied"); synchronized (this) { done = true; notifyAll(); } } public synchronized boolean isDone() { return done; } /** * Blocks until the copy is complete, or until the thread is interrupted */ public synchronized void waitUntilDone() throws InterruptedException { boolean interrupted = false; // poll interrupted flag, while waiting for copy to complete while (!(interrupted = Thread.interrupted()) && !done) wait(1000); //if (interrupted) // System.out.println("TESTSCRIPT DETECTS interrupted() " + Thread.currentThread().getName()); //else // System.out.println("TESTSCRIPT waitUntilDone OK " + Thread.currentThread().getName()); // workaround; if the exception hasn't been thrown already, do it now if (interrupted) { //System.out.println("Stream copier: throwing InterruptedException"); throw new InterruptedException(); } } /** * Return the status information from the child process if it returned * any on the log stream, otherwise return null. */ public Status exitStatus() { if (lastStatusLine == null) return null; else return Status.parse(lastStatusLine.substring(Status.EXIT_PREFIX.length())); } private BufferedReader in; private PrintWriter out; private String lastStatusLine; private boolean done; } private static boolean useFailedOnException = Boolean.getBoolean("javatest.processCommand.useFailedOnException"); private static int serial; private Hashtable statusTable; private Status defaultStatus; private File execDir; }