/* * $Id$ * * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javatest; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.IOException; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.Handler; import java.util.logging.LogRecord; import com.sun.javatest.finder.BinaryTestFinder; import com.sun.javatest.finder.HTMLTestFinder; import com.sun.javatest.finder.TestFinderDecorator; import com.sun.javatest.interview.LegacyParameters; import com.sun.javatest.lib.KeywordScript; import com.sun.javatest.logging.WorkDirLogHandler; import com.sun.javatest.logging.ObservedFile; import com.sun.javatest.services.ServiceManager; import com.sun.javatest.services.ServiceReader; import com.sun.javatest.services.PropertyServiceReader; import com.sun.javatest.util.BackupPolicy; import com.sun.javatest.util.I18NResourceBundle; import com.sun.javatest.util.StringArray; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * A class providing information about and access to the tests in a test suite. * The primary methods to access and run the tests are *
TestSuite.getRoot()
as the value for
* canonRoot. Using this is only desired when disposal of the shared
* TestSuite object is not desired - traditionally, it is not disposed
* and is reused if the test suite is reopened.
* @param canonRoot Canonical root of the test suite.
* @see TestSuite#getRoot
* @return The object which is about to be discarded. Null if it was not
* not cached here.
*/
/*
public static TestSuite close(File canonRoot) {
WeakReference ref = (WeakReference)(dirMap.remove(canonRoot));
if (ref != null) {
TestSuite ts = (TestSuite)(ref.get());
if (ts != null) {
return ts;
}
}
return null;
}
*/
/**
* Create a TestSuite object.
* @param root The root file for this test suite.
* @param tsInfo Test suite properties, typically read from the test suite properties file
* in the root directory of the test suite.
* @param cl A class loader to be used to load additional classes as required,
* typically using a class path defined in the test suite properties file.
* @throws TestSuite.Fault if a problem occurs while creating this test suite.
*/
public TestSuite(File root, Maptests
property in the test suite properties file.
* If this entry is found, it must either identify an absolute filename, or
* a directory relative to the test suite root directory, using '/' to
* separate the components of the path.
* /tests/testsuite.html
exists,
* the result is the directory root/tests
. This is
* for compatibility with standard TCK layout.
* finder
entry in the
* test suite properties file, which should identify the class to be used
* and any arguments it may require. The class will be loaded via the class
* loader specified when the test suite was opened, if one was given;
* otherwise, the system class loader will be used.
*
* The default implementation attempts to use a file testsuite.jtd
* in the tests directory. If found, a BinaryTestFinder will be created
* using this file. If it is not found, then it searches for a property
* named finder in the test suite properties and will attempt to
* instantiate that. If no entry is found or it is blank, an
* HTMLTestFinder is used, using whatever a basic settings HTMLTestFinder
* initializes to.
* @return a test finder to be used to read the tests in the test suite
* @throws TestSuite.Fault if there is a problem creating the test finder
* @see #getTestFinder
* @see #setTestFinder
* @see #getTestsDir
*/
protected TestFinder createTestFinder() throws Fault {
File testsDir = getTestsDir();
// no BTF file; look for a finder=class args... entry
String[] finderCmd = StringArray.split((tsInfo.get("finder")));
String finderClassName;
String[] finderArgs = new String[0];
if (finderCmd == null || finderCmd.length == 0) {
//finderCmd = new String[] {HTMLTestFinder.class.getName()};
finderCmd = null; // ensure null for later use
finderClassName = HTMLTestFinder.class.getName();
}
else {
finderClassName = finderCmd[0];
if (finderCmd.length > 1) {
finderArgs = new String[finderCmd.length - 1];
System.arraycopy(finderCmd, 1, finderArgs, 0, finderArgs.length);
}
else {
// finderArgs should remain empty array
}
}
// first, try looking for testsuite.jtd
String jtd = tsInfo.get("testsuite.jtd");
File jtdFile = (jtd == null ? new File(testsDir, "testsuite.jtd") : new File(root, jtd));
if (jtdFile.exists()) {
try {
// found a file for BinaryTestFinder
// only pass the finder class if it was not defaulted to HTMLTestFinder
return createBinaryTestFinder((finderCmd == null ? null : finderClassName),
finderArgs, testsDir, jtdFile);
}
catch (TestFinder.Fault e) {
// ignore, try to continue with normal finder
}
catch (Fault f) {
// ignore, try to continue with normal finder
}
}
try {
Class extends TestFinder> c = loadClass(finderClassName);
TestFinder tf = newInstance(c);
// called old deprecated entry till we know no-one cares
//tf.init(finderArgs, testsRoot, null, null, tsInfo/*pass in env?*/);
// this likely kills ExpandTestFinder, finally
tf.init(finderArgs, testsDir, null, null, null/*pass in env?*/);
return tf;
}
catch (ClassCastException e) {
throw new Fault(i18n, "ts.notASubtype",
new Object[] {finderClassName, "finder", TestFinder.class.getName()});
}
catch (TestFinder.Fault e) {
throw new Fault(i18n, "ts.errorInitFinder",
new Object[] {finderClassName, e.getMessage()});
}
}
/**
* In the case where a JTD file is found, attempt to load a binary test finder.
* The default implementation attempts to use the finder property in the
* test suite properties if it is a BinaryTestFinder subclass.
*
* @param finderClassName Finder class name to attempt to use as a BTF. Null if
* the default BTF class should be used.
* @param finderArgs Arguments to finder given from the test suite property.
* @param testsDir Reference location to pass to finder.
* @param jtdFile Location of the JTD file to give to the BTF.
* @return The binary test finder which was created.
* @throws com.sun.javatest.TestSuite.Fault
* @throws com.sun.javatest.TestFinder.Fault
* @see com.sun.javatest.TestFinder
* @see com.sun.javatest.finder.BinaryTestFinder
*/
protected TestFinder createBinaryTestFinder(String finderClassName,
String finderArgs[], File testsDir, File jtdFile) throws Fault, TestFinder.Fault {
try {
TestFinder tf = null;
if (finderClassName != null) {
Class extends TestFinder> c = loadClass(finderClassName);
tf = newInstance(c);
}
if (tf instanceof BinaryTestFinder) {
tf.init(finderArgs, testsDir, null, null, null);
return tf;
}
else {
return new BinaryTestFinder(testsDir, jtdFile);
}
}
catch (ClassCastException e) {
throw new Fault(i18n, "ts.notASubtype",
new Object[] {finderClassName, "finder", TestFinder.class.getName()});
}
catch (TestFinder.Fault e) {
throw new Fault(i18n, "ts.errorInitFinder",
new Object[] {finderClassName, e.getMessage()});
}
}
/**
* Create and initialize a TestRunner that can be used to run
* a series of tests.
* The default implementation returns a TestRunner that
* creates a number of test execution threads which each
* create and run a script for each test obtained from
* the test runners iterator.
* @return a TestRunner that can be used to run a series of tests
*/
public TestRunner createTestRunner() {
return new DefaultTestRunner();
}
/**
* Create and initialize a Script that can be used to run a test.
* The default implementation looks for a script
entry in the configuration
* data provided, and if not found, looks for a script
entry in the
* test suite properties. The script entry should define the script class
* to use and any arguments it may require. The class will be loaded via the class
* loader specified when the test suite was opened, if one was given;
* otherwise, the system class loader will be used. Individual test suites will
* typically use a more direct means to create an appropriate script object.
* The parameters for this method are normally passed through to the script
* that is created.
*
* Note that the name of this method is "create", it is not recommended
* that the value returned ever be re-used or cached for subsequent requests
* to this method.
* @param td The test description for the test to be executed.
* @param exclTestCases Any test cases within the test that should not be executed.
* @param scriptEnv Configuration data to be given to the test as necessary.
* @param workDir A work directory in which to store the results of the test.
* @param backupPolicy A policy object used to control how to backup any files that
* might be overwritten.
* @return a script to be used to execute the given test
* @throws TestSuite.Fault if any errors occur while creating the script
*/
public Script createScript(TestDescription td, String[] exclTestCases, TestEnvironment scriptEnv,
WorkDirectory workDir,
BackupPolicy backupPolicy) throws Fault {
if (scriptClass == null) {
String[] script = envLookup(scriptEnv, "script");
if (script.length == 0)
script = StringArray.split(tsInfo.get("script"));
if (script.length > 0) {
scriptClass = loadClass(script[0]);
if (!Script.class.isAssignableFrom(scriptClass)) {
throw new Fault(i18n, "ts.notASubtype",
new Object[] {script[0], "script", Script.class.getName()});
}
scriptArgs = new String[script.length - 1];
System.arraycopy(script, 1, scriptArgs, 0, scriptArgs.length);
}
else {
// for backwards compatibility,
// see if KeywordScript is a reasonable default
boolean keywordScriptOK = false;
for (IteratorThe default implementation returns a {@link LegacyParameters default} * interview suitable for use with test suites built with earlier versions * of the JT Harness: it provides questions equivalent to the fields in * the GUI Parameter Editor or command-line -params option. As such, much of the * necessary configuration data is provided indirectly via environment (.jte) files * which must be created and updated separately. *
Individual test suites should provide their own interview, with questions
* customized to the configuration data they require.
*
* Note that the name of this method is "create", the harness may instantiate
* multiple copies for temporary use, resetting data or transferring data.
* Do not override this method with an implementation which caches the
* return value.
* @return A configuration interview to collect the configuration data for a test run.
* @throws TestSuite.Fault if a problem occurs while creating the interview
*/
public InterviewParameters createInterview()
throws Fault
{
String[] classNameAndArgs = StringArray.split((tsInfo.get("interview")));
if (classNameAndArgs == null || classNameAndArgs.length == 0) {
try {
return new LegacyParameters(this);
}
catch (InterviewParameters.Fault e) {
throw new Fault(i18n, "ts.errorInitDefaultInterview",
e.getMessage());
}
}
String className = classNameAndArgs[0];
String[] args = new String[classNameAndArgs.length - 1];
System.arraycopy(classNameAndArgs, 1, args, 0, args.length);
try {
Class extends InterviewParameters> c = loadClass(className);
InterviewParameters p = newInstance(c);
p.init(args);
p.setTestSuite(this);
return p;
}
catch (ClassCastException e) {
throw new Fault(i18n, "ts.notASubtype",
new Object[] {className, "interview", InterviewParameters.class.getName()});
}
catch (InterviewParameters.Fault e) {
//e.printStackTrace();
throw new Fault(i18n, "ts.errorInitInterview",
new Object[] {className, e.getMessage()});
}
}
/**
* Create a configuration interview based on specified map of template values
* @return A configuration interview to collect the configuration data for a test run.
* @throws TestSuite.Fault if a problem occurs while creating the interview
*/
public InterviewParameters loadInterviewFromTemplate(Mapnull
if no WorkDirectory
* currently available (the log will be registered for the first WD created for this TestSuite
* @param b name of ResorceBundle used for this logger; may be null
if not required
* @param key key for this log
* @return general purpose logger with given key registered for given WorkDirectory or TestSuite (if WD is null)
* @throws TestSuite.DuplicateLogNameFault if log with this key has been registered in the system already
* @see #getLog
*/
public Logger createLog(WorkDirectory wd, String b, String key) throws DuplicateLogNameFault {
if (key == null || "".equals(key)) {
throw new IllegalArgumentException("Log name can not be empty");
}
String logName = wd.getLogFileName();
if (gpls == null)
gpls = new Vectorwd
is null
* @see #createLog
*/
public Logger getLog(WorkDirectory wd, String key) throws NoSuchLogFault {
if (gpls == null)
throw new NoSuchLogFault(i18n, "ts.logger.nologscreated", key);
if (wd == null)
throw new NullPointerException(i18n.getString("ts.logger.nullwd"));
String logFile = wd.getLogFileName();
for (int i = 0; i < gpls.size(); i++) {
GeneralPurposeLogger logger = gpls.get(i);
if (logger.getLogFileName().equals(logFile) && logger.getName().equals(key))
return logger;
}
throw new NoSuchLogFault(i18n, "ts.logger.nosuchlogfault", key);
}
/**
* Cleans the log file in given WorkDirectory
* @param wd WorkDirectory desired logger is registered for
* @throws IOException if log file's content can't be erased
*/
public void eraseLog(WorkDirectory wd) throws IOException {
if (wd == null)
throw new NullPointerException(i18n.getString("ts.logger.nullwd"));
if (gpls != null)
for (int i = 0; i < gpls.size(); i++) {
GeneralPurposeLogger gpl = gpls.get(i);
if (gpl.getLogFileName().equals(wd.getLogFileName())) {
Handler[] h = gpl.getHandlers();
if (h[0] instanceof WorkDirLogHandler) {
((WorkDirLogHandler)h[0]).eraseLogFile();
return;
}
}
}
}
private static boolean isReadableFile(File f) {
return (f.exists() && f.isFile() && f.canRead());
}
/**
* Should tests which no longer exist in the test suite be
* deleted from a work directory when it is opened?
*/
public static final int DELETE_NONTEST_RESULTS = 0;
/*
* Should the content of the test suite be refreshed as the
* tests run? So the test description should be updated from the
* finder just before the test runs.
*/
public static final int REFRESH_ON_RUN = 1;
/**
* Should a test be reset to not run if it is found that the
* test has changed in the test suite (test description does
* not match the one in the existing result).
*/
public static final int CLEAR_CHANGED_TEST = 2;
private static class NotificationLogger extends Logger {
private NotificationLogger(String resourceBundleName) {
super(notificationLogName, resourceBundleName);
setLevel(Level.CONFIG);
// needs to be reimplemented - this initializes Swing, which is not
// allowed inside the core harness
// should be implemented so that the GUI attaches to the logging system
// on startup
//addHandler(new ErrorDialogHandler());
}
public synchronized void log(LogRecord record) {
record.setLoggerName(this.getName());
if (record.getThrown() != null) {
record.setLevel(Level.INFO);
}
super.log(record);
}
// overwrite to make sure exception is handled
public void throwing(String sourceClass, String sourceMethod, Throwable thrown) {
LogRecord lr = new LogRecord(Level.INFO, "THROW");
lr.setSourceClassName(sourceClass);
lr.setSourceMethodName(sourceMethod);
lr.setThrown(thrown);
log(lr);
}
}
private static class GeneralPurposeLogger extends Logger {
private GeneralPurposeLogger(String name, WorkDirectory wd, String resourceBundleName, TestSuite ts) {
super(name, resourceBundleName);
this.logFileName = wd.getLogFileName();
if (wd != null) {
if (!handlersMap.containsKey(wd.getLogFileName())) {
WorkDirLogHandler wdlh = new WorkDirLogHandler(ts.getObservedFile(wd));
handlersMap.put(wd.getLogFileName(), wdlh);
}
addHandler(handlersMap.get(wd.getLogFileName()));
}
setLevel(Level.ALL);
}
public void log(LogRecord record) {
Handler targets[] = getHandlers();
if (targets != null) {
for (int i = 0; i < targets.length; i++) {
if (targets[i] instanceof WorkDirLogHandler) {
((WorkDirLogHandler)targets[i]).publish(record, getName());
} else {
targets[i].publish(record);
}
}
}
}
private String getLogFileName() {
return logFileName;
}
private String logFileName;
}
private static final String TESTSUITE_HTML = "testsuite.html";
private static final String TESTSUITE_JTT = "testsuite.jtt";
private static final String FIND_LEGACY_CONSTRUCTOR = "com.sun.javatest.ts.findLegacyCtor";
private File root;
private Map