Modified: river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/MainTestDescription.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/MainTestDescription.java?rev=1634322&r1=1634321&r2=1634322&view=diff ============================================================================== --- river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/MainTestDescription.java (original) +++ river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/MainTestDescription.java Sun Oct 26 13:17:28 2014 @@ -1,909 +1,910 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.sun.jini.qa.harness; - -// java.util -import java.util.Properties; -import java.util.StringTokenizer; -import java.util.ArrayList; - -// java.util.logging -import java.util.logging.Logger; -import java.util.logging.Level; - -// java.io -import java.io.BufferedReader; -import java.io.File; -import java.io.Reader; -import java.io.Writer; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; - -/** - * A <code>TestDescription</code> which represents tests that are - * implemented as source code which is compiled automatically and - * executed by calling the main method of the compiled class. - * Tags - * must be provided in the first comment block of the test source. - * The supported syntax is quite rigid: - * <ul> - * <li>The tags must reside in the first comment block in the code - * <li>The comment block must comprise the first non-blank text - * in the code - * <li>The comment block must begin with the '\/\*' sequence and - * any tags on this initial line will be ignored - * <li>The tag '@test' must be the first tag in the comment block - * <li>Tags may be of the form: - * <table> - * <tr> - * <td>@key - * <td>key value is implicitly <code>true</code> - * <tr> - * <td>@key value1 value2 ... - * <td>values are separated by white space and may not - * contain white space - * <tr> - * <td>@key=value - * <td>value may contain any character, including '='. White - * space in value is retained - * </table> - * <li>The @library and @build tags may occur multiple times. - * The tag values are - * appended in the order read - * <li>If the <code>run</code> tag is encountered, the <code>run</code> - * options are processed. There is no action implied by the run - * tag. The test class returned by <code>getTestClassName</code> - * is always run as the test, regardless of the existance or content - * of the <code>run</code> tag. - * <li>The comment block must be terminated with the '\*\/' sequence - * </ul> - * The following tags are defined: - * <table> - * <tr> - * <td>@test - * <td>A mandatory tag which identifies this as a valid test description - * <tr> - * <td>@library - * <td>identifies libraries of source files which may be accessed by the - * test. The library directory must be specified relative to the location of the - * test source file containing the @library tag. Multiple entries per line, and - * multiple occurances of the @library tag, are allowed. - * <tr> - * <td>@build - * <td>identifies the names of source files to compile (the .java extension is - * omitted. All library files to be compiled must be named in @build tags. It is - * legal, but unnecessary, to include the name of the test source file to - * compile. However, if other source files in the test directory must also be - * compiled, those names should also be specified in the build tag. Multiple - * entries per line, and multiple occurances of the @build tag, are allowed. - * <tr> - * <td>@run main/policy=policyfile vmarg1 vmarg2 ... - * <td>This tag defines the execution environment of the test. The main token - * defines this as a main-style test, and may be omitted. If /policy= is - * defined, the named policy file is resolved relative to the test source - * directory. Any remaining tokens which begin with a '-' character are included - * in the VM options when the test VM is invoked. - * </table> - * Any other tags are interpreted as configuration value definitions. - */ -public class MainTestDescription extends TestDescription { - - /** the config object */ - private QAConfig config; - - /** The scratch directory */ - private File scratchDir = null; - - /** the directory containing the test source */ - private File testSourceDir = null; - - /** the directory containing the test classes */ - private File testClassDir = null; - - /** the policy file specified by the /policy= modifier */ - //XXX this field must not be explicitly set. It is set by - // a method called by the parent class constructor. If it - // is set here, the value set by the parent class constructor - // is overwritten. This is very bad. Rework this. - private String policyTag; - - /** the logger */ - private static final Logger logger = - Logger.getLogger("com.sun.jini.qa.harness"); - - /** - * Construct a test description for a test with the given <code>name</code>. - * The name is assumed to represent the name of a source file relative - * to the installation directory of the test harness. - * The internal properties object is populated by parsing a set of - * tags provided at the beginning of the test source file. - * - * @param name the name of the test - * @param config the config object for this test - * - * @throws TestException if mandatory tags are missing or well-known - * tags are malformed - */ - public MainTestDescription(String name, Properties p, QAConfig config) - throws TestException - { - super(name, p, config); - this.config = config; - } - - /** - * Overridden method which causes the constructor to initialize the - * internal properties object from tags in the source file. - * - * @throws TestException if mandatory tags are missing or well-known - * tags are malformed - */ - protected void initProperties() throws TestException { - parseTags(getName()); - } - - /** - * Indicates whether some other object is equal to this one. - * <code>obj</code> is considered equal to this object if it - * is an instance of <code>TestDescription</code> and has a - * name which is identical to this name. - * - * @param obj the object to test for equality - * @return true if <code>obj</code> is equal to this object - */ - public boolean equals(Object obj) { - if (!(obj instanceof MainTestDescription)) { - return false; - } - return ((MainTestDescription) obj).getName().equals(getName()); - } - - /** - * Return a hashcode value for the object. - * - * @return the hashcode - */ - public int hashCode() { - return getName().hashCode(); - } - - /** - * Return a string representation of this object. - * - * @return a string representing this object - */ - public String toString() { - return "MainTestDescription[" + getName() + "]"; - } - - - /** - * Returns the classpath for compilation and test execution. - * The classpath consists of the JAR file containing - * <code>MainWrapper</code> followed by the directory containing the - * test classes, followed by the directory containing the - * test sources. - * - * @return the classpath string - */ - public String getClasspath() { - if (scratchDir == null) { - throw new IllegalStateException("must call initDirectories first"); - } - String classpath = config.getKitHomeDir() - + File.separator - + "lib" - + File.separator - + "qa1-mainwrapper.jar"; - classpath += File.pathSeparator; - classpath += testClassDir.getAbsolutePath(); - classpath += File.pathSeparator; - classpath += testSourceDir.getAbsolutePath(); - return classpath; - } - - /** - * Augments the arguments returned by the superclass call with - * property definition strings for the properties: - * <ul> - * <li><code>test.src</code> - * <li><code>test.classes</code> - * </ul> - * - * @return the array of property definitions - */ - public String[] getJVMArgs() { - if (scratchDir == null) { - throw new IllegalStateException("must call initDirectories first"); - } - ArrayList l = new ArrayList(10); - String[] superStrings = super.getJVMArgs(); // typically null - if (superStrings != null) { - for (int i = 0; i < superStrings.length; i++) { - l.add(superStrings[i]); - } - } - l.add("-Dtest.src=" + testSourceDir.getAbsolutePath()); - l.add("-Dtest.classes=" + testClassDir.getAbsolutePath()); - if (getPolicyFile() != null) { - l.add("-Djava.security.manager=default"); - } - l.add("-Dcom.sun.jini.qa.home=" + config.getKitHomeDir()); - return (String[]) l.toArray(new String[l.size()]); - } - - - /** - * Get the policy file for the test VM. If the policy is not - * specified by the configuration, then <code>null</code> is - * returned to indicate no policy is to be used - * - * @return the policy file to use, or <code>null</code> - */ - public String getPolicyFile() { - String policyFile = config.getStringConfigVal("testPolicyfile", null); - if (policyFile != null) { - String installDir = config.getKitHomeDir(); - policyFile = config.relativeToAbsolutePath(installDir, policyFile); - } - return policyFile; - } - - /** - * Returns a default category name if no categories have been - * defined for the test. This is special behavior for main tests - * to retain compatibility with jtreg tagged source files. - * - * @return a single category, <code>default</code>, if no categories - * are defined for the test - */ - public String[] getCategories() { - String[] cats = super.getCategories(); - if (cats.length == 0) { - cats = new String[]{"default"}; - } - return cats; - } - - /** - * Get the command line for the test VM. Before the command - * line is constructed, any required test directories are - * created and source files are compiled. - * - * @throws TestException if a failure occurs setting up the - * test environment or compiling the sources. - */ - public String[] getCommandLine() throws TestException { - buildTest(); - return super.getCommandLine(null); - } - - /** - * Creates the working directories for the test, and sets the values - * for: - * <table> - * <tr><td>scratchDir<td>a scratch directory for misc test files - * <tr><td>testClassDir<td>the directory where the test classes are placed - * <tr><td>testSourceDir<td>the directory where the test sources are located - * </table> - * In addition, if <code>policyTag</code> is non-null, then a new security - * policy is generated in the scratch directory and the - * <code>testPolicyfile</code> configuration value is set to point to it. - * - * @throws TestException if the source directory is missing, if any - * of the target directories cannot be created, - * or if an I/OO error occurs creating the policy file - */ - private void initDirectories() throws TestException { - testSourceDir = - new File(config.getKitHomeDir(), getName()).getParentFile(); - if (!testSourceDir.exists()) { - throw new TestException("source directory " - + testSourceDir - + " missing"); - } - File baseDir = new File("maintests"); - if (!baseDir.exists()) { - if (!baseDir.mkdir()) { - throw new TestException("Can't make maintests directory"); - } - } - File classesRoot = new File(baseDir, "classes"); - if (!classesRoot.exists()) { - if (!classesRoot.mkdir()) { - throw new TestException("Can't make classes directory"); - } - } - testClassDir = new File(classesRoot, getName()).getParentFile(); - if (!testClassDir.exists()) { - if (!testClassDir.mkdirs()) { - throw new TestException("Can't make classes directory " - + testClassDir); - } - } - scratchDir = new File(baseDir, "scratch"); - if (!scratchDir.exists()) { - if (!scratchDir.mkdir()) { - throw new TestException("Can't make scratch directory"); - } - } - if (policyTag != null) { - File origPolicy = new File(testSourceDir, policyTag); - File newPolicy = null; - FileReader reader = null; - FileWriter writer = null; - try { - newPolicy = new File(scratchDir, policyTag + "_new"); - String sep = System.getProperty("line.separator"); - reader = new FileReader(origPolicy); - writer = new FileWriter(newPolicy); - writer.write("// grant added by harness" + sep); - writer.write("grant codebase \"file:${com.sun.jini.qa.home}${/}lib${/}qa1-mainwrapper.jar\" {" + sep); - writer.write(" permission java.security.AllPermission;" + sep); - writer.write("};" + sep); - writer.write(sep); - writer.write("grant {" + sep); - writer.write(" permission java.io.FilePermission \"" - + classesRoot.getAbsolutePath() - + "${/}-\", \"read\";" + sep); - writer.write("};" + sep); - writer.write(sep); - writer.write("// original policy file:" + sep); - writer.write("// " + origPolicy + sep); - int c; - while ((c = reader.read()) >= 0) { - writer.write(c); - } - } catch (IOException e) { - e.printStackTrace(); - throw new TestException("Error creating policy file" - + newPolicy, e); - } finally { - try { - reader.close(); - writer.close(); - } catch (Exception ignore) { - } - } - config.setDynamicParameter("testPolicyfile", - newPolicy.getAbsolutePath()); - logger.log(Level.FINEST, - "policy found, set to " + newPolicy.getAbsolutePath()); - } - } - - /** - * Retrieve a value from the configuration by searching - * for the given <code>key</code> and return the value - * parsed into a <code>String</code> array. The tokens - * in the value may be separated by space, tab, or ','. - * If the configuration value does not exist or contains - * no tokens, an empty string is returned. - * - * @param key the name of the configuration value - * - * @return a string array containing the set of tokens - * parsed from the configuration value, or an - * empty string - */ - private String[] getConfigStrings(String key) { - String[] stringArray = new String[0]; - String s = config.getStringConfigVal(key, null); - if (s != null) { - stringArray = config.parseString(s, ", \t"); - } - return stringArray; - } - - /** - * Returns the codebase for the test. The codebase may be tagged - * by the parameter name <code>testCodebase</code>. - * If this value is not defined, <code>null</code> - * is returned (inhibiting any codebase annotation). Tests run - * by <code>MainWrapper</code> will typically not set a - * codebase annotation. - * - * @return the codebase for the test - */ - public String getCodebase() { - String codebase = config.getStringConfigVal("testCodebase", null); - logger.log(Level.FINEST, "using codebase: " + codebase); - return codebase; - } - - /** - * Return the wrapper class name for executing a test in another VM. - * The value returned is that obtained by searching the configuration - * for the key <code>testWrapper</code>. If this key is not found, - * the default value <code>com.sun.jini.qa.harness.MainWrapper</code> is - * returned. One special value is recognized: - * <table> - * <tr> - * <td>mainwrapper - * <td>if the search for <code>testWrapper</code> returns this - * value (case insensitive), then this method will return - * <code>com.sun.jini.qa.harness.MainWrapper</code> - * </table> - * If the search for <code>testWrapper</code> returns any other - * value, then that value is returned by this method. Note that - * this test descriptor performs command-line setup specific - * to <code>MainWrapper</code>; it is unlikely that specifying - * a different wrapper would result in correct behavior. - * - * @return the class name of the test wrapper - */ - public String getWrapperClassName() { - return config.getStringConfigVal("testWrapper", - "com.sun.jini.qa.harness.MainWrapper"); - } - - /** - * Parse the configuration tags contained in a java source file. - * The supported syntax is quite rigid: - * <ul> - * <li>The tags must reside in the first comment block in the code - * <li>The comment block must comprise the first non-blank text - * in the code - * <li>The comment block must begin with the '\/\*' sequence and - * any tags on this initial line will be ignored - * <li>The tag '@test' must be the first tag in the comment block - * <li>Tags may be of the form: - * <table> - * <tr> - * <td>@key - * <td>key value is implicitly <code>true</code> - * <tr> - * <td>@key value1 value2 ... - * <td>values are separated by white space and may not - * contain white space - * <tr> - * <td>@key=value - * <td>value may contain any character, including '='. White - * space in value is retained - * </table> - * <li>The @library and @build tags may occur multiple times. - * The tag values are - * appended in the order read - * <li>If the <code>run</code> tag is encountered, the <code>run</code> - * options are processed. There is no action implied by the run - * tag. The test class returned by <code>getTestClassName</code> - * is always run as the test, regardless of the existance or content - * of the <code>run</code> tag. - * <li>The comment block must be terminated with the '\*\/' sequence - * </ul> - * The internal properties object is updated to reflect tag values. - * Also, values for <code>testClass</code> and <code>testWrapper</code> - * are generated on the assumption that the test name is also the test - * class to be executed and is to be executed using the - * <code>MainWrapper</code> - * - * @param source the name of the java source file, which may be - * relative to the kit installation directory - * - * @throws TestException if the <code>@test</code> tag is missing or the - * tag comment block is malformed - */ - private void parseTags(String source) throws TestException { - logger.log(Level.FINEST, "parseTags source: " + source); - String installDir = config.getKitHomeDir(); - String absName = config.relativeToAbsolutePath(installDir, source); - String baseName = source.substring(0, source.lastIndexOf(".java")); - baseName = baseName.replace('\\', '/'); - baseName = baseName.substring(baseName.lastIndexOf('/') + 1); - setProperty("testClass", baseName); - setProperty("testWrapper", "com.sun.jini.qa.harness.MainWrapper"); - try { - String line; - boolean gotTestTag = false; - BufferedReader r = new BufferedReader(new FileReader(absName)); - while ((line = r.readLine()) != null) { - StringTokenizer tok = new StringTokenizer(line); - if (! tok.hasMoreTokens()) { - continue; // skip blanks lines - } - String token = tok.nextToken(); - if (! token.startsWith("/*")) { - throw new TestException("Tags comment block required"); - } - if (line.indexOf("@test") >= 0) { - gotTestTag = true; - } - break; // leading comment delimiter found - } - if (line == null) { - return; //no comments in file - } - if (!gotTestTag) { - /* find @test in a comment */ - while ((line = r.readLine()) != null) { - if (line.indexOf("*/") >= 0) { - throw new TestException("Missing @test tag"); - } - if (line.indexOf("@test") >= 0) { - break; // found @test - } - } - } - if (line == null) { - throw new TestException("EOF before end of tag block"); - } - boolean doingRun = false; - while ((line = r.readLine()) != null) { - if (line.indexOf('@') >= 0) { - String rest = line.substring(line.indexOf('@') + 1); - if (rest.startsWith(" ")) { - throw new TestException("white space follows '@' in tag"); - } - String key = null; - String value = ""; // last else relies on this init - int firstEq = rest.indexOf("="); - int firstSp = rest.indexOf(" "); - if (firstEq >= 0 && (firstSp < 0 || (firstEq < firstSp))) { - key = rest.substring(0,rest.indexOf('=')); - value = rest.substring(rest.indexOf('=') +1); - } else { - StringTokenizer tok = new StringTokenizer(rest); - if (tok.countTokens() <= 0) { - continue; // ignore solo '@' - } else if (tok.countTokens() == 1) { - key = tok.nextToken(); - value = "true"; - } else { - key = tok.nextToken(); - value = tok.nextToken(); - while (tok.hasMoreTokens()) { - value += " " + tok.nextToken(); - } - } - } - if (key.equals("run")) { - doingRun = true; - processRunOptions(value, source); - } else { - doingRun = false; - } - if (key.equals("library") || key.equals("build")) { - String oldValue = getProperty(key); - if (oldValue != null) { - value = oldValue + " " + value; - } - } - setProperty(key, value); - } else if (doingRun) { - while (line.startsWith(" ") - || line.startsWith("\t") - || line.startsWith("* ") - || line.startsWith("*\t")) { - line = line.substring(1); - } - if (line.startsWith("-")) { - processRunOptions("main " + line, source); //XXX ugly - } - } //XXX assumes main, assumes run is last - if (line.indexOf("*/") >= 0) { - break; // end of comment block - } - } - if (line == null) { - throw new TestException("EOF before end of tag block"); - } - // always add the implied build target - String buildList = getProperty("build"); - if (buildList == null) { - buildList = baseName; - } else { - if (buildList.indexOf(baseName) < 0) { - buildList += " " + baseName; - } - } - setProperty("build", buildList); - return; - } catch (IOException e) { - throw new TestException("problem parsing tags", e); - } - } - - /** - * Parse the options following the <code>@run</code> tag. The - * only option recognized is the <code>/policy=</code> option, - * which must refer to a security policy file in the test source - * directory. In addition, any VM options specified in the tag - * will be added to the set current set of VM options. A VM option - * is any token with a leading '-'. A valid - * tag might look like: - * <pre> - * @run main/policy=policy.file/othervm -Dfoo=bar -Da=b testname - * <pre> - * In this example, the security policy used for the test VM will - * be <code>policy.file</code> located in the test source directory. - * The tokens <code>main</code> and <code>othervm</code> are - * ignored but legal. The property definitions <code>-Dfoo=bar</code> - * and -Da=b will be applied to the test VM. The final token, - * <code>testname</code> is ignored. - * - * @throw TestException if the <code>main</code> action flag is missing - */ - private void processRunOptions(String options, String testName) - throws TestException - { - StringTokenizer optionsTok = new StringTokenizer(options); - if (optionsTok.hasMoreTokens()) { - String actions = optionsTok.nextToken(); - StringTokenizer actionsTok = new StringTokenizer(actions, "/"); - String actionString = actionsTok.nextToken(); - if (!actionString.equals("main")) { - throw new TestException("run tag requires main action"); - } - while (actionsTok.hasMoreTokens()) { - String actOption = actionsTok.nextToken(); - logger.log(Level.FINEST, "action option: " + actOption); - if (actOption.startsWith("policy=")) { - policyTag = actOption.substring("policy=".length()); - } - } - String newOptions = null; - while (optionsTok.hasMoreTokens()) { - String vmOption = optionsTok.nextToken(); - if (!vmOption.startsWith("-")) { // stop on first non-option - break; - } - if (newOptions == null) { - newOptions = vmOption; - } else { - newOptions += " " + vmOption; - } - } - if (newOptions != null) { - String optionArgs = getProperty("testjvmargs"); - if (optionArgs == null) { - optionArgs = newOptions; - } else { - optionArgs += " " + newOptions; - } - setProperty("testjvmargs", optionArgs); - } - } - } - - /** - * Builds all of the classes identified by the build tag. Places - * class files in the class directories relative to where the - * corresponding source files are found in the source file tree. - * - * @throws TestException if expected files/directories do not exist - */ - private void buildTest() throws TestException { - initDirectories(); - String cp = getClasspath(); - String sp = getSourcepath(); - String[] buildList = getConfigStrings("build"); - for (int i = 0; i < buildList.length; i++) { - File sourceFile = getSourceFile(buildList[i]); - if (compileNeeded(buildList[i], sourceFile)) { - File sourceDir = getCompilationDirectory(buildList[i]); - String sourceName = buildList[i].replace('.', '/'); - logger.log(Level.FINEST, "compilation working directory is " - + sourceDir); - String cmdLine = "javac" - + " -classpath " + cp - + " -sourcepath " + sp - + " -d " + testClassDir.getAbsolutePath() - + " " + sourceName + ".java"; - logger.log(Level.FINEST, "compile cmdline: " + cmdLine); - try { - Process p = Runtime.getRuntime().exec(cmdLine, - null, - sourceDir); - logger.log(Level.FINEST, "compile started"); - p.waitFor(); - logger.log(Level.FINEST, "compile finished"); - } catch (InterruptedException ignore) { - } catch (IOException e) { - throw new TestException("Exception running compiler", e); - } - File testClassFile = new File(testClassDir, - sourceName + ".class"); - if (!testClassFile.exists()) { - throw new TestException("Failed to generate " - + " class file from" - + buildList[i]); - } - } - } - } - - /** - * Returns the directory path containing the source file - * identified by <code>targetName</code>. The returned name is - * expressed as a path relative to the test source - * directory. The possible set of return values includes - * the components of the <code>@library</code> tag, or ".", - * signifying the test source directory. - * - * @param targetName the name token identifying the compilation - * target (no <code>.java</code> or <code>.class</code> - * extension is included). - * @return the source directory path for the target - * @throws TestException if expected files/directories do not exist - */ - private File getSourceFile(String targetName) throws TestException { - if (scratchDir == null) { - throw new IllegalStateException("must call initDirectories first"); - } - targetName = targetName.replace('.','/'); - File target = new File(testSourceDir, targetName + ".java"); - if (target.exists()) { - return target; - } - String[] libStrings = getConfigStrings("library"); - for (int i = 0; i < libStrings.length; i++) { - File dir = new File(testSourceDir, libStrings[i]); - if (!dir.exists()) { - throw new TestException("library directory " - + dir - + " does not exist"); - } - target = new File(dir, targetName + ".java"); - if (target.exists()) { - return target; - } - } - throw new TestException("could not find source file " - + targetName + ".java"); - } - - /** - * Get the compilation directory for a source file. Converts any - * package identifiers to path separators. - * - * @param targetName the compilation target specified in package - * notation - * @return the directory in which the target source file is expected - * to reside. - * - * @throws TestException if the source file could not be found - */ - private File getCompilationDirectory(String targetName) - throws TestException - { - if (scratchDir == null) { - throw new IllegalStateException("must call initDirectories first"); - } - targetName = targetName.replace('.','/'); - File target = new File(testSourceDir, targetName + ".java"); - if (target.exists()) { - return testSourceDir; - } - String[] libStrings = getConfigStrings("library"); - for (int i = 0; i < libStrings.length; i++) { - File dir = new File(testSourceDir, libStrings[i]); - if (!dir.exists()) { - throw new TestException("library directory " - + dir - + " does not exist"); - } - target = new File(dir, targetName + ".java"); - if (target.exists()) { - return dir; - } - } - throw new TestException("could not find source file " - + targetName + ".java"); - } - - /** - * Determines whether a source file must be compiled. Compares the - * timestamp of a source file with it's correspondingly named class - * file, and returns <code>true</code> if the source is newer. All - * class files are assumed to be located in the directory referenced - * by the <code>testClassDir</code> attribute. - * - * @param targetName the name of the compilation target expressed - * as a simple name token (that is <code>Foo</code> - * rather than <code>Foo.java</code> or - * <code>Foo.class</code> - * @param sourceFile the source file, which is assumed to exist - * - * @return <code>true</code> if the target class file does not exist or - * is older than the source file - * - * @throws TestException if the directory names cannot be determined - * from the system properties, or if the source - * file cannot be found - */ - private boolean compileNeeded(String targetName, File sourceFile) - throws TestException - { - if (scratchDir == null) { - throw new IllegalStateException("must call initDirectories first"); - } - targetName = targetName.replace('.', '/'); - File target = new File(testClassDir, targetName + ".class"); - if (!target.exists()) { - logger.log(Level.FINEST, "Target class " + target + " not found"); - return true; - } - boolean needsUpdate = target.lastModified() < sourceFile.lastModified(); - if (needsUpdate) { - logger.log(Level.FINEST, "Target class " + target + " out of date"); - } else { - logger.log(Level.FINEST, "Target class " + target + " is current"); - } - return needsUpdate; - } - - /** - * Returns the sourcepath for compilation. - * The sourcepath consists of the the directory containing the - * test sources, followed by - * components of the library tag resolved - * relative to the test source directory. - * - * @return the sourcepath string - */ - private String getSourcepath() { - String srcpath = testSourceDir.toString(); - String[] libString = getConfigStrings("library"); - for (int i = 0; i < libString.length; i++) { - srcpath += File.pathSeparator; - File f = new File(testSourceDir, libString[i]); - srcpath += f.getAbsolutePath(); - } - return srcpath; - } - - /** - * Get the test arguments. This method returns a test argument - * list formated for <code>MainWrapper</code>, which requires - * the name of the test, the name of the test class, and the - * set of test arguments retrieved from the configuration bound - * to the key <code>testArgs</code>. - * - * @return the complete VM argument list, which is never null - */ - public String[] getTestArgs() { - String argStrings = config.getStringConfigVal("testArgs", ""); - String[] testArgs = config.parseString(argStrings); - if (testArgs == null) { - testArgs = new String[0]; - } - String[] args = new String[testArgs.length + 2]; - args[0] = getName(); - args[1] = getTestClassName(); - for (int i = 0; i < testArgs.length; i++) { - args[i + 2] = testArgs[i]; - } - return args; - } - - /** - * Return the working directory for test execution. For MainWrapper - * tests, the working directory is the scratch directory. If the - * scratch directory has not been created, an - * <code>IllegalStateException</code> is thrown. - * - * @return the scratch directory - */ - public File getWorkingDir() { - if (scratchDir == null) { - throw new IllegalStateException("must call initDirectories first"); - } - return scratchDir; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.jini.qa.harness; + +// java.util +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.ArrayList; + +// java.util.logging +import java.util.logging.Logger; +import java.util.logging.Level; + +// java.io +import java.io.BufferedReader; +import java.io.File; +import java.io.Reader; +import java.io.Writer; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +/** + * A <code>TestDescription</code> which represents tests that are + * implemented as source code which is compiled automatically and + * executed by calling the main method of the compiled class. + * Tags + * must be provided in the first comment block of the test source. + * The supported syntax is quite rigid: + * <ul> + * <li>The tags must reside in the first comment block in the code + * <li>The comment block must comprise the first non-blank text + * in the code + * <li>The comment block must begin with the '\/\*' sequence and + * any tags on this initial line will be ignored + * <li>The tag '@test' must be the first tag in the comment block + * <li>Tags may be of the form: + * <table> + * <tr> + * <td>@key + * <td>key value is implicitly <code>true</code> + * <tr> + * <td>@key value1 value2 ... + * <td>values are separated by white space and may not + * contain white space + * <tr> + * <td>@key=value + * <td>value may contain any character, including '='. White + * space in value is retained + * </table> + * <li>The @library and @build tags may occur multiple times. + * The tag values are + * appended in the order read + * <li>If the <code>run</code> tag is encountered, the <code>run</code> + * options are processed. There is no action implied by the run + * tag. The test class returned by <code>getTestClassName</code> + * is always run as the test, regardless of the existance or content + * of the <code>run</code> tag. + * <li>The comment block must be terminated with the '\*\/' sequence + * </ul> + * The following tags are defined: + * <table> + * <tr> + * <td>@test + * <td>A mandatory tag which identifies this as a valid test description + * <tr> + * <td>@library + * <td>identifies libraries of source files which may be accessed by the + * test. The library directory must be specified relative to the location of the + * test source file containing the @library tag. Multiple entries per line, and + * multiple occurances of the @library tag, are allowed. + * <tr> + * <td>@build + * <td>identifies the names of source files to compile (the .java extension is + * omitted. All library files to be compiled must be named in @build tags. It is + * legal, but unnecessary, to include the name of the test source file to + * compile. However, if other source files in the test directory must also be + * compiled, those names should also be specified in the build tag. Multiple + * entries per line, and multiple occurances of the @build tag, are allowed. + * <tr> + * <td>@run main/policy=policyfile vmarg1 vmarg2 ... + * <td>This tag defines the execution environment of the test. The main token + * defines this as a main-style test, and may be omitted. If /policy= is + * defined, the named policy file is resolved relative to the test source + * directory. Any remaining tokens which begin with a '-' character are included + * in the VM options when the test VM is invoked. + * </table> + * Any other tags are interpreted as configuration value definitions. + */ +public class MainTestDescription extends TestDescription { + + /** the config object */ + private QAConfig config; + + /** The scratch directory */ + private File scratchDir = null; + + /** the directory containing the test source */ + private File testSourceDir = null; + + /** the directory containing the test classes */ + private File testClassDir = null; + + /** the policy file specified by the /policy= modifier */ + //XXX this field must not be explicitly set. It is set by + // a method called by the parent class constructor. If it + // is set here, the value set by the parent class constructor + // is overwritten. This is very bad. Rework this. + private String policyTag; + + /** the logger */ + private static final Logger logger = + Logger.getLogger("com.sun.jini.qa.harness"); + + /** + * Construct a test description for a test with the given <code>name</code>. + * The name is assumed to represent the name of a source file relative + * to the installation directory of the test harness. + * The internal properties object is populated by parsing a set of + * tags provided at the beginning of the test source file. + * + * @param name the name of the test + * @param config the config object for this test + * + * @throws TestException if mandatory tags are missing or well-known + * tags are malformed + */ + public MainTestDescription(String name, Properties p, QAConfig config) + throws TestException + { + super(name, p, config); + this.config = config; + } + + /** + * Overridden method which causes the constructor to initialize the + * internal properties object from tags in the source file. + * + * @throws TestException if mandatory tags are missing or well-known + * tags are malformed + */ + protected void initProperties() throws TestException { + parseTags(getName()); + } + + /** + * Indicates whether some other object is equal to this one. + * <code>obj</code> is considered equal to this object if it + * is an instance of <code>TestDescription</code> and has a + * name which is identical to this name. + * + * @param obj the object to test for equality + * @return true if <code>obj</code> is equal to this object + */ + public boolean equals(Object obj) { + if (!(obj instanceof MainTestDescription)) { + return false; + } + return ((MainTestDescription) obj).getName().equals(getName()); + } + + /** + * Return a hashcode value for the object. + * + * @return the hashcode + */ + public int hashCode() { + return getName().hashCode(); + } + + /** + * Return a string representation of this object. + * + * @return a string representing this object + */ + public String toString() { + return "MainTestDescription[" + getName() + "]"; + } + + + /** + * Returns the classpath for compilation and test execution. + * The classpath consists of the JAR file containing + * <code>MainWrapper</code> followed by the directory containing the + * test classes, followed by the directory containing the + * test sources. + * + * @return the classpath string + */ + public String getClasspath() { + if (scratchDir == null) { + throw new IllegalStateException("must call initDirectories first"); + } + String classpath = config.getKitHomeDir() + + File.separator + + "lib" + + File.separator + + "qa1-mainwrapper.jar"; + classpath += File.pathSeparator; + classpath += testClassDir.getAbsolutePath(); + classpath += File.pathSeparator; + classpath += testSourceDir.getAbsolutePath(); + return classpath; + } + + /** + * Augments the arguments returned by the superclass call with + * property definition strings for the properties: + * <ul> + * <li><code>test.src</code> + * <li><code>test.classes</code> + * </ul> + * + * @return the array of property definitions + */ + public String[] getJVMArgs() { + if (scratchDir == null) { + throw new IllegalStateException("must call initDirectories first"); + } + ArrayList l = new ArrayList(10); + String[] superStrings = super.getJVMArgs(); // typically null + if (superStrings != null) { + for (int i = 0; i < superStrings.length; i++) { + l.add(superStrings[i]); + } + } + l.add("-Dtest.src=" + testSourceDir.getAbsolutePath()); + l.add("-Dtest.classes=" + testClassDir.getAbsolutePath()); + if (getPolicyFile() != null) { + l.add("-Djava.security.manager=default"); + } + l.add("-Dcom.sun.jini.qa.home=" + config.getKitHomeDir()); + return (String[]) l.toArray(new String[l.size()]); + } + + + /** + * Get the policy file for the test VM. If the policy is not + * specified by the configuration, then <code>null</code> is + * returned to indicate no policy is to be used + * + * @return the policy file to use, or <code>null</code> + */ + public String getPolicyFile() { + String policyFile = config.getStringConfigVal("testPolicyfile", null); + if (policyFile != null) { + String installDir = config.getKitHomeDir(); + policyFile = config.relativeToAbsolutePath(installDir, policyFile); + } + return policyFile; + } + + /** + * Returns a default category name if no categories have been + * defined for the test. This is special behavior for main tests + * to retain compatibility with jtreg tagged source files. + * + * @return a single category, <code>default</code>, if no categories + * are defined for the test + */ + public String[] getCategories() { + String[] cats = super.getCategories(); + if (cats.length == 0) { + cats = new String[]{"default"}; + } + return cats; + } + + /** + * Get the command line for the test VM. Before the command + * line is constructed, any required test directories are + * created and source files are compiled. + * + * @throws TestException if a failure occurs setting up the + * test environment or compiling the sources. + */ + public String[] getCommandLine() throws TestException { + buildTest(); + return super.getCommandLine(null); + } + + /** + * Creates the working directories for the test, and sets the values + * for: + * <table> + * <tr><td>scratchDir<td>a scratch directory for misc test files + * <tr><td>testClassDir<td>the directory where the test classes are placed + * <tr><td>testSourceDir<td>the directory where the test sources are located + * </table> + * In addition, if <code>policyTag</code> is non-null, then a new security + * policy is generated in the scratch directory and the + * <code>testPolicyfile</code> configuration value is set to point to it. + * + * @throws TestException if the source directory is missing, if any + * of the target directories cannot be created, + * or if an I/OO error occurs creating the policy file + */ + private void initDirectories() throws TestException { + testSourceDir = + new File(config.getKitHomeDir(), getName()).getParentFile(); + if (!testSourceDir.exists()) { + throw new TestException("source directory " + + testSourceDir + + " missing"); + } + File baseDir = new File("maintests"); + if (!baseDir.exists()) { + if (!baseDir.mkdir()) { + throw new TestException("Can't make maintests directory"); + } + } + File classesRoot = new File(baseDir, "classes"); + if (!classesRoot.exists()) { + if (!classesRoot.mkdir()) { + throw new TestException("Can't make classes directory"); + } + } + testClassDir = new File(classesRoot, getName()).getParentFile(); + if (!testClassDir.exists()) { + if (!testClassDir.mkdirs()) { + throw new TestException("Can't make classes directory " + + testClassDir); + } + } + scratchDir = new File(baseDir, "scratch"); + if (!scratchDir.exists()) { + if (!scratchDir.mkdir()) { + throw new TestException("Can't make scratch directory"); + } + } + if (policyTag != null) { + File origPolicy = new File(testSourceDir, policyTag); + File newPolicy = null; + FileReader reader = null; + FileWriter writer = null; + try { + newPolicy = new File(scratchDir, policyTag + "_new"); + String sep = System.getProperty("line.separator"); + reader = new FileReader(origPolicy); + writer = new FileWriter(newPolicy); + writer.write("// grant added by harness" + sep); + writer.write("grant codebase \"file:${com.sun.jini.qa.home}${/}lib${/}qa1-mainwrapper.jar\" {" + sep); + writer.write(" permission java.security.AllPermission;" + sep); + writer.write("};" + sep); + writer.write(sep); + writer.write("grant {" + sep); + writer.write(" permission java.io.FilePermission \"" + + classesRoot.getAbsolutePath() + + "${/}-\", \"read\";" + sep); + writer.write("};" + sep); + writer.write(sep); + writer.write("// original policy file:" + sep); + writer.write("// " + origPolicy + sep); + int c; + while ((c = reader.read()) >= 0) { + writer.write(c); + } + } catch (IOException e) { + e.printStackTrace(); + throw new TestException("Error creating policy file" + + newPolicy, e); + } finally { + try { + reader.close(); + writer.close(); + } catch (Exception ignore) { + } + } + config.setDynamicParameter("testPolicyfile", + newPolicy.getAbsolutePath()); + logger.log(Level.FINEST, + "policy found, set to " + newPolicy.getAbsolutePath()); + } + } + + /** + * Retrieve a value from the configuration by searching + * for the given <code>key</code> and return the value + * parsed into a <code>String</code> array. The tokens + * in the value may be separated by space, tab, or ','. + * If the configuration value does not exist or contains + * no tokens, an empty string is returned. + * + * @param key the name of the configuration value + * + * @return a string array containing the set of tokens + * parsed from the configuration value, or an + * empty string + */ + private String[] getConfigStrings(String key) { + String[] stringArray = new String[0]; + String s = config.getStringConfigVal(key, null); + if (s != null) { + stringArray = config.parseString(s, ", \t"); + } + return stringArray; + } + + /** + * Returns the codebase for the test. The codebase may be tagged + * by the parameter name <code>testCodebase</code>. + * If this value is not defined, <code>null</code> + * is returned (inhibiting any codebase annotation). Tests run + * by <code>MainWrapper</code> will typically not set a + * codebase annotation. + * + * @return the codebase for the test + */ + public String getCodebase() { + String codebase = config.getStringConfigVal("testCodebase", null); + logger.log(Level.FINEST, "using codebase: " + codebase); + return codebase; + } + + /** + * Return the wrapper class name for executing a test in another VM. + * The value returned is that obtained by searching the configuration + * for the key <code>testWrapper</code>. If this key is not found, + * the default value <code>com.sun.jini.qa.harness.MainWrapper</code> is + * returned. One special value is recognized: + * <table> + * <tr> + * <td>mainwrapper + * <td>if the search for <code>testWrapper</code> returns this + * value (case insensitive), then this method will return + * <code>com.sun.jini.qa.harness.MainWrapper</code> + * </table> + * If the search for <code>testWrapper</code> returns any other + * value, then that value is returned by this method. Note that + * this test descriptor performs command-line setup specific + * to <code>MainWrapper</code>; it is unlikely that specifying + * a different wrapper would result in correct behavior. + * + * @return the class name of the test wrapper + */ + public String getWrapperClassName() { + return config.getStringConfigVal("testWrapper", + "com.sun.jini.qa.harness.MainWrapper"); + } + + /** + * Parse the configuration tags contained in a java source file. + * The supported syntax is quite rigid: + * <ul> + * <li>The tags must reside in the first comment block in the code + * <li>The comment block must comprise the first non-blank text + * in the code + * <li>The comment block must begin with the '\/\*' sequence and + * any tags on this initial line will be ignored + * <li>The tag '@test' must be the first tag in the comment block + * <li>Tags may be of the form: + * <table> + * <tr> + * <td>@key + * <td>key value is implicitly <code>true</code> + * <tr> + * <td>@key value1 value2 ... + * <td>values are separated by white space and may not + * contain white space + * <tr> + * <td>@key=value + * <td>value may contain any character, including '='. White + * space in value is retained + * </table> + * <li>The @library and @build tags may occur multiple times. + * The tag values are + * appended in the order read + * <li>If the <code>run</code> tag is encountered, the <code>run</code> + * options are processed. There is no action implied by the run + * tag. The test class returned by <code>getTestClassName</code> + * is always run as the test, regardless of the existance or content + * of the <code>run</code> tag. + * <li>The comment block must be terminated with the '\*\/' sequence + * </ul> + * The internal properties object is updated to reflect tag values. + * Also, values for <code>testClass</code> and <code>testWrapper</code> + * are generated on the assumption that the test name is also the test + * class to be executed and is to be executed using the + * <code>MainWrapper</code> + * + * @param source the name of the java source file, which may be + * relative to the kit installation directory + * + * @throws TestException if the <code>@test</code> tag is missing or the + * tag comment block is malformed + */ + private void parseTags(String source) throws TestException { + logger.log(Level.FINEST, "parseTags source: " + source); + String installDir = config.getKitHomeDir(); + String absName = config.relativeToAbsolutePath(installDir, source); + String baseName = source.substring(0, source.lastIndexOf(".java")); + baseName = baseName.replace('\\', '/'); + baseName = baseName.substring(baseName.lastIndexOf('/') + 1); + setProperty("testClass", baseName); + setProperty("testWrapper", "com.sun.jini.qa.harness.MainWrapper"); + try { + String line; + boolean gotTestTag = false; + BufferedReader r = new BufferedReader(new FileReader(absName)); + while ((line = r.readLine()) != null) { + StringTokenizer tok = new StringTokenizer(line); + if (! tok.hasMoreTokens()) { + continue; // skip blanks lines + } + String token = tok.nextToken(); + if (! token.startsWith("/*")) { + throw new TestException("Tags comment block required"); + } + if (line.indexOf("@test") >= 0) { + gotTestTag = true; + } + break; // leading comment delimiter found + } + if (line == null) { + return; //no comments in file + } + if (!gotTestTag) { + /* find @test in a comment */ + while ((line = r.readLine()) != null) { + if (line.indexOf("*/") >= 0) { + throw new TestException("Missing @test tag"); + } + if (line.indexOf("@test") >= 0) { + break; // found @test + } + } + } + if (line == null) { + throw new TestException("EOF before end of tag block"); + } + boolean doingRun = false; + while ((line = r.readLine()) != null) { + if (line.indexOf('@') >= 0) { + String rest = line.substring(line.indexOf('@') + 1); + if (rest.startsWith(" ")) { + throw new TestException("white space follows '@' in tag"); + } + String key = null; + String value = ""; // last else relies on this init + int firstEq = rest.indexOf("="); + int firstSp = rest.indexOf(" "); + if (firstEq >= 0 && (firstSp < 0 || (firstEq < firstSp))) { + key = rest.substring(0,rest.indexOf('=')); + value = rest.substring(rest.indexOf('=') +1); + } else { + StringTokenizer tok = new StringTokenizer(rest); + if (tok.countTokens() <= 0) { + continue; // ignore solo '@' + } else if (tok.countTokens() == 1) { + key = tok.nextToken(); + value = "true"; + } else { + key = tok.nextToken(); + value = tok.nextToken(); + while (tok.hasMoreTokens()) { + value += " " + tok.nextToken(); + } + } + } + if (key.equals("run")) { + doingRun = true; + processRunOptions(value, source); + } else { + doingRun = false; + } + if (key.equals("library") || key.equals("build")) { + String oldValue = getProperty(key); + if (oldValue != null) { + value = oldValue + " " + value; + } + } + setProperty(key, value); + } else if (doingRun) { + while (line.startsWith(" ") + || line.startsWith("\t") + || line.startsWith("* ") + || line.startsWith("*\t")) { + line = line.substring(1); + } + if (line.startsWith("-")) { + processRunOptions("main " + line, source); //XXX ugly + } + } //XXX assumes main, assumes run is last + if (line.indexOf("*/") >= 0) { + break; // end of comment block + } + } + if (line == null) { + throw new TestException("EOF before end of tag block"); + } + // always add the implied build target + String buildList = getProperty("build"); + if (buildList == null) { + buildList = baseName; + } else { + if (buildList.indexOf(baseName) < 0) { + buildList += " " + baseName; + } + } + setProperty("build", buildList); + return; + } catch (IOException e) { + throw new TestException("problem parsing tags", e); + } + } + + /** + * Parse the options following the <code>@run</code> tag. The + * only option recognized is the <code>/policy=</code> option, + * which must refer to a security policy file in the test source + * directory. In addition, any VM options specified in the tag + * will be added to the set current set of VM options. A VM option + * is any token with a leading '-'. A valid + * tag might look like: + * <pre> + * @run main/policy=policy.file/othervm -Dfoo=bar -Da=b testname + * <pre> + * In this example, the security policy used for the test VM will + * be <code>policy.file</code> located in the test source directory. + * The tokens <code>main</code> and <code>othervm</code> are + * ignored but legal. The property definitions <code>-Dfoo=bar</code> + * and -Da=b will be applied to the test VM. The final token, + * <code>testname</code> is ignored. + * + * @throw TestException if the <code>main</code> action flag is missing + */ + private void processRunOptions(String options, String testName) + throws TestException + { + StringTokenizer optionsTok = new StringTokenizer(options); + if (optionsTok.hasMoreTokens()) { + String actions = optionsTok.nextToken(); + StringTokenizer actionsTok = new StringTokenizer(actions, "/"); + String actionString = actionsTok.nextToken(); + if (!actionString.equals("main")) { + throw new TestException("run tag requires main action"); + } + while (actionsTok.hasMoreTokens()) { + String actOption = actionsTok.nextToken(); + logger.log(Level.FINEST, "action option: " + actOption); + if (actOption.startsWith("policy=")) { + policyTag = actOption.substring("policy=".length()); + } + } + String newOptions = null; + while (optionsTok.hasMoreTokens()) { + String vmOption = optionsTok.nextToken(); + if (!vmOption.startsWith("-")) { // stop on first non-option + break; + } + if (newOptions == null) { + newOptions = vmOption; + } else { + newOptions += " " + vmOption; + } + } + if (newOptions != null) { + String optionArgs = getProperty("testjvmargs"); + if (optionArgs == null) { + optionArgs = newOptions; + } else { + optionArgs += " " + newOptions; + } + setProperty("testjvmargs", optionArgs); + } + } + } + + /** + * Builds all of the classes identified by the build tag. Places + * class files in the class directories relative to where the + * corresponding source files are found in the source file tree. + * + * @throws TestException if expected files/directories do not exist + */ + private void buildTest() throws TestException { + initDirectories(); + String cp = getClasspath(); + String sp = getSourcepath(); + String[] buildList = getConfigStrings("build"); + for (int i = 0; i < buildList.length; i++) { + File sourceFile = getSourceFile(buildList[i]); + if (compileNeeded(buildList[i], sourceFile)) { + File sourceDir = getCompilationDirectory(buildList[i]); + String sourceName = buildList[i].replace('.', '/'); + logger.log(Level.FINEST, "compilation working directory is " + + sourceDir); + String cmdLine = "javac" + + " -classpath " + cp + + " -sourcepath " + sp + + " -d " + testClassDir.getAbsolutePath() + + " " + sourceName + ".java"; + logger.log(Level.FINEST, "compile cmdline: " + cmdLine); + try { + Process p = Runtime.getRuntime().exec(cmdLine, + null, + sourceDir); + logger.log(Level.FINEST, "compile started"); + p.waitFor(); + logger.log(Level.FINEST, "compile finished"); + } catch (InterruptedException ignore) { + Thread.currentThread().interrupt(); + } catch (IOException e) { + throw new TestException("Exception running compiler", e); + } + File testClassFile = new File(testClassDir, + sourceName + ".class"); + if (!testClassFile.exists()) { + throw new TestException("Failed to generate " + + " class file from" + + buildList[i]); + } + } + } + } + + /** + * Returns the directory path containing the source file + * identified by <code>targetName</code>. The returned name is + * expressed as a path relative to the test source + * directory. The possible set of return values includes + * the components of the <code>@library</code> tag, or ".", + * signifying the test source directory. + * + * @param targetName the name token identifying the compilation + * target (no <code>.java</code> or <code>.class</code> + * extension is included). + * @return the source directory path for the target + * @throws TestException if expected files/directories do not exist + */ + private File getSourceFile(String targetName) throws TestException { + if (scratchDir == null) { + throw new IllegalStateException("must call initDirectories first"); + } + targetName = targetName.replace('.','/'); + File target = new File(testSourceDir, targetName + ".java"); + if (target.exists()) { + return target; + } + String[] libStrings = getConfigStrings("library"); + for (int i = 0; i < libStrings.length; i++) { + File dir = new File(testSourceDir, libStrings[i]); + if (!dir.exists()) { + throw new TestException("library directory " + + dir + + " does not exist"); + } + target = new File(dir, targetName + ".java"); + if (target.exists()) { + return target; + } + } + throw new TestException("could not find source file " + + targetName + ".java"); + } + + /** + * Get the compilation directory for a source file. Converts any + * package identifiers to path separators. + * + * @param targetName the compilation target specified in package + * notation + * @return the directory in which the target source file is expected + * to reside. + * + * @throws TestException if the source file could not be found + */ + private File getCompilationDirectory(String targetName) + throws TestException + { + if (scratchDir == null) { + throw new IllegalStateException("must call initDirectories first"); + } + targetName = targetName.replace('.','/'); + File target = new File(testSourceDir, targetName + ".java"); + if (target.exists()) { + return testSourceDir; + } + String[] libStrings = getConfigStrings("library"); + for (int i = 0; i < libStrings.length; i++) { + File dir = new File(testSourceDir, libStrings[i]); + if (!dir.exists()) { + throw new TestException("library directory " + + dir + + " does not exist"); + } + target = new File(dir, targetName + ".java"); + if (target.exists()) { + return dir; + } + } + throw new TestException("could not find source file " + + targetName + ".java"); + } + + /** + * Determines whether a source file must be compiled. Compares the + * timestamp of a source file with it's correspondingly named class + * file, and returns <code>true</code> if the source is newer. All + * class files are assumed to be located in the directory referenced + * by the <code>testClassDir</code> attribute. + * + * @param targetName the name of the compilation target expressed + * as a simple name token (that is <code>Foo</code> + * rather than <code>Foo.java</code> or + * <code>Foo.class</code> + * @param sourceFile the source file, which is assumed to exist + * + * @return <code>true</code> if the target class file does not exist or + * is older than the source file + * + * @throws TestException if the directory names cannot be determined + * from the system properties, or if the source + * file cannot be found + */ + private boolean compileNeeded(String targetName, File sourceFile) + throws TestException + { + if (scratchDir == null) { + throw new IllegalStateException("must call initDirectories first"); + } + targetName = targetName.replace('.', '/'); + File target = new File(testClassDir, targetName + ".class"); + if (!target.exists()) { + logger.log(Level.FINEST, "Target class " + target + " not found"); + return true; + } + boolean needsUpdate = target.lastModified() < sourceFile.lastModified(); + if (needsUpdate) { + logger.log(Level.FINEST, "Target class " + target + " out of date"); + } else { + logger.log(Level.FINEST, "Target class " + target + " is current"); + } + return needsUpdate; + } + + /** + * Returns the sourcepath for compilation. + * The sourcepath consists of the the directory containing the + * test sources, followed by + * components of the library tag resolved + * relative to the test source directory. + * + * @return the sourcepath string + */ + private String getSourcepath() { + String srcpath = testSourceDir.toString(); + String[] libString = getConfigStrings("library"); + for (int i = 0; i < libString.length; i++) { + srcpath += File.pathSeparator; + File f = new File(testSourceDir, libString[i]); + srcpath += f.getAbsolutePath(); + } + return srcpath; + } + + /** + * Get the test arguments. This method returns a test argument + * list formated for <code>MainWrapper</code>, which requires + * the name of the test, the name of the test class, and the + * set of test arguments retrieved from the configuration bound + * to the key <code>testArgs</code>. + * + * @return the complete VM argument list, which is never null + */ + public String[] getTestArgs() { + String argStrings = config.getStringConfigVal("testArgs", ""); + String[] testArgs = config.parseString(argStrings); + if (testArgs == null) { + testArgs = new String[0]; + } + String[] args = new String[testArgs.length + 2]; + args[0] = getName(); + args[1] = getTestClassName(); + for (int i = 0; i < testArgs.length; i++) { + args[i + 2] = testArgs[i]; + } + return args; + } + + /** + * Return the working directory for test execution. For MainWrapper + * tests, the working directory is the scratch directory. If the + * scratch directory has not been created, an + * <code>IllegalStateException</code> is thrown. + * + * @return the scratch directory + */ + public File getWorkingDir() { + if (scratchDir == null) { + throw new IllegalStateException("must call initDirectories first"); + } + return scratchDir; + } +}
