blob: 49a389f0b97f54634eba55fb5023a18f5c47f72e [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.
*/
package org.apache.openjpa.lib.test;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.textui.TestRunner;
import org.apache.regexp.RE;
import org.apache.regexp.RESyntaxException;
import org.apache.regexp.REUtil;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.log.LogFactoryImpl;
import org.apache.openjpa.lib.util.Localizer;
/**
* TestCase framework to run various tests against solarmetric code.
* This class contains various utility methods for the following functions:
* <ul>
* <li>Using multiple, isolated ClassLoaders</li>
* <li>Running a test in multiple concurrent threads</li>
* <li>Assertion helpers</li>
* <li>Creating random Strings, numbers, etc.</li>
* </ul>
*
* @author Marc Prud'hommeaux
* @author Patrick Linskey
*/
public abstract class AbstractTestCase extends TestCase {
public static final String TEST_METHODS =
System.getProperty(AbstractTestCase.class.getName() + ".testMethods");
public static final long PLATFORM_ALL = 2 << 1;
public static final long PLATFORM_UNKNOWN = 2 << 2;
public static final String SKIP_TOKEN = "SOLARSKIP";
public static final String SKIP_DELIMITER = "|";
private static final Localizer _loc =
Localizer.forPackage(AbstractTestCase.class);
private Log log = null;
private static Map _times = new HashMap();
private static AbstractTestCase _lastTest = null;
private static WatchdogThread _watchdog = new WatchdogThread();
private long _timeout;
/**
* Constructor. Create a test case with the specified name.
*/
public AbstractTestCase(String test) {
super(test);
}
public AbstractTestCase() {
}
protected final Log getLog() {
if (log == null)
log = newLog();
return log;
}
protected Log newLog() {
// this implementation leaves much to be desired, as it just
// creates a new LogFactoryImpl each time, and does not apply
// any configurations.
return new LogFactoryImpl().getLog(getLogName());
}
protected String getLogName() {
return "com.solarmetric.Runtime";
}
/**
* Called before the watchdog thread is about to kill the entire
* JVM due to a test case's timeout. This method offers the
* ability to try to resolve whatever contention is taking place
* in the test. It will be given 10 seconds to try to end the
* test peacefully before the watchdog exits the JVM.
*/
protected void preTimeout() {
}
public void run(TestResult result) {
if (skipTest()) {
// keep track of the tests we skip so that we can get an
// idea in the autobuild status
System.err.println(SKIP_TOKEN + SKIP_DELIMITER
+ ("" + getClass().getName())
+ "." + getName() + SKIP_DELIMITER);
return;
}
if (_lastTest != null && _lastTest.getClass() != getClass()) {
try {
_lastTest.tearDownTestClass();
} catch (Throwable t) {
getLog().error(null, t);
}
}
if (_lastTest == null || _lastTest.getClass() != getClass()) {
try {
setUpTestClass();
} catch (Throwable t) {
getLog().error(null, t);
}
}
_lastTest = this;
// inform the watchdog thread that we are entering the test
_watchdog.enteringTest(this);
try {
super.run(result);
} finally {
_watchdog.leavingTest(this);
}
}
/**
* If this test should be skipped given the current
* environment, return <code>true</code>. This allows a unit test
* class to disable test cases on a per-method granularity, and
* prevents the test from showing up as a passed test just
* because it was skipped.
* For example, if a particular test case method should not be
* run against a certain database, this method could check the
* name of the test result and the current database configuration
* in order to make the decision:
* <p/>
* <code> protected boolean skipTest() {
* // don't run with pointbase: it uses a DataSource, which
* // can't be translated into a JBoss DataSource configuration.
* if ("testJBoss".equals(getName()) &&
* getCurrentPlatform() == PLATFORM_POINTBASE)
* return true;
* }
* </code>
* If you want to disable execution of an entire test case
* class for a given database, you might want to add the class to
* the excluded test list in that database's properties file.
*/
protected boolean skipTest() {
if (TEST_METHODS != null && TEST_METHODS.length() > 0)
return TEST_METHODS.indexOf(getName()) == -1;
return false;
}
/**
* This method is called before the first test in this test class
* is executed.
*/
public void setUpTestClass() throws Exception {
}
/**
* This method is called after the last test in this test class
* is executed. It can be used to do things like clean up
* large, slow processes that may have been started.
*/
public void tearDownTestClass() throws Exception {
}
public void tearDown() throws Exception {
if ("true".equals(System.getProperty("meminfo")))
printMemoryInfo();
super.tearDown();
}
//////////////////////////
// Generating random data
//////////////////////////
/**
* Support method to get a random Integer for testing.
*/
public static Integer randomInt() {
return new Integer((int) (Math.random() * Integer.MAX_VALUE));
}
/**
* Support method to get a random Character for testing.
*/
public static Character randomChar() {
char [] TEST_CHAR_ARRAY = new char []{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1',
'2', '3', '4', '5', '6', '7', '8', '9' };
return new Character(TEST_CHAR_ARRAY[
(int) (Math.random() * TEST_CHAR_ARRAY.length)]);
}
/**
* Support method to get a random Long for testing.
*/
public static Long randomLong() {
return new Long((long) (Math.random() * Long.MAX_VALUE));
}
/**
* Support method to get a random Short for testing.
*/
public static Short randomShort() {
return new Short((short) (Math.random() * Short.MAX_VALUE));
}
/**
* Support method to get a random Double for testing.
*/
public static Double randomDouble() {
return new Double((double) (Math.round(Math.random() * 5000d)) / 1000d);
}
/**
* Support method to get a random Float for testing.
*/
public static Float randomFloat() {
return new Float((float) (Math.round(Math.random() * 5000f)) / 1000f);
}
/**
* Support method to get a random Byte for testing.
*/
public static Byte randomByte() {
return new Byte((byte) (Math.random() * Byte.MAX_VALUE));
}
/**
* Support method to get a random Boolean for testing.
*/
public static Boolean randomBoolean() {
return new Boolean(Math.random() > 0.5 ? true : false);
}
/**
* Support method to get a random Date for testing.
*/
public static Date randomDate() {
long millis = (long) (Math.random() * System.currentTimeMillis());
// round millis to the nearest 1000: this is because some
// databases do not store the milliseconds correctly(e.g., MySQL).
// This is a really a bug we should fix. FC #27.
millis -= (millis % 1000);
return new Date(millis);
}
/**
* Support method to get a random String for testing.
*/
public static String randomString() {
// default to a small string, in case column sizes are
// limited(such as with a string primary key)
return randomString(50);
}
/**
* Support method to get a random String for testing.
*/
public static String randomString(int len) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < (int) (Math.random() * len) + 1; i++)
buf.append(randomChar());
return buf.toString();
}
/**
* Support method to get a random clob for testing.
*/
public static String randomClob() {
StringBuffer sbuf = new StringBuffer();
while (sbuf.length() < (5 * 1024)) { // at least 5K
sbuf.append(randomString(1024));
}
return sbuf.toString();
}
/**
* Support method to get a random BigInteger for testing.
*/
public static BigInteger randomBigInteger() {
// too many of our test databases don't support bigints > MAX_LONG:
// I don't like it, but for now, let's only test below MAX_LONG
BigInteger lng = new BigInteger(
((long) (Math.random() * Long.MAX_VALUE)) + "");
BigInteger multiplier = new BigInteger("1");
// (1 + (int)(Math.random() * 10000)) + "");
if (Math.random() < 0.5)
multiplier = multiplier.multiply(new BigInteger("-1"));
return lng.multiply(multiplier);
}
/**
* Support method to get a random BigDecimal for testing.
*/
public static BigDecimal randomBigDecimal() {
BigInteger start = randomBigInteger();
String str = start.toString();
// truncate off the last 8 digits: we still get some
// overflows with lame databases.
for (int i = 0; i < 8; i++)
if (str.length() > 2)
str = str.substring(0, str.length() - 1);
start = new BigInteger(str);
String val = start + "." + ((int) (Math.random() * 10))
+ ((int) (Math.random() * 10))
+ ((int) (Math.random() * 10))
+ ((int) (Math.random() * 10))
+ ((int) (Math.random() * 10))
+ ((int) (Math.random() * 10))
+ ((int) (Math.random() * 10))
+ ((int) (Math.random() * 10))
+ ((int) (Math.random() * 10))
+ ((int) (Math.random() * 10));
return new BigDecimal(val);
}
/**
* Support method to get a random blob for testing.
*/
public static byte[] randomBlob() {
// up to 100K blob
byte [] blob = new byte [(int) (Math.random() * 1024 * 100)];
for (int i = 0; i < blob.length; i++)
blob[i] = randomByte().byteValue();
return blob;
}
/**
* Invoke setters for pimitives and primitive wrappers on the
* specified object.
*/
public static Object randomizeBean(Object bean)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
BeanInfo info = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor [] props = info.getPropertyDescriptors();
for (int i = 0; i < props.length; i++) {
Method write = props[i].getWriteMethod();
if (write == null)
continue;
Class [] params = write.getParameterTypes();
if (params == null || params.length != 1)
continue;
Class paramType = params[0];
Object arg = null;
if (paramType == boolean.class || paramType == Boolean.class)
arg = randomBoolean();
else if (paramType == byte.class || paramType == Byte.class)
arg = randomByte();
else if (paramType == char.class || paramType == Character.class)
arg = randomChar();
else if (paramType == short.class || paramType == Short.class)
arg = randomShort();
else if (paramType == int.class || paramType == Integer.class)
arg = randomInt();
else if (paramType == long.class || paramType == Long.class)
arg = randomLong();
else if (paramType == double.class || paramType == Double.class)
arg = randomDouble();
else if (paramType == float.class || paramType == Float.class)
arg = randomFloat();
else if (paramType == String.class)
arg = randomString();
else if (paramType == BigInteger.class)
arg = randomBigInteger();
else if (paramType == BigDecimal.class)
arg = randomBigDecimal();
else if (paramType == Date.class)
arg = randomDate();
if (arg != null)
write.invoke(bean, new Object []{ arg });
}
return bean;
}
/**
* Utility method to start a profile.
*
* @see #endProfile(String)
*/
public void startProfile(String name) {
_times.put(name, new Long(System.currentTimeMillis()));
}
/**
* Utility to end the profile and print out the time. Example usage:
* <p/>
* <pre><code> startProfile("Some long task"); doSomeLongTask();
* endProfile("Some long task");
* </code></pre>
*
* @param name
* @return the amount of time that this profile invocation took, or
* -1 if <code>name</code> was never started.
*/
public long endProfile(String name) {
Long time = (Long) _times.remove(name);
long elapsed = -1;
if (time != null)
elapsed = System.currentTimeMillis() - time.longValue();
getLog().info(_loc.get("profile-info", name,
(time == null ? "???" : "" + elapsed)));
return elapsed;
}
/////////////////////////
// ClassLoader functions
/////////////////////////
/**
* Create a ClassLoader that will not use the parent
* ClassLoader to resolve classes. This is useful for
* testing interactions between Kodo in running
* in ClassLoaderA and instances in ClassLoaderB.
*/
public ClassLoader createIsolatedClassLoader() {
return new IsolatedClassLoader();
}
public NestedClassLoader createNestedClassLoader() {
return new NestedClassLoader(false);
}
public NestedClassLoader createNestedParentClassLoader() {
return new NestedClassLoader(true);
}
/**
* Reload the specified class in an isolated ClassLoader.
*
* @param target the target class to load
* @return the Class as reloaded in an new ClassLoader
*/
public Class isolate(Class target) throws ClassNotFoundException {
Class result = isolate(target.getName());
assertTrue(result != target);
assertNotEquals(result, target);
assertTrue(result.getClassLoader() != target.getClassLoader());
return result;
}
public Class isolate(String target) throws ClassNotFoundException {
ClassLoader il = createIsolatedClassLoader();
Class result = il.loadClass(target);
assertEquals(result.getName(), target);
return result;
}
public Class nest(Class target) throws ClassNotFoundException {
ClassLoader il = createNestedClassLoader();
Class result = il.loadClass(target.getName());
assertTrue(result != target);
assertNotEquals(result, target);
assertTrue(result.getClassLoader() != target.getClassLoader());
assertEquals(result.getName(), target.getName());
return result;
}
public Object isolateNew(Class target)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException {
return isolate(target).newInstance();
}
private static class NestedClassLoader extends AntClassLoader {
public NestedClassLoader(boolean useParent) {
super(ClassLoader.getSystemClassLoader(), useParent);
for (StringTokenizer cltok = new StringTokenizer(
System.getProperty("java.class.path"), File.pathSeparator);
cltok.hasMoreTokens();) {
String path = cltok.nextToken();
// only load test paths, not jar files
if (path.indexOf(".jar") != -1)
continue;
if (path.indexOf(".zip") != -1)
continue;
addPathElement(path);
}
try {
if (!useParent) {
assertTrue(loadClass
(AbstractTestCase.class.getName()).getClassLoader()
!= AbstractTestCase.class.getClassLoader());
}
} catch (ClassNotFoundException cnfe) {
fail(cnfe.toString());
}
}
public Class findClass(String name) throws ClassNotFoundException {
// don't isolate PC and related classes in kodo.enhnace
if (name.indexOf(".enhance.") != -1)
throw new ClassNotFoundException(name);
if (name.indexOf("/enhance/") != -1)
throw new ClassNotFoundException(name);
return super.findClass(name);
}
}
/**
* A ClassLoader that is completely isolated with respect to
* any classes that are loaded in the System ClassLoader.
*
* @author Marc Prud'hommeaux
*/
private static class IsolatedClassLoader extends NestedClassLoader {
public IsolatedClassLoader() {
super(false);
setIsolated(false);
}
}
///////////////
// Collections
///////////////
/**
* Validate that the specified {@link Collection} fulfills the
* Collection contract as specified by the Collections API.
* <p/>
* <strong>Note</strong>: does not validate mutable operations
*/
public static void validateCollection(Collection collection) {
int size = collection.size();
int iterated = 0;
// ensure we can walk along the iterator
for (Iterator i = collection.iterator(); i.hasNext();) {
iterated++;
i.next();
}
// ensure the number of values iterated is the same as the list size
assertEquals(size, iterated);
// also validate the list
if (collection instanceof List) {
List ll = new ArrayList();
for (int i = 0; i < 100; i++)
ll.add(new Integer(i));
validateList((List) ll);
validateList((List) collection);
}
}
/**
* Validate that the specified {@link List} fulfills the
* List contract as specified by the Collections API.
* <p/>
* <strong>Note</strong>: does not validate mutable operations
*/
public static void validateList(List list) {
Object [] coreValues = list.toArray();
Object [] values1 = new Object [list.size()];
Object [] values2 = new Object [list.size()];
Object [] values3 = new Object [list.size()];
Object [] values4 = new Object [list.size()];
// fill sequential index access list
for (int i = 0; i < list.size(); i++)
values1[i] = list.get(i);
// fill sequential list
int index = 0;
ListIterator iter;
for (iter = list.listIterator(0); iter.hasNext();) {
assertEquals(index, iter.nextIndex());
assertEquals(index, iter.previousIndex() + 1);
values2[index] = iter.next();
assertTrue(list.contains(values2[index]));
index++;
}
// ensure NoSuchElementException is thrown as appropriate
try {
iter.next();
fail("next() should have resulted in a NoSuchElementException");
} catch (NoSuchElementException e) {
} // as expected
// fill reverse sequential list
int back = 0;
for (iter = list.listIterator(list.size()); iter.hasPrevious();) {
assertEquals(index, iter.previousIndex() + 1);
assertEquals(index, iter.nextIndex());
values3[--index] = iter.previous();
back++;
}
assertEquals(list.size(), back);
// ensure NoSuchElementException is thrown as appropriate
try {
iter.previous();
fail("previous() should have resulted in a "
+ "NoSuchElementException");
} catch (NoSuchElementException e) {
} // as expected
// fill random access list
List indices = new LinkedList();
for (int i = 0; i < list.size(); i++)
indices.add(new Integer(i));
for (int i = 0; i < list.size(); i++) {
int rand = (int) (Math.random() * indices.size());
Integer randIndex = (Integer) indices.remove(rand);
values4[randIndex.intValue()] = list.get(randIndex.intValue());
}
assertEquals(Arrays.asList(coreValues), Arrays.asList(values1));
assertIdentical(Arrays.asList(coreValues), Arrays.asList(values1));
assertEquals(Arrays.asList(coreValues), Arrays.asList(values2));
assertIdentical(Arrays.asList(coreValues), Arrays.asList(values2));
assertEquals(Arrays.asList(coreValues), Arrays.asList(values4));
assertIdentical(Arrays.asList(coreValues), Arrays.asList(values4));
assertEquals(Arrays.asList(coreValues), Arrays.asList(values3));
assertIdentical(Arrays.asList(coreValues), Arrays.asList(values3));
}
/**
* Assert that the given List contain the exact same
* elements. This is different than the normal List contract, which
* states that list1.equals(list2) if each element e1.equals(e2).
* This method asserts that e1 == n2.
*/
public static void assertIdentical(List c1, List c2) {
assertEquals(c1.size(), c2.size());
for (Iterator i1 = c1.iterator(), i2 = c2.iterator();
i1.hasNext() && i2.hasNext();)
assertTrue(i1.next() == i2.next());
}
/**
* Assert that the collection parameter is already ordered
* according to the specified comparator.
*/
public void assertOrdered(Collection c, Comparator comp) {
List l1 = new LinkedList(c);
List l2 = new LinkedList(c);
assertEquals(l1, l2);
Collections.sort(l2, comp);
assertEquals(l1, l2);
Collections.sort(l1, comp);
assertEquals(l1, l2);
}
////////////////////
// Assertion Helpers
////////////////////
public void assertNotEquals(Object a, Object b) {
if (a == null && b != null)
return;
if (a != null && b == null)
return;
if (!(a.equals(b)))
return;
if (!(b.equals(a)))
return;
fail("expected !<" + a + ">.equals(<" + b + ">)");
}
public void assertSize(int size, Object ob) {
if (ob == null) {
assertEquals(size, 0);
return;
}
if (ob instanceof Collection)
ob = ((Collection) ob).iterator();
if (ob instanceof Iterator) {
Iterator i = (Iterator) ob;
int count = 0;
while (i.hasNext()) {
count++;
i.next();
}
assertEquals(size, count);
} else
fail("assertSize: expected Collection, Iterator, "
+ "Query, or Extent, but got " + ob.getClass().getName());
}
/////////////////////
// Generic utilities
/////////////////////
public void copy(File from, File to) throws IOException {
copy(new FileInputStream(from), to);
}
public void copy(InputStream in, File to) throws IOException {
FileOutputStream fout = new FileOutputStream(to);
byte[] b = new byte[1024];
for (int n = 0; (n = in.read(b)) != -1;)
fout.write(b, 0, n);
}
/**
* Print out information on memory usage.
*/
public void printMemoryInfo() {
Runtime rt = Runtime.getRuntime();
long total = rt.totalMemory();
long free = rt.freeMemory();
long used = total - free;
NumberFormat nf = NumberFormat.getInstance();
getLog().warn(_loc.get("mem-info",
nf.format(used),
nf.format(total),
nf.format(free)));
}
/**
* Return a list of all values iterated by the given iterator.
*/
public static List iteratorToList(Iterator i) {
LinkedList list = new LinkedList();
while (i.hasNext())
list.add(i.next());
return list;
}
/**
* Return an array of the objects iterated by the given iterator.
*/
public static Object [] iteratorToArray(Iterator i, Class [] clazz) {
return iteratorToList(i).toArray(clazz);
}
/**
* Run ant on the specified build file.
*
* @param buildFile the build file to use
* @param target the name of the target to invoke
*/
public void ant(File buildFile, String target) {
assertTrue(buildFile.isFile());
Project project = new Project();
project.init();
project.setUserProperty("ant.file", buildFile.getAbsolutePath());
ProjectHelper.configureProject(project, buildFile);
project.executeTarget(target);
}
/**
* Serialize and deserialize the object.
*
* @param validateEquality make sure the hashCode and equals
* methods hold true
*/
public static Object roundtrip(Object orig, boolean validateEquality)
throws IOException, ClassNotFoundException {
assertNotNull(orig);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bout);
out.writeObject(orig);
ByteArrayInputStream bin = new ByteArrayInputStream(
bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin);
Object result = in.readObject();
if (validateEquality) {
assertEquals(orig, result);
assertEquals(orig.hashCode(), result.hashCode());
}
return result;
}
/**
* @return true if the specified input matches the regular expression regex.
*/
public static boolean matches(String regex, String input)
throws RESyntaxException {
RE re = REUtil.createRE(regex);
return re.match(input);
}
public static void assertMatches(String regex, String input) {
try {
if (!(matches(regex, input)))
fail("Expected regular expression: <" + regex + ">"
+ " did not match: <" + input + ">");
} catch (RESyntaxException e) {
throw new IllegalArgumentException(e.toString());
}
}
public static void assertNotMatches(String regex, String input) {
try {
if (matches(regex, input))
fail("Regular expression: <" + regex + ">"
+ " should not match: <" + input + ">");
} catch (RESyntaxException e) {
throw new IllegalArgumentException(e.toString());
}
}
/**
* Check the list if strings and return the ones that match
* the specified match.
*/
public static List matches(String regex, Collection input)
throws RESyntaxException {
List matches = new ArrayList();
for (Iterator i = input.iterator(); i.hasNext();) {
String check = (String) i.next();
if (matches(regex, check))
matches.add(check);
}
return matches;
}
/**
* Assert that the specified collection of Strings contains at least
* one string that matches the specified regular expression.
*/
public static void assertMatches(String regex, Collection input) {
try {
if (matches(regex, input).size() == 0)
fail("The specified list of size " + input.size()
+ " did not contain any strings that match the"
+ " specified regular expression(\"" + regex + "\")");
} catch (RESyntaxException e) {
throw new IllegalArgumentException(e.toString());
}
}
/**
* Assert that the specified collection of Strings does not match
* the specified regular expression.
*/
public static void assertNotMatches(String regex, Collection input) {
try {
List matches;
if (((matches = matches(regex, input))).size() > 0)
fail("The specified list of size " + input.size()
+ " did contain one or more strings that matchs the"
+ " specified illegal regular expression"
+ " (\"" + regex + "\")."
+ " First example of a matching message is: "
+ matches.iterator().next());
} catch (RESyntaxException e) {
throw new IllegalArgumentException(e.toString());
}
}
/**
* To be called by the child. E.g.:
* <code> public static void main(String [] args) { main(TestBug375.class);
* }
* </code>
*/
public static void main(Class c) {
TestRunner.run(c);
}
/**
* To be called by child. Figures out the class from the calling context.
*/
public static void main() {
String caller = new SecurityManager() {
public String toString() {
return getClassContext()[2].getName();
}
}.toString();
try {
main(Class.forName(caller));
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe.toString());
}
}
/**
* Returns the jar file in which the class is contained.
*
* @return the jar file, or none if the class is not in a jar
* @throws FileNotFoundException if the jar file cannot located
*/
public static File getJarFile(Class clazz) throws FileNotFoundException {
URL url = clazz.getResource(clazz.getName().substring(
clazz.getName().lastIndexOf(".") + 1) + ".class");
if (url == null)
throw new FileNotFoundException(clazz.toString());
String file = url.getFile();
if (file == null)
throw new FileNotFoundException(url.toString());
int index = file.indexOf("!");
if (index == -1)
throw new FileNotFoundException(file);
file = file.substring(0, index);
file = file.substring("file:".length());
File f = new File(file);
if (!(f.isFile()))
throw new FileNotFoundException(file);
return f.getAbsoluteFile();
}
/**
* The number of milliseconds each test case will have for a timeout.
*/
public void setTimeout(long timeout) {
_timeout = timeout;
}
/**
* The number of milliseconds each test case will have for a timeout.
*/
public long getTimeout() {
return _timeout;
}
/**
* A watchdog that just exits the JVM if a test has not completed in
* a certain amount of time. This speeds up the mechanism of determining
* if a timeout has occurred, since we can exit the entire test run
* if a test hasn't completed in a shorted amount of time than
* the global test timeout.
*
* @author Marc Prud'hommeaux
*/
private static class WatchdogThread extends Thread {
private final long _timeoutms;
private long _endtime = -1;
private AbstractTestCase _curtest = null;
public WatchdogThread() {
super("Kodo test case watchdog thread");
setDaemon(true);
int timeoutMin = new Integer
(System.getProperty("autobuild.testcase.timeout", "20"))
.intValue();
_timeoutms = timeoutMin * 60 * 1000;
}
public void run() {
while (true) {
try {
sleep(200);
} catch (InterruptedException ie) {
}
if (_endtime > 0 && System.currentTimeMillis() > _endtime) {
Thread preTimeout = new Thread
("Attempting pre-timeout for " + _curtest) {
public void run() {
_curtest.preTimeout();
}
};
preTimeout.start();
// wait a little while for the pre-timeout
// thread to complete
try {
preTimeout.join(10 * 1000);
} catch (Exception e) {
}
// give it a few more seconds...
try {
sleep(5 * 1000);
} catch (Exception e) {
}
// new endtime? resume...
if (System.currentTimeMillis() < _endtime)
continue;
new Exception("test case "
+ (_curtest != null ? _curtest.getName()
: "UNKNOWN") + " timed out after "
+ _timeoutms + "ms").printStackTrace();
// also run "killall -QUIT java" to try to grab
// a stack trace
try {
Runtime.getRuntime().exec
(new String[]{ "killall", "-QUIT", "java" });
} catch (Exception e) {
}
try {
sleep(1000);
} catch (InterruptedException ie) {
}
// now actually exit
System.exit(111);
}
}
}
public synchronized void enteringTest(AbstractTestCase test) {
long timeout = test.getTimeout();
if (timeout <= 0)
timeout = _timeoutms;
_endtime = System.currentTimeMillis() + timeout;
_curtest = test;
if (!isAlive())
start();
}
public synchronized void leavingTest(AbstractTestCase test) {
_endtime = -1;
_curtest = null;
}
}
}