river-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From peter_firmst...@apache.org
Subject svn commit: r1634322 [2/41] - in /river/jtsk/skunk/qa_refactor/trunk: qa/src/com/sun/jini/qa/harness/ qa/src/com/sun/jini/test/impl/end2end/e2etest/ qa/src/com/sun/jini/test/impl/joinmanager/ qa/src/com/sun/jini/test/impl/mahalo/ qa/src/com/sun/jini/te...
Date Sun, 26 Oct 2014 13:17:31 GMT
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;
+    }
+}



Mime
View raw message