# HG changeset patch # User Mark Wielaard # Date 1211185983 -7200 # Node ID f42fc832db863122de04c0920a631767337b2078 # Parent bf4662205a02b75a43d425e7606e4050ad24321a Add missing (ignored) lib/ files. Add test report and classes dirs to ignore. 2008-05-19 Mark Wielaard * test/jtreg/com/sun/javatest/lib/*.java: Added. * .hgignore: Add test/hotspot, test/jdk, test/langtools and test/jtreg/classes diff -r bf4662205a02 -r f42fc832db86 .hgignore --- a/.hgignore Mon May 19 08:16:22 2008 +0200 +++ b/.hgignore Mon May 19 10:33:03 2008 +0200 @@ -22,6 +22,10 @@ platform_zero jvm.cfg ergo.c +test/hotspot +test/jdk +test/langtools +test/jtreg/classes rt/com/sun/jdi/AbsentInformationException.java rt/com/sun/jdi/Accessible.java rt/com/sun/jdi/ArrayReference.java diff -r bf4662205a02 -r f42fc832db86 ChangeLog --- a/ChangeLog Mon May 19 08:16:22 2008 +0200 +++ b/ChangeLog Mon May 19 10:33:03 2008 +0200 @@ -1,3 +1,9 @@ +2008-05-19 Mark Wielaard + + * test/jtreg/com/sun/javatest/lib/*.java: Added. + * .hgignore: Add test/hotspot, test/jdk, test/langtools and + test/jtreg/classes + 2008-05-19 Mark Wielaard * test/jtreg/*: New jtreg and jtharness sources. diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/APIScript.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/APIScript.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,114 @@ +/* + * $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.File; +import java.io.PrintWriter; + +import com.sun.javatest.Script; +import com.sun.javatest.Status; +import com.sun.javatest.TestEnvironment; +import com.sun.javatest.TestDescription; + +/** + * A Script designed to compile/execute a test. + */ +public class APIScript extends Script +{ + /// XXX this code really needs to be re-visited! + + /** + * The method that interprets the tags provided in the test description and + * performs actions accordingly. + * + * @param args Any arguments that the APIScript may use. Currently + * there are none (value ignored). + * @param td The current TestDescription. + * @param env The test environment giving the details of how to run the + * test. + * @return The result of running the script on the given test + * description. + */ + public Status run(String [] args, TestDescription td, TestEnvironment env) { + + PrintWriter trOut = getTestResult().getTestCommentWriter(); + + Status status = decodeArgs(args); + if (status != null) + return status; + + // XXX This isn't everything. We need to make sure that this is a + // XXX reasonable subset of JCKScript. Do we want to handle all options + // XXX available there? How about the keywords? + + // compile + File [] srcs = td.getSourceFiles(); + Status compileStatus; + if (precompileClassDir == null) { + trOut.println("Unconditionally compiling all sources"); + compileStatus = compileTogether(TEST_COMPILE, srcs); + } else { + trOut.println("Compiling sources only if necessary"); + compileStatus = compileIfNecessary(TEST_COMPILE, srcs, precompileClassDir); + } + + if (!compileStatus.isPassed()) + return compileStatus; + + // execute + String executeClass = td.getParameter("executeClass"); + String executeArgs = td.getParameter("executeArgs"); + Status executeStatus = execute(TEST_EXECUTE, executeClass, executeArgs); + + return executeStatus; + } // run() + + //----------private methods------------------------------------------------- + + private Status decodeArgs(String [] args) { + // decode args + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-precompileClassDir") && (i+1 < args.length)) + precompileClassDir = args[++i]; + else + return Status.failed(UNRECOGNIZED_ARG + args[i]); + } + + return null; + } // init() + + //----------member variables------------------------------------------------ + + private static final String TEST_COMPILE = "testCompile"; + private static final String TEST_EXECUTE = "testExecute"; + + // special option to use compileIfNecessary + private String precompileClassDir; + + private static final String + UNRECOGNIZED_ARG = "Unrecognized argument for script: "; +} diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/Deprecated.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/Deprecated.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,52 @@ +/* + * $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.OutputStream; +import java.io.PrintStream; + +/** + * This class should take the hit for all deprecated methods used in this + * package. + */ +class Deprecated +{ + /** + * This method is for use in place of calls to the deprecated constructors + * for PrintStream(). It is necessary to keep using PrintStreams for + * java.lang.System.setOut(), java.lang.System.setErr(), or calls to things + * in sun.tools that have no alternative entry points that do not use + * PrintStreams. + * + * @param out The output stream to which values and objects will be + * printed. + * @return A PrintStream. + */ + static PrintStream createPrintStream(OutputStream out) { + return new PrintStream(out); + } +} diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/ExecStdTestOtherJVMCmd.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/ExecStdTestOtherJVMCmd.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,77 @@ +/* + * $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 com.sun.javatest.Status; + +/** + * This is a modification of ProcessCommand suitable + * for executing standard tests in a separate JVM. When run in a + * separate process, these tests report their exit status by calling + * Status.exit(). + **/ +public class ExecStdTestOtherJVMCmd extends ProcessCommand +{ + + /** + * 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 + * status.exit(). + * + * @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 status.exit(), + * 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 If logStatus is not null, it will + * be used as the result; this will normally correspond + * to the status for which the test called + * status.exit();. + *

If logStatus is null, that means + * that for some reason the test did not successfully + * call status.exit() and the test is + * deemed to have failed. If the exit code is zero, + * a likely possibility is that the test raised an + * exception which caused the JVM to dump the stack + * and exit. In this case, the result is + * Status.failed("exit without status, exception assumed"). + * In other cases, the result is simply + * Status.failed("exit code" + exitCode). + * + **/ + protected Status getStatus(int exitCode, Status logStatus) { + if (logStatus != null) + return logStatus; + else if (exitCode == 0) + return Status.failed("exit without status, exception assumed"); + else + return Status.failed("exit code " + exitCode); + } +} diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/ExecStdTestSameJVMCmd.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/ExecStdTestSameJVMCmd.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,155 @@ +/* + * $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.File; +import java.io.PrintWriter; +import com.sun.javatest.Command; +import com.sun.javatest.Status; +import com.sun.javatest.Test; +import com.sun.javatest.util.DirectoryClassLoader; + + +/** + * ExecStdTestSameJVMCmd executes a standard test (one that implements + * the Test interface) in the same Java Virtual Machine as the caller. + * + * It can use either a private class loader or the system class loader. + * A private class loader will be created if the -loadDir option is given; + * otherwise the system class loader will be used. A private class + * loader minimises the interference between tests, but you may be + * restricted from using private class loaders if you are running the + * harness inside a web browser. + * + *

If the the -repeat option is provided, then the test will be + * run multiple times in the same JVM. Status.error() will be + * returned (and the remainder of the iterations will not be performed) if any + * repetition of the test returns an error, or if the status return type changes + * between iterations. The returned status after each iteration will be + * included in the log. If this option is not given, the test will be run once. + * + * @see com.sun.javatest.lib.ExecStdTestOtherJVMCmd + */ +public class ExecStdTestSameJVMCmd extends Command +{ + /** + * The method that that does the work of the command. + * @param args [-loadDir dir] [-saveProps] executeClass executeArgs + * @param log A stream to which to report messages and errors + * @param ref A stream to which to write reference output + * @return The result of the command + */ + public Status run(String[] args, PrintWriter log, PrintWriter ref) { + int repeat = 1; + String className = null; + String[] executeArgs = { }; + ClassLoader loader = getClassLoader(); + + int i = 0; + + for (; i < args.length && args[i].startsWith("-"); i++) { + if ("-loadDir".equals(args[i]) && i+1 < args.length) { + // -loadDir is optional; if given, a new class loader will be created + // to load the class to execute; if not given, the system class loader + // will be used. + loader = new DirectoryClassLoader(new File(args[++i])); + } else if ("-repeat".equals(args[i]) && i+1 < args.length) { + // -repeat is optional; if given, the test will be run that + // number of times (in the same JVM) + try { + if ((repeat = Integer.parseInt(args[++i])) < 1) + return Status.error("Unexpected number of repetitions: " + repeat); + } + catch (NumberFormatException e) { + return Status.error("Unrecognized number of repetitions: " + repeat); + } + } + } + + // Next must come the executeClass + if (i < args.length) { + className = args[i]; + i++; + } else + return Status.failed("No executeClass specified"); + + // Finally, any optional args + if (i < args.length) { + executeArgs = new String[args.length - i]; + System.arraycopy(args, i, executeArgs, 0, executeArgs.length); + } + + Status status = null; + try { + Class c; + if (loader == null) + c = Class.forName(className); + else + c = loader.loadClass(className); + + Status prevStatus = null; + for (int j = 0; j < repeat; j++) { + if (repeat > 1) + log.println("iteration: " + (j+1)); + + Test t = (Test) (c.newInstance()); + status = t.run(executeArgs, log, ref); + + if (repeat > 1) + log.println(" " + status); + + if ((prevStatus != null) && status.getType() != prevStatus.getType()) + status = Status.error("Return status type changed at repetition: " + (j+1)); + + if (status.isError()) + return status; + else + prevStatus = status; + } + } + catch (ClassCastException e) { + status = Status.failed("Can't load test: required interface not found"); + } + catch (ClassNotFoundException e) { + status = Status.failed("Can't load test: " + e); + } + catch (InstantiationException e) { + status = Status.failed("Can't instantiate test: " + e); + } + catch (IllegalAccessException e) { + status = Status.failed("Illegal access to test: " + e); + } + catch (VerifyError e) { + return Status.failed("Class verification error while trying to load test class `" + className + "': " + e); + } + catch (LinkageError e) { + return Status.failed("Class linking error while trying to load test class `" + className + "': " + e); + } + return status; + } +} + diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/JavaCompileCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/JavaCompileCommand.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,317 @@ +/* + * $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.OutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.InvocationTargetException; +import com.sun.javatest.Command; +import com.sun.javatest.Status; +import com.sun.javatest.util.PathClassLoader; +import com.sun.javatest.util.WriterStream; + +/** + * Invoke a Java compiler via reflection. + * The compiler is assumed to have a constructor and compile method + * matching the following signature: + *

+ * public class COMPILER {
+ *     public COMPILER(java.io.OutputStream out, String compilerName);
+ *     public boolean compile(String[] args);
+ * }
+ *
+ * or + *
+ * public class COMPILER {
+ *     public COMPILER();
+ *     public int compile(String[] args);
+ * }
+ *
+ * or + *
+ * public class COMPILER {
+ *    public static int compile(String[] args, PrintWriter out);
+ * }
+ * 
+ * This means the command is suitable for (but not limited to) the + * compiler javac suplied with JDK. (Note that this uses an internal + * API of javac which is not documented and is not guaranteed to exist + * in any specific release of JDK.) + */ +public class JavaCompileCommand extends Command +{ + /** + * A stand-alone entry point for this command. An instance of this + * command is created, and its run method invoked, + * passing in the command line args and System.out and + * System.err as the two streams. + * @param args command line arguments for this command. + * @see #run + */ + public static void main(String[] args) { + PrintWriter out = new PrintWriter(System.out); + PrintWriter err = new PrintWriter(System.err); + Status s; + try { + JavaCompileCommand c = new JavaCompileCommand(); + s = c.run(args, out, err); + } + finally { + out.flush(); + err.flush(); + } + s.exit(); + } + + + /** + * Invoke a specified compiler, or the default, javac. + * If the first word in the args array is "-compiler" + * the second is interpreted as the class name for the compiler to be + * invoked, optionally preceded by a name for the compiler, separated + * from the class name by a colon. If no -compiler is specified, + * the default is `javac:com.sun.tools.javac.Main'. If -compiler is specified + * but no compiler name is given before the class name, the default name + * will be `java ' followed by the classname. For example, `-compiler Main' + * will result in the class name being `Main' and the compiler name being + * `java Main'. After determining the class and compiler name, + * an instance of the compiler class will be created, passing it a stream + * using the ref parameter, and the name of the compiler. + * Then the `compile' method will be invoked, passing it the remaining + * values of the `args' parameter. If the compile method returns true, + * the result will be a status of `passed'; if it returns `false', the + * result will be `failed'. If any problems arise, the result will be + * a status of `error'. + * @param args An optional specification for the compiler to be invoked, + * followed by arguments for the compiler's compile method. + * @param log Not used. + * @param ref Passed to the compiler that is invoked. + * @return `passed' if the compilation is successful; `failed' if the + * compiler is invoked and errors are found in the file(s) + * being compiler; or `error' if some more serios problem arose + * that prevented the compiler performing its task. + */ + public Status run(String[] args, PrintWriter log, PrintWriter ref) { + + if (args.length == 0) + return Status.error("No args supplied"); + + String compilerClassName = null; + String compilerName = null; + String classpath = null; + String[] options = null; + + // If we find a '-' in the args, what comes before it are + // options for this class and what comes after it are args + // for the compiler class. If don't find a '-', there are no + // options for this class, and everything is handed off to + // the compiler class + + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-")) { + options = new String[i]; + System.arraycopy(args, 0, options, 0, options.length); + args = shift(args, i+1); + break; + } + } + + if (options != null) { + for (int i = 0; i < options.length; i++) { + if (options[i].equals("-compiler")) { + if (i + 1 == options.length) + return Status.error("No compiler specified after -compiler option"); + + String s = options[++i]; + int colon = s.indexOf(":"); + if (colon == -1) { + compilerClassName = s; + compilerName = "java " + s; + } + else { + compilerClassName = s.substring(colon + 1); + compilerName = s.substring(0, colon); + } + } + else if (options[i].equals("-cp") || options[i].equals("-classpath")) { + if (i + 1 == options.length) + return Status.error("No path specified after -cp or -classpath option"); + classpath = options[++i]; + } + else if (options[i].equals("-verbose")) + verbose = true; + else + return Status.error("Unrecognized option: " + options[i]); + } + } + + this.log = log; + + try { + + ClassLoader loader; + if (classpath == null) + loader = null; + else + loader = new PathClassLoader(classpath); + + Class compilerClass; + if (compilerClassName != null) { + compilerClass = getClass(loader, compilerClassName); + if (compilerClass == null) + return Status.error("Cannot find compiler: " + compilerClassName); + } + else { + compilerName = "javac"; + compilerClass = getClass(loader, "com.sun.tools.javac.Main"); // JDK1.3+ + if (compilerClass == null) + compilerClass = getClass(loader, "sun.tools.javac.Main"); // JDK1.1-2 + if (compilerClass == null) + return Status.error("Cannot find compiler"); + } + + loader = null; + + Object[] compileMethodArgs; + Method compileMethod = getMethod(compilerClass, "compile", // JDK1.4+ + new Class[] { String[].class, PrintWriter.class }); + if (compileMethod != null) + compileMethodArgs = new Object[] { args, ref }; + else { + compileMethod = getMethod(compilerClass, "compile", // JDK1.1-3 + new Class[] { String[].class }); + if (compileMethod != null) + compileMethodArgs = new Object[] { args }; + else + return Status.error("Cannot find compile method for " + compilerClass.getName()); + } + + Object compiler; + if (Modifier.isStatic(compileMethod.getModifiers())) + compiler = null; + else { + Object[] constrArgs; + Constructor constr = getConstructor(compilerClass, // JDK1.1-2 + new Class[] { OutputStream.class, String.class }); + if (constr != null) + constrArgs = new Object[] { new WriterStream(ref), compilerName }; + else { + constr = getConstructor(compilerClass, new Class[0]); // JDK1.3 + if (constr != null) + constrArgs = new Object[0]; + else + return Status.error("Cannot find suitable constructor for " + compilerClass.getName()); + } + try { + compiler = constr.newInstance(constrArgs); + } + catch (Throwable t) { + t.printStackTrace(log); + return Status.error("Cannot instantiate compiler"); + } + } + + Object result; + try { + result = compileMethod.invoke(compiler, compileMethodArgs); + } + catch (Throwable t) { + t.printStackTrace(log); + return Status.error("Error invoking compiler"); + } + + // result might be a boolean (old javac) or an int (new javac) + if (result instanceof Boolean) { + boolean ok = ((Boolean)result).booleanValue(); + return (ok ? passed : failed); + } + else if (result instanceof Integer) { + int rc = ((Integer)result).intValue(); + return (rc == 0 ? passed : failed); + } + else + return Status.error("Unexpected return value from compiler: " + result); + } + finally { + log.flush(); + ref.flush(); + } + } + + private Class getClass(ClassLoader loader, String name) { + try { + return (loader == null ? Class.forName(name) : loader.loadClass(name)); + } + catch (ClassNotFoundException e) { + return null; + } + } + + private Constructor getConstructor(Class c, Class[] argTypes) { + try { + return c.getConstructor(argTypes); + } + catch (NoSuchMethodException e) { + return null; + } + catch (Throwable t) { + if (verbose) + t.printStackTrace(log); + return null; + } + } + + private Method getMethod(Class c, String name, Class[] argTypes) { + try { + return c.getMethod(name, argTypes); + } + catch (NoSuchMethodException e) { + return null; + } + catch (Throwable t) { + if (verbose) + t.printStackTrace(log); + return null; + } + } + + private static String[] shift(String[] args, int n) { + String[] newArgs = new String[args.length - n]; + System.arraycopy(args, n, newArgs, 0, newArgs.length); + return newArgs; + } + + public static boolean defaultVerbose = Boolean.getBoolean("javatest.JavaCompileCommand.verbose"); + private boolean verbose = defaultVerbose; + private PrintWriter log; + + private static final Status passed = Status.passed("Compilation successful"); + private static final Status failed = Status.failed("Compilation failed"); +} diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/KeywordScript.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/KeywordScript.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,235 @@ +/* + * $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.util.Iterator; +import java.util.Set; +import java.util.Vector; +import java.io.IOException; +import java.io.PrintWriter; + +import com.sun.javatest.Script; +import com.sun.javatest.Status; +import com.sun.javatest.TestResult; +import com.sun.javatest.TestDescription; +import com.sun.javatest.TestEnvironment; +import com.sun.javatest.util.StringArray; + +/** + * Default script, which delegates to one of a number of scripts defined in + * environment entries, according to the keywords on the test description. + */ +public class KeywordScript extends Script +{ + /** + * Run the script, using the parameters set up by the standard initialization + * methods. + */ + public void run() { + + PrintWriter trOut = getTestResult().getTestCommentWriter(); + TestDescription td = getTestDescription(); + + for (int i = 0; i < scriptArgs.length; i++) { + if (scriptArgs[i].equals("-debug")) + debug = true; + else { + setStatus(Status.error("bad args for script: " + scriptArgs[i])); + return; + } // else + } // for + + String prefix = "script."; + Set testKeys = td.getKeywordTable(); + Vector choices = new Vector(); // the set of choices + Vector matches = new Vector(); // the set of matches + int wordsMatchingInMatches = 0;// the number of words matching + + findMatch: + for (Iterator iter = env.keys().iterator(); iter.hasNext(); ) { + String key = (String) (iter.next()); + + // if the key does not begin with the `script.' prefix, ignore key + if (!key.startsWith(prefix)) + continue; + + if (debug) + trOut.println("CHECKING " + key); + + String keyList = key.substring(prefix.length()).replace('_', ' ').toLowerCase(); + String[] keys = StringArray.split(keyList); + + choices.addElement(keyList); + + if (debug) + trOut.println("keys: " + StringArray.join(keys)); + + // if there are no words after the `script.' prefix, + // or if it has fewer words than the best match so far, ignore key + if (keys == null || keys.length < wordsMatchingInMatches) + continue; + + for (int i = 0; i < keys.length; i++) { + // if key has a word that is not for the test, ignore key + if (!testKeys.contains(keys[i])) { + + if (debug) + trOut.println("discarding, because of " + keys[i]); + + continue findMatch; + } + } + + // see if key is better than best so far + if (keys.length > wordsMatchingInMatches) { + // update best so far + + if (debug) + trOut.println("new best match, " + keys.length + " keys"); + + matches = new Vector(); + wordsMatchingInMatches = keys.length; + } + + // this key deserves note + matches.addElement(key); + } // for + + // check we have a unique script selected + String name = env.getName(); + String envName = (name.length() == 0 ? + "The anonymous environment" : + "Environment `" + env.getName() + "'"); + if (matches.size() == 0) { + if (choices.size() == 0) { + String s = envName + " has no `script' entries"; + trOut.println(s); + setStatus(Status.error(s)); + return; + } + else { + String s = envName + " has no suitable `script' entry"; + trOut.println(s); + trOut.println("The keyword combinations for scripts in this environment are: "); + for (int i = 0; i < choices.size(); i++) { + trOut.println((String)choices.elementAt(i)); + } // for + + setStatus(Status.error(s)); + return; + } // inner else + } else if (matches.size() > 1) { + String s = envName + " has ambiguous `script' entries"; + trOut.println(s); + for (int i = 0; i < matches.size(); i++) { + trOut.println(i + ": " + matches.elementAt(i)); + } // for + + setStatus(Status.error(s)); + return; + } // else if + + String bestScript = (String)matches.elementAt(0); + //trOut.report.println("BEST " + bestScript); + + try { + String[] command = env.lookup(bestScript); + if (command.length == 0) { + String s = "INTERNAL ERROR: failed to lookup key: " + bestScript; + trOut.println(s); + setStatus(Status.error(s)); + return; + } + + trOut.println("test: " + td.getRootRelativeURL()); + trOut.println("script: " + this.getClass().getName() + " " + + StringArray.join(scriptArgs)); + + String[] msgs = { + "Based on these keywords: " + + bestScript.substring(prefix.length()).replace('_', ' ').toLowerCase(), + "this script has now been selected: " + " " + + StringArray.join(command) }; + printStrArr(trOut, msgs); + + try { + Class c = Class.forName(command[0]); + + Script script = (Script)c.newInstance(); + String[] scriptArgs = new String[command.length - 1]; + System.arraycopy(command, 1, scriptArgs, 0, scriptArgs.length); + initDelegate(script, scriptArgs); + + script.run(); + } + catch (ClassNotFoundException ex) { + setStatus(Status.error("Can't find class `" + + command[0] + "' for `" + env.getName() + "'")); + } + catch (IllegalAccessException ex) { + setStatus(Status.error("Illegal access to class `" + + command[0] + "' for `" + env.getName() + "'")); + } + catch (InstantiationException ex) { + setStatus(Status.error("Can't instantiate class`" + + command[0] + "' for `" + env.getName() + "'")); + } + } + catch (TestEnvironment.Fault ex) { + setStatus(Status.error("environment `" + + env.getName() + + "' has bad `script' entry for `" + + bestScript +"'")); + } + } + + public Status run(String[] args, TestDescription td, TestEnvironment env) { + throw new Error("Method not applicable."); + } + + private static void printStrArr(PrintWriter pw, String[] data) { + if(data == null) return; + + for(int i = 0; i < data.length; i++) { + pw.println(data[i]); + } + } + + private void setStatus(Status s) { + TestResult tr = getTestResult(); + tr.setEnvironment(env); + tr.setStatus(s); + try { + tr.writeResults(workDir, backupPolicy); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + private static boolean debug = false; +} diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/MultiStatus.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/MultiStatus.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,246 @@ +/* + * $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.PrintWriter; +import com.sun.javatest.Status; + +/** + * When executing multiple test cases in the same test class, it is usually + * easier for each test case to return a Status object representing whether that + * individual test case passed or failed. However, combining those individual + * Status objects into one Status object to represent the overall Status of the + * tests executed can be difficult. This test library is designed to solve the + * problem of generating an aggregate or overall Status from multiple Status + * objects. The static method overallStatus is designed to take an array of + * Status objects from individual test cases and generate a Status object that + * correctly reflects the overall status of all the individual the test + * cases executed. + * + *

The rule for how MultiStatus calculates the overall Status of an array of + * Status objects is based on the following precedence: + *

+ * If any of the test cases return a Status.FAILED, then the overall status is + * Status.FAILED.
+ * If all test cases return Status.PASSED, then the overall status is + * Status.PASSED.
+ * If at least one test case returns either a null Status or some other Status, + * the overall status is Status.FAILED. + *
+ * + *

For an example of how to use this library see the UmbrellaTest library or the + * JCK test case: tests/api/java_lang/Double/SerializeTests.html. + */ +public class MultiStatus { + + /** + * Create a MultiStatus object to accumulate individual Status objects. + */ + public MultiStatus() { + } + + /** + * Create a MultiStatus object to accumulate individual Status objects. + * @param out A stream to which the report the outcome of the tests. + * If the stream is null, no reporting is done. + */ + public MultiStatus(PrintWriter out) { + this.out = out; + } + + /** + * Get the number of individual test results that have been added. + * @return the number of individual results that have been added. + */ + public int getTestCount() { + return iTestCases; + } + + /** + * Add another test result into the set for consideration. + * + * @param testID A name for this test case. Should not be null. + * @param status The outcome of this test case + */ + public void add(String testID, Status status) { + if (out != null) { + out.println(testID + ": " + status); + } + + ++iTestCases; + + if (status != null) { + int t = status.getType(); + + switch (t) { + case Status.PASSED: + ++iPassed; + break; + + case Status.FAILED: + ++iFail; + break; + + case Status.ERROR: + ++iError; + break; + + default: + ++iBad; + break; + } + + if (t != Status.PASSED && firstTestCase.length() == 0) + firstTestCase = testID; + } + } + + /** + * Get the aggregate outcome of all the outcomes passed to "add". + * @return the aggregate outcome + */ + public Status getStatus() { + + // If stream not null, flush it + if( out != null ) { + out.flush(); + } + + String summary; + if (iTestCases == 0) + summary = "No tests cases found (or all test cases excluded.)"; + else { + summary = "test cases: " + iTestCases; + if (iPassed > 0) { + if (iPassed == iTestCases) + summary += "; all passed"; + else + summary += "; passed: " + iPassed; + } + + if (iFail > 0) { + if (iFail == iTestCases) + summary += "; all failed"; + else + summary += "; failed: " + iFail; + } + + if (iError > 0) { + if (iError == iTestCases) + summary += "; all had an error"; + else + summary += "; error: " + iError; + } + + if (iBad > 0) { + if (iBad == iTestCases) + summary += "; all bad"; + else + summary += "; bad status: " + iBad; + } + } + + /* Return a status object that reflects the aggregate of the various test cases. */ + + /* At least one test case was bad */ + if (iBad > 0) { + return Status.error(summary + "; first bad test case result found: " + firstTestCase); + } + /* At least one test case was bad */ + else if (iError > 0) { + return Status.error(summary + "; first test case with error: " + firstTestCase); + } + /* At least one test case failed */ + else if (iFail > 0) { + return Status.failed(summary + "; first test case failure: " + firstTestCase); + } + /* All test cases passed */ + else { + return Status.passed(summary); + } + } + + + /** + * Generates a Status object that reflects an array of Status objects. + * Uses the algorithm above to generate an overall status from an array of + * Status objects. This method prints out the individual Status values from + * each test case to the PrintWriter supplied. If the PrintWriter is null, + * no output is generated. + * + * @param testIDs an array of names used to identify the individual test cases. + * @param status an array of Status objects giving the outcomes of the individual test cases. + * @param out a PrintWriter that can be used to output the individual test case + * status values. If null, no output is generated. + * @return the aggregate status of the array of Status objects. + */ + public static Status overallStatus(String testIDs[], Status status[], PrintWriter out) { + + /* Check the number of tests against the number of statuses */ + if( testIDs.length != status.length ) { + return Status.failed( "mismatched array sizes; test cases: " + testIDs.length + + " statuses: " + status.length ); + } + + /* Loop through status objects, check types, + * increment appropriate counters, and identify the + * first test that should be listed in the aggregate status. + */ + + MultiStatus ms = new MultiStatus(out); + for( int i = 0; i < status.length; ++i ) { + ms.add(testIDs[i], status[i]); + } + + return ms.getStatus(); + } + + + /** + * Generates a Status object that reflects an array of Status objects. + * Uses the algorithm above to generate an overall status from an array of + * Status objects. This method does not output any information + * + * @param testIDs an array of names used to identify the individual test cases. + * @param status an array of Status objects giving the outcomes of the individual test cases. + * @return overall status of the specified array of Status objects. + */ + public static Status overallStatus(String testIDs[], Status status[]) { + return MultiStatus.overallStatus(testIDs, status, null); + } + + // These values accumulate the aggregate outcome, without requiring + // the individual outcomes be stored + private int iTestCases = 0; + private int iPassed = 0; + private int iFail = 0; + private int iError = 0; + private int iBad = 0; + private String firstTestCase = ""; + + private PrintWriter out = null; +} diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/MultiTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/MultiTest.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,314 @@ +/* + * $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.OutputStreamWriter; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import com.sun.javatest.lib.TestCases; +import com.sun.javatest.Status; +import com.sun.javatest.Test; + +/** + * Base class for tests with multiple sub test cases. + * This base class implements the standard com.sun.javatest.Test + * features so that you can provide the additional test cases without concern about + * the boilerplate needed to execute individual test case methods. + * + *

You must add individual test case methods to your derived test class + * to create a useful test class. Each test case method must take no + * arguments. If you need to pass an argument into a test method, you should + * design a wrapper test case to calculate the argument values and then call + * the test method with the correct arguments. The test case methods must + * implement this interface: + *

+ * public Status methodName( ) + *
+ * + * @see com.sun.javatest.Test + * @see com.sun.javatest.lib.TestCases + */ +public class MultiTest implements Test +{ + /** + * This exception is thrown when a problem occurs initializing the test. + * It may also be used to indicate that the test is not applicable in the + * current circumstances and should not be run. + */ + public static class SetupException extends Exception { + /** + * Construct a new SetupException object that signals failure + * with a corresponding message. + * + * @param s the string containing a comment + */ + public SetupException(String s) { + super(s); + } + + /** + * Creates a SetupException object which indicates that + * this test is not applicable. The cases when it is needed + * are rare, so please think twice whether you really need it. + * + * @param msg a detail string, explaining why the test is "not applicable". + * @return an exception object that indicates the test should not be run + * because it is not applicable. + */ + public static SetupException notApplicable(String msg) { + SetupException e = new SetupException("Test not applicable: " + msg); + e.passed = true; + return e; + } + + /** + * Determines whether this SetupException signals failure or not. + * @return true if and only if the test is not applicable and should be + * deemed to have "passed, by default". + * + */ + public boolean isPassed() { + return passed; + + } + + /** + * Indicate whether this exception was the result of calling {@link #notApplicable}. + * @serial + */ + private boolean passed = false; + } + + + /** + * Run the test cases contained in this object. The test cases are determined + * and invoked via reflection. The set of test cases can be specified with + * -select case1,case2,case3... and/or restricted with -exclude case1,case2,case3... + * + * @see #decodeAllArgs + * @see #init + * + * @param args Execute arguments passed in from either the + * command line or the execution harness. + * @param log Output stream for general messages from the tests. + * @param ref Output stream for reference output from the tests. + * @return Overall status of running all of the test cases. + */ + public Status run(String[] args, PrintWriter log, PrintWriter ref) { + this.log = log; + this.ref = ref; + testCases = new TestCases(this, log); + + Status initStatus = init(args); + if (testNotApplicable + || (initStatus != null && initStatus.getType( ) != Status.PASSED)) { + return initStatus; + } + + return testCases.invokeTestCases(); + } + + /** + * Run the test cases contained in this object + * + * This method is a convenience wrapper around the primary run method + * which takes PrintWriters: this variant takes PrintStreams and wraps + * them into PrintWriters. + * + * @see #decodeAllArgs + * @see #init + * + * @param argv Execute arguments passed in from either the + * command line or the execution harness. + * @param log Output stream for general messages from the tests. + * @param ref Output stream for reference output from the tests. + * @return Overall status of running all of the test cases. + */ + public final Status run(String[] argv, PrintStream log, PrintStream ref) { + PrintWriter pwLog = new PrintWriter(new OutputStreamWriter(log)); + PrintWriter pwRef = new PrintWriter(new OutputStreamWriter(ref)); + try { + return run(argv, pwLog, pwRef); + } + finally { + pwLog.flush(); + pwRef.flush(); + } + } + + + /** + * + * Initialize the test from the given arguments. The arguments will + * be passed to decodeAllArgs, and then init() + * will be called. + * + * @param args The arguments for the test, passed to decodeArgs. + * @return null if initialization is successful, or a status indicating why + * initialization was not successful. + * + * @see #decodeAllArgs + * @see #decodeArg + * @see #init() + * + * @deprecated Use decodeArg(String) and init() instead. + */ + protected Status init(String[] args) { + try { + decodeAllArgs(args); + init(); + return null; + } + catch (SetupException e) { + testNotApplicable = true; + return (e.isPassed() + ? Status.passed(e.getMessage()) + : Status.failed(e.getMessage()) ); + } + } + + /** + * A setup method called after argument decoding is complete, + * and before the test cases are executed. By default, it does + * nothing; it may be overridden to provide additional behavior. + * + * @throws MultiTest.SetupException if processing should not continue. + * This may be due to some inconsistency in the arguments, + * or if it is determined the test should not execute for + * some reason. + */ + protected void init() throws SetupException { } + + /** + * Parses the arguments passed to the test. + * + * This method embodies the main loop for all of the test's arguments. + * It calls decodeArg for successive arguments in the + * argument array. + * + * @param args arguments passed to the test. + * + * @throws MultiTest.SetupException raised when an invalid parameter is passed, + * or another error occurred. + * + * @see #decodeArg + */ + protected final void decodeAllArgs(String args[]) throws SetupException { + int i = 0; + while (i < args.length) { + int elementsConsumed = decodeArg(args, i); + if (elementsConsumed == 0 ) { + // The argument was not recognized. + throw new SetupException("Could not recognize argument: " + args[i]); + } + i += elementsConsumed; + } + } + + /** + * Decode the next argument in the argument array. This will typically be + * overridden by subtypes that wish to decode additional arguments. If an + * overriding method does not recognize an argument, it should return + * super.decodeArg(args, index) to give supertypes a change + * to decode the argument as well. + * + * @param args The array containing all the arguments + * @param index The position of the next argument to be decoded. + * @return the number of elements in the array were "consumed" by this call. + * + * @throws MultiTest.SetupException is there is a problem decoding the + * argument. + */ + protected int decodeArg(String[] args, int index) throws SetupException { + try { + if (args[index].equals("-select") && index + 1 < args.length) { + testCases.select(args[index + 1]); + return 2; + } + else if (args[index].equals("-exclude") && index + 1 < args.length) { + testCases.exclude(args[index + 1]); + return 2; + } + // support -TestCaseID for historical compatibility + else if (args[index].equals("-TestCaseID")) { + if (index + 1 < args.length && args[index + 1].equals("ALL")) { + // ignore -TestCaseID ALL, since it is the default + return 2; + } + else { + int i; + for (i = index + 1; i < args.length && !args[i].startsWith("-"); i++) + testCases.select(args[i]); + return (i - index); + } + + } + else if (args[index].equals("-autoFlush")) { + ref = new PrintWriter(ref, true); + log = new PrintWriter(ref, true); + return 1; + } + else + return 0; + } + catch (TestCases.Fault e) { + throw new SetupException(e.getMessage()); + } + } + + /** + * Default way to invoke a specified test case. + * @param m The method to be invoked. + * @return The result of invoking the specified test case. + * @throws IllegalAccessException if there was a problem accessing the specified method + * @throws InvocationTargetException if the specified method threw an exception when + * it was invoked. + */ + protected Status invokeTestCase(Method m) + throws IllegalAccessException, InvocationTargetException { + Object[] testArgs = { }; + return (Status) (m.invoke(this, testArgs)); + } + + // the set of test cases to be executed + private TestCases testCases; + + // may be set if SetupException is thrown during decodeArgs() or init + private boolean testNotApplicable; + + /** + * Output to be logged to result file. + */ + protected PrintWriter ref; + + /** + * Output to be logged to result file. + */ + protected PrintWriter log; +} diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/ProcessCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/ProcessCommand.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,462 @@ +/* + * $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 run method invoked, + * passing in the command line args and System.out and + * System.err 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 + // 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 + * command-options, + * environment-variables, + * command, + * command-arguments. + *
+ * + * The command-options are an optional + * set of options, each beginning with `-', to be + * used by this object. + * The options are + *
+ *
-v + *
verbose mode + *
-pass|-fail|-error exit-code string + *
set the status to be returned for the given + * exit code to one of + * Status.passed/Status.failed/Status.error. + * exit-code can be either an integer or "default". + * string the message string provided in the + * status object. + *
-execDir execDir + *
set the directory in which to execute the command. + * + *
+ * + * The environment-variables are an + * optional list of environment variable to be supplied + * to the command. They should be in the form + * NAME=VALUE. + *
+ * + * The command identifies the command to + * be executed. This name will be platform specific. + *
+ * + * The command-arguments 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 + * getStatus 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: + * Status.passed("exit code 0") + * if the command exited with exit status 0, or + * Status.failed("exit code " + exitCode) + * otherwise. getStatus 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 + * getStatus 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 + * status.exit(). + * + * @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 status.exit(), + * 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 + * Status.passed("exit code 0") + * if the command exited with exit code 0, or + * Status.failed("exit code " + exitCode) + * 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; +} + diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/ReportScript.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/ReportScript.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,59 @@ +/* + * $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 com.sun.javatest.Script; +import com.sun.javatest.Status; +import com.sun.javatest.TestDescription; +import com.sun.javatest.TestEnvironment; +import com.sun.javatest.TestResult; + +/** + * A special script which reads the result file which is presumed to exist in + * the work directory as the result of a prior run. This will allow the + * creation of a TestResult object from a old test run. + */ +public class ReportScript extends Script +{ + public final void run() { + TestResult tr = getTestResult(); + try { + tr.reloadFromWorkDir(workDir); + } + catch (TestResult.Fault ignore) { + } + } + + /** This method should not be called; for this class, its identity + * is significant, not its implementation. + * + * @throws Error if called + */ + public Status run(String[] args, TestDescription td, TestEnvironment env) { + throw new Error("should not be called"); + } +} diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/StdTestScript.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/StdTestScript.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,223 @@ +/* + * $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.File; +import com.sun.javatest.Script; +import com.sun.javatest.Status; +import com.sun.javatest.TestDescription; +import com.sun.javatest.TestEnvironment; +import com.sun.javatest.util.StringArray; + +/** + * A Script to compile/execute a standard test. + */ +public class StdTestScript extends Script +{ + public Status run(String[] args, TestDescription td, TestEnvironment env) { + try { + String[] m = env.lookup("script.mode"); + if (m != null && m.length == 1) + setMode(m[0]); + } + catch (TestEnvironment.Fault e) { + return Status.failed("error determining script mode: " + e.getMessage()); + } + + boolean compile = false; + boolean execute = false; + boolean expectFail = false; + + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + + if (arg.equals("-certify")) { + compile = false; + execute = true; + } + else if (arg.equals("-precompile")) { + compile = true; + execute = false; + } + else if (arg.equals("-developer")) { + compile = true; + execute = true; + } + else if (arg.equals("-compile")) { + compile = true; + } + else if (arg.equals("-execute")) { + execute = true; + } + else if (arg.equals("-expectFail")) { + expectFail = true; + } + else + return Status.failed("bad arg for script: `" + arg + "'"); + } + + if (compile == false && execute == false) { + // not set in args, so set from mode + compile = (mode == DEVELOPER || mode == PRECOMPILE); + execute = (mode == DEVELOPER || mode == CERTIFY); + } + + if (compile) { + String srcsParameter = td.getParameter("sources"); + if (srcsParameter == null) + // check "source" for backwards compatibility + srcsParameter = td.getParameter("source"); + String[] srcs = StringArray.split(srcsParameter); + File[] files = new File[srcs.length]; + File tdDir = td.getDir(); + for (int i = 0; i < files.length; i++) + files[i] = new File(tdDir, srcs[i].replace('/', File.separatorChar)); + + Status compileStatus = compileTogether(files); + + // if we're not going to execute the test, this is the end of the task + if (!execute) { + if (expectFail) { + // backwards compatibility for negative compiler tests, + // for which we expect the compilation to fail, + // so verify that it did, and return accordingly + if (compileStatus.getType() == Status.FAILED) + return pass_compFailExp.augment(compileStatus); + else + return fail_compSuccUnexp.augment(compileStatus); + } else + // normal exit for compile-only tests + return compileStatus; + } + + // if we want to execute the test, but the compilation failed, we can't go on + if (compileStatus.isFailed()) + return fail_compFailUnexp.augment(compileStatus); + } + + if (execute) { + String executeClass = td.getParameter("executeClass"); + if (executeClass == null) + return error_noExecuteClass; + + Status executeStatus = execute(executeClass, td.getParameter("executeArgs")); + + if (expectFail) { + // backwards compatibility for negative execution tests, + // for which we expect the execution to fail, + // so verify that it did, and return accordingly + if (executeStatus.getType() == Status.FAILED) + return pass_execFailExp.augment(executeStatus); + else + return fail_execSuccUnexp.augment(executeStatus); + } else + // normal exit for (compile and) execute tests + return executeStatus; + } + + return error_noActionSpecified; + } + + /** + * Get the execution mode for this script. The default mode is CERTIFY. + * @return an integer signifying the execution mode for this script + * @see #setMode + * @see #UNKNOWN + * @see #CERTIFY + * @see #PRECOMPILE + * @see #DEVELOPER + */ + public int getMode() { + return mode; + } + + /** + * Set the execution mode for this script. + * @param mode an integer signifying the execution mode for this script + * @see #getMode + * @see #UNKNOWN + * @see #CERTIFY + * @see #PRECOMPILE + * @see #DEVELOPER + */ + public void setMode(int mode) { + switch (mode) { + case CERTIFY: + case PRECOMPILE: + case DEVELOPER: + this.mode = mode; + break; + + default: + throw new IllegalArgumentException(); + } + } + + private void setMode(String mode) { + setMode(parseMode(mode)); + } + + private static int parseMode(String m) { + if (m == null || m.equals("certify")) + return CERTIFY; + else if (m.equals("precompile")) + return PRECOMPILE; + else if (m.equals("developer")) + return DEVELOPER; + else + return UNKNOWN; + } + + private static int getDefaultMode() { + return parseMode(System.getProperty("javatest.stdTestScript.defaultMode")); + } + + /** + * An integer signifying that the execution mode is unknown. + */ + public static final int UNKNOWN = 0; + + /** + * An integer signifying that the execution mode is to perform + * a certification run, executing precompiled classes. + */ + public static final int CERTIFY = 1; + + /** + * An integer signifying that the execution mode is to precompile + * but not otherwise execute the tests. + */ + public static final int PRECOMPILE = 2; + + /** + * An integer signifying that the execution mode is to compile + * and execute the tests. + */ + public static final int DEVELOPER = 3; + + private int mode = getDefaultMode(); +} diff -r bf4662205a02 -r f42fc832db86 test/jtreg/com/sun/javatest/lib/TestCases.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jtreg/com/sun/javatest/lib/TestCases.java Mon May 19 10:33:03 2008 +0200 @@ -0,0 +1,275 @@ +/* + * $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.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import com.sun.javatest.Status; +import com.sun.javatest.Test; + +/** + * A handler for the set of test cases in a test. + * Test cases are those methods with no args that return a + * {@link com.sun.javatest.Status status}. + * Test cases can be explicitly selected into or excluded from the + * set. + */ +public class TestCases { + /** + * Exception used to report internal errors. + */ + public static class Fault extends Exception { + /** + * Construct a new Fault object that signals failure + * with a corresponding message. + * + * @param s the string containing a comment + */ + public Fault(String s) { + super(s); + } + } + + /** + * Create an object to handle the test cases of the given test. + * @param t The test containing the test cases. + * @param log An optional stream to which to write log messages. + * Use null if messages are not desired. + */ + public TestCases(Test t, PrintWriter log) { + test = t; + this.log = log; + testClass = t.getClass(); + } + + /** + * Explicitly select a set of test cases by name. Subsequent calls + * are cumulative; if no selections are made, the default is all + * test cases are selected. Excluded tests will be excluded from the + * selection; the order of select and exclude calls does not matter. + * @param testCaseNames a comma-separated list of test cases names. + * Each name must identify a method in the test object, that takes + * no arguments and returns a {@link com.sun.javatest.Status status}. + * @throws TestCases.Fault if any of the test case names are invalid. + */ + public void select(String testCaseNames) throws Fault { + select(split(testCaseNames)); + } + + + /** + * Explicitly select a set of test cases by name. Subsequent calls + * are cumulative; if no selections are made, the default is all + * test cases are selected. Excluded tests will be excluded from the + * selection; the order of select and exclude calls does not matter. + * @param testCaseNames an array of test cases names. + * Each name must identify a method in the test object, that takes + * no arguments and returns a {@link com.sun.javatest.Status status}. + * @throws TestCases.Fault if any of the test case names are invalid. + */ + public void select(String[] testCaseNames) throws Fault { + for (int i = 0; i < testCaseNames.length; i++) { + String t = testCaseNames[i]; + selectedCases.put(t, getTestCase(t)); + } + } + + + /** + * Explicitly exclude a set of test cases by name. Subsequent calls + * are cumulative; by default, no test cases are excluded. + * @param testCaseNames a comma-separated list of test cases names. + * Each name must identify a method in the test object, that takes + * no arguments and returns a {@link com.sun.javatest.Status status}. + * @throws TestCases.Fault if any of the test case names are invalid. + */ + public void exclude(String testCaseNames) throws Fault { + exclude(split(testCaseNames)); + } + + + /** + * Explicitly exclude a set of test cases by name. Subsequent calls + * are cumulative; by default, no test cases are excluded. + * @param testCaseNames an array of test cases names. + * Each name must identify a method in the test object, that takes + * no arguments and returns a {@link com.sun.javatest.Status status}. + * @throws TestCases.Fault if any of the test case names are invalid. + */ + public void exclude(String[] testCaseNames) throws Fault { + for (int i = 0; i < testCaseNames.length; i++) { + String t = testCaseNames[i]; + excludedCases.put(t, getTestCase(t)); + } + } + + + /** + * Return an enumeration of the selected test cases, based on the + * select and exclude calls that have been made, if any. + * @return An enumeration of the test cases. + */ + public Enumeration enumerate() { + Vector v = new Vector(); + if (selectedCases.isEmpty()) { + Method[] methods = testClass.getMethods(); + for (int i = 0; i < methods.length; i++) { + Method m = methods[i]; + if (excludedCases.get(m.getName()) == null) { + Class[] paramTypes = m.getParameterTypes(); + Class returnType = m.getReturnType(); + if ((paramTypes.length == 0) && Status.class.isAssignableFrom(returnType)) + v.addElement(m); + } + } + } + else { + for (Enumeration e = selectedCases.elements(); e.hasMoreElements(); ) { + Method m = (Method)(e.nextElement()); + if (excludedCases.get(m.getName()) == null) + v.addElement(m); + } + } + return v.elements(); + } + + + /** + * Invoke each of the selected test cases, based upon the select and exclude + * calls that have been made, if any. + * If the test object provides a public method + * {@link com.sun.javatest.Status}invokeTestCase({@link java.lang.reflect.Method}) + * that method will be called to invoke the test cases; otherwise, the test + * cases will be invoked directly. + * It is an error if no test cases are selected, (or if they have all been excluded.) + * @return the combined result of executing all the test cases. + */ + public Status invokeTestCases() { + // see if test object provides Status invokeTestCase(Method m) + Method invoker; + try { + invoker = testClass.getMethod("invokeTestCase", new Class[] {Method.class}); + if (!Status.class.isAssignableFrom(invoker.getReturnType())) + invoker = null; + } + catch (NoSuchMethodException e) { + invoker = null; + } + + MultiStatus ms = new MultiStatus(log); + for (Enumeration e = enumerate(); e.hasMoreElements(); ) { + Method m = (Method)(e.nextElement()); + Status s; + try { + if (invoker != null) + s = (Status)invoker.invoke(test, new Object[] {m}); + else + s = (Status)m.invoke(test, noArgs); + } + catch (IllegalAccessException ex) { + s = Status.failed("Could not access test case: " + m.getName()); + } + catch (InvocationTargetException ex) { + printStackTrace(ex.getTargetException()); + s = Status.failed("Exception from test case: " + + ex.getTargetException().toString()); + } + catch (ThreadDeath t) { + printStackTrace(t); + throw t; + } + catch (Throwable t) { + printStackTrace(t); + s = Status.failed("Unexpected Throwable: " + t); + } + + ms.add(m.getName(), s); + } + if (ms.getTestCount() == 0) + return Status.passed("Test passed by default: no test cases executed."); + else + return ms.getStatus(); + } + + /** + * Print a stack trace for an exception to the log. + * @param t The Throwable for which to print the trace + */ + protected void printStackTrace(Throwable t) { + if (log != null) + t.printStackTrace(log); + } + + /** + * Look up a test case in the test object. + * @param name the name of the test case; it must identify a method + * Status name() + * @returns the selected method + * @throws Fault if the name does not identify an appropriate method. + */ + private Method getTestCase(String name) throws Fault { + try { + Method m = testClass.getMethod(name, noArgTypes); + if (!Status.class.isAssignableFrom(m.getReturnType())) + throw new Fault("Method for test case '" + name + "' has wrong return type" ); + return m; + } + catch (NoSuchMethodException e) { + throw new Fault("Could not find test case: " + name); + } + catch (SecurityException e) { + throw new Fault(e.toString()); + } + } + + private String[] split(String s) { + Vector v = new Vector(); + int start = 0; + for (int i = s.indexOf(','); i != -1; i = s.indexOf(',', start)) { + v.addElement(s.substring(start, i)); + start = i + 1; + } + if (start != s.length()) + v.addElement(s.substring(start)); + String[] ss = new String[v.size()]; + v.copyInto(ss); + return ss; + } + + private Object test; + private Class testClass; + private Hashtable selectedCases = new Hashtable(); + private Hashtable excludedCases = new Hashtable(); + private PrintWriter log; + + private static final Object[] noArgs = { }; + private static final Class[] noArgTypes = { }; +}