blob: cf42e22ffac693496fb145031dca0625a977ea97 [file] [log] [blame]
/*
* 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.
*/
/*
* $Id$
*/
package org.apache.qetest;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Hashtable;
/**
* Static utility class for both general-purpose testing methods
* and a few XML-specific methods.
* Also provides a simplistic Test/Testlet launching helper
* functionality. Simply execute this class from the command
* line with a full or partial classname (in the org.apache.qetest
* area, obviously) and we'll load and execute that class instead.
* @author shane_curcuru@lotus.com
* @version $Id$
*/
public abstract class QetestUtils
{
// abstract class cannot be instantiated
/**
* Utility method to translate a String filename to URL.
*
* Note: This method is not necessarily proven to get the
* correct URL for every possible kind of filename; it should
* be improved. It handles the most common cases that we've
* encountered when running Conformance tests on Xalan.
* Also note, this method does not handle other non-file:
* flavors of URLs at all.
*
* If the name is null, return null.
* If the name starts with a common URI scheme (namely the ones
* found in the examples of RFC2396), then simply return the
* name as-is (the assumption is that it's already a URL)
* Otherwise we attempt (cheaply) to convert to a file:/// URL.
*
* @param String local path\filename of a file
* @return a file:/// URL, the same string if it appears to
* already be a URL, or null if error
*/
public static String filenameToURL(String filename)
{
// null begets null - something like the commutative property
if (null == filename)
return null;
// Don't translate a string that already looks like a URL
if (isCommonURL(filename))
return filename;
File f = new File(filename);
String tmp = null;
try
{
// This normally gives a better path
tmp = f.getCanonicalPath();
}
catch (IOException ioe)
{
// But this can be used as a backup, for cases
// where the file does not exist, etc.
tmp = f.getAbsolutePath();
}
// URLs must explicitly use only forward slashes
if (File.separatorChar == '\\') {
tmp = tmp.replace('\\', '/');
}
// Note the presumption that it's a file reference
// Ensure we have the correct number of slashes at the
// start: we always want 3 /// if it's absolute
// (which we should have forced above)
if (tmp.startsWith("/"))
return "file://" + tmp;
else
return "file:///" + tmp;
}
/**
* Utility method to find a relative path.
*
* <p>Attempt to find a relative path based from the current
* directory (usually user.dir property).</p>
*
* <p>If the name is null, return null. If the name starts
* with a common URI scheme (namely the ones
* found in the examples of RFC2396), then simply return
* the name itself (future work could attempt to detect
* file: protocols if needed).</p>
*
* @param String local path\filename of a file
* @return a local path\file that is relative; if we can't
* find one, we return the original name
*/
public static String filenameToRelative(String filename)
{
// null begets null - something like the commutative property
if (null == filename)
return null;
// Don't translate a string that already looks like a URL
if (isCommonURL(filename))
return filename;
String base = null;
try
{
File userdir = new File(System.getProperty("user.dir"));
// Note: use CanonicalPath, since this ensures casing
// will be identical between the two files
base = userdir.getCanonicalPath();
}
catch (Exception e)
{
// If we can't detect this, we can't determine
// relativeness, so just return the name
return filename;
}
File f = new File(filename);
String tmp = null;
try
{
tmp = f.getCanonicalPath();
}
catch (IOException ioe)
{
tmp = f.getAbsolutePath();
}
// If it's not relative to the base, just return as-is
// (note: this may not be the answer you expect)
if (!tmp.startsWith(base))
return tmp;
// Strip off the base
tmp = tmp.substring(base.length());
// Also strip off any beginning file separator, since we
// don't want it to be mistaken for an absolute path
if (tmp.startsWith(File.separator))
return tmp.substring(1);
else
return tmp;
}
/**
* Worker method to detect common absolute URLs.
*
* @param s String path\filename or URL (or any, really)
* @return true if s starts with a common URI scheme (namely
* the ones found in the examples of RFC2396); false otherwise
*/
protected static boolean isCommonURL(String s)
{
if (null == s)
return false;
if (s.startsWith("file:")
|| s.startsWith("http:")
|| s.startsWith("ftp:")
|| s.startsWith("gopher:")
|| s.startsWith("mailto:")
|| s.startsWith("news:")
|| s.startsWith("telnet:")
)
return true;
else
return false;
}
/**
* Utility method to get a testing Class object.
* This is mainly a bit of syntactic sugar to allow users
* to specify only the end parts of a package.classname
* and still have it loaded. It basically does a
* Class.forName() search, starting with the provided
* classname, and if not found, searching through a list
* of root packages to try to find the class.
*
* Note the inherent danger when there are same-named
* classes in different packages, where the behavior will
* depend on the order of searchPackages.
*
* Commonly called like:
* <code>testClassForName("PerformanceTestlet",
* new String[] {"org.apache.qetest", "org.apache.qetest.xsl" },
* "org.apache.qetest.StylesheetTestlet");</code>
*
* @param String classname FQCN or partially specified classname
* that you wish to load
* @param String[] rootPackages a list of packages to search
* for the classname specified in array order; if null then
* we don't search any additional packages
* @param String defaultClassname a default known-good FQCN to
* return if the classname was not found
*
* @return Class object asked for if one found by combining
* clazz with one of the rootPackages; if none, a Class of
* defaultClassname; or null if an error occoured
*/
public static Class testClassForName(String classname,
String[] rootPackages,
String defaultClassname)
{
// Ensure we have a valid classname, and try it
if ((null != classname) && (classname.length() > 0))
{
// Try just the specified classname, in case it's a FQCN
try
{
return Class.forName(classname);
}
catch (Exception e)
{
/* no-op, fall through */
}
// Now combine each of the rootPackages with the classname
// and see if one of them gets loaded
if (null != rootPackages)
{
for (int i = 0; i < rootPackages.length; i++)
{
try
{
return Class.forName(rootPackages[i] + "." + classname);
}
catch (Exception e)
{
/* no-op, continue */
}
} // end for
} // end if rootPackages...
} // end if classname...
// If we fell out here, try the defaultClassname
try
{
return Class.forName(defaultClassname);
}
catch (Exception e)
{
// You can't always get you what you want
return null;
}
}
/**
* Utility method to get a class name of a test.
* This is mainly a bit of syntactic sugar built on
* top of testClassForName.
*
* @param String classname FQCN or partially specified classname
* that you wish to load
* @param String[] rootPackages a list of packages to search
* for the classname specified in array order; if null then
* we don't search any additional packages
* @param String defaultClassname a default known-good FQCN to
* return if the classname was not found
*
* @return name of class that testClassForName returns;
* or null if an error occoured
*/
public static String testClassnameForName(String classname,
String[] rootPackages,
String defaultClassname)
{
Class clazz = testClassForName(classname, rootPackages, defaultClassname);
if (null == clazz)
return null;
else
return clazz.getName();
}
/**
* Utility method to create a unique runId.
*
* This is used to construct a theoretically unique Id for
* each run of a test script. It is used later in some results
* analysis stylesheets to create comparative charts showing
* differences in results and timing data from one run of
* a test to another.
*
* Current format: MMddHHmm[;baseId]
* where baseId is not used if null.
*
* @param String Id base to start with
*
* @return String Id to use; will include a timestamp
*/
public static String createRunId(String baseId)
{
java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat ("MMddHHmm");
if (null != baseId)
//return formatter.format(new java.util.Date())+ ";" + baseId;
return baseId + ":" + formatter.format(new java.util.Date());
else
return formatter.format(new java.util.Date());
}
/**
* Utility method to get info about the environment.
*
* This is a simple way to get a Hashtable about the current
* JVM's environment from either Xalan's EnvironmentCheck
* utility or from org.apache.env.Which.
*
* @return Hashtable with info about the environment
*/
public static Hashtable getEnvironmentHash()
{
Hashtable hash = new Hashtable();
// Attempt to use Which, which will be better supported
Class clazz = testClassForName("org.apache.env.Which", null, null);
try
{
if (null != clazz)
{
// Call Which's method to fill hash
final Class whichSignature[] =
{ Hashtable.class, String.class, String.class };
Method which = clazz.getMethod("which", whichSignature);
String projects = "";
String options = "";
Object whichArgs[] = { hash, projects, options };
which.invoke(null, whichArgs);
}
else
{
// Use Xalan's EnvironmentCheck
clazz = testClassForName("org.apache.xalan.xslt.EnvironmentCheck", null, null);
if (null != clazz)
{
Object envCheck = clazz.newInstance();
final Class getSignature[] = { };
Method getHash = clazz.getMethod("getEnvironmentHash", getSignature);
Object getArgs[] = { }; // empty
hash = (Hashtable)getHash.invoke(envCheck, getArgs);
}
}
}
catch (Throwable t)
{
hash.put("FATAL-ERROR", "QetestUtils.getEnvironmentHash no services available; " + t.toString());
t.printStackTrace();
}
return hash;
}
/**
* Main method to run from the command line - this acts
* as a cheap launching mechanisim for Xalan tests.
*
* Simply finds the class specified in the first argument,
* instantiates one, and passes it any remaining command
* line arguments we were given.
* The primary motivation here is to provide a simpler
* command line for inexperienced users. You can either
* pass the FQCN, or just the classname and it will still
* get run. Note the one danger is the order of package
* lookups and the potential for the wrong class to run
* when we have identically named classes in different
* packages - but this will usually work!
*
* @param args command line argument array
*/
public static void main(String[] args)
{
if (args.length < 1)
{
System.err.println("QetestUtils.main() ERROR in usage: must have at least one arg: classname [options]");
return;
}
// Get the class specified by the first arg...
Class clazz = QetestUtils.testClassForName(
args[0], defaultPackages, null); // null = no default class
if (null == clazz)
{
System.err.println("QetestUtils.main() ERROR: Could not find class:" + args[0]);
return;
}
try
{
// ...find the main() method...
Class[] parameterTypes = new Class[1];
parameterTypes[0] = java.lang.String[].class;
java.lang.reflect.Method main = clazz.getMethod("main", parameterTypes);
// ...copy over our remaining cmdline args...
final String[] appArgs = new String[(args.length) == 1 ? 0 : args.length - 1];
if (args.length > 1)
{
System.arraycopy(args, 1,
appArgs, 0,
args.length - 1);
}
// ...and execute the method!
Object[] mainArgs = new Object[1];
mainArgs[0] = appArgs;
main.invoke(null, mainArgs);
}
catch (Throwable t)
{
System.err.println("QetestUtils.main() ERROR: running " + args[0]
+ ".main() threw: " + t.toString());
t.printStackTrace();
}
}
/**
* Default list of packages for xml-xalan tests.
* Technically this is Xalan-specific and should really be
* in some other directory, but I'm being lazy tonight.
* This looks for Xalan-related tests in the following
* packages in <b>this order</b>:
* <ul>
* <li>org.apache.qetest.xsl</li>
* <li>org.apache.qetest.xalanj2</li>
* <li>org.apache.qetest.trax</li>
* <li>org.apache.qetest.trax.dom</li>
* <li>org.apache.qetest.trax.sax</li>
* <li>org.apache.qetest.trax.stream</li>
* <li>org.apache.qetest.xslwrapper</li>
* <li>org.apache.qetest.xalanj1</li>
* <li>org.apache.qetest</li>
* <li>org.apache.qetest.qetesttest</li>
* </ul>
* Note the normal naming convention for automated tests
* is either *Test.java or *Testlet.java; although this is
* not required, it will make it easier to write simple
* test discovery mechanisims.
*/
public static final String[] defaultPackages =
{
"org.apache.qetest.xsl",
"org.apache.qetest.xalanj2",
"org.apache.qetest.trax",
"org.apache.qetest.trax.dom",
"org.apache.qetest.trax.sax",
"org.apache.qetest.trax.stream",
"org.apache.qetest.xslwrapper",
"org.apache.qetest.dtm",
"org.apache.qetest.xalanj1",
"org.apache.qetest",
"org.apache.qetest.qetesttest"
};
}