blob: 50a1fce3158c54751b3f91d505f842b549320d28 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.api.tools.builder.tests;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import junit.framework.AssertionFailedError;
import junit.framework.ComparisonFailure;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.jdt.core.Flags;
public class TestCase extends junit.framework.TestCase {
// Filters
public static final String METHOD_PREFIX = "test";
public static String RUN_ONLY_ID = "ONLY_";
// Ordering
public static final int NO_ORDER = 0;
public static final int ALPHABETICAL_SORT = 1;
public static final int ALPHA_REVERSE_SORT = 2;
public static final int RANDOM_ORDER_JDT = 3;
public static final int RANDOM_ORDER_TIME = 4;
/**
* Expected tests order while building tests list for test suites.
* @see #buildTestsList(Class, int, long)
* <br>
* User may use following different values:
* <ul>
* <li>{@link #NO_ORDER}: none (this is the default)</li>
* <li>{@link #ALPHABETICAL_SORT}: alphabetical order (ie. ascending)</li>
* <li>{@link #ALPHA_REVERSE_SORT}: alpha reverse order (ie. descending )</li>
* <li>{@link #RANDOM_ORDER_JDT}: random order using JDT/Core current version as seed</li>
* <li>{@link #RANDOM_ORDER_TIME}: random order using current time as seed (used time value is displayed in console)</li>
* <li>other values: random order using given <code>long</code> value as seed</li>
* </ul>
* This value is initialized with <code>"ordering"</code> system property.
*/
public static final long ORDERING;
static {
long ordering = NO_ORDER; // default is no order
try {
long seed = Long.parseLong(System.getProperty("ordering", "0"));
try {
int kind = Integer.parseInt(System.getProperty("ordering", "0"));
switch (kind) {
case NO_ORDER:
break;
case ALPHABETICAL_SORT:
ordering = kind;
System.err.println("Note that tests will be run sorted using alphabetical order...");
break;
case ALPHA_REVERSE_SORT:
ordering = kind;
System.err.println("Note that tests will be run sorted using alphabetical reverse order...");
break;
case RANDOM_ORDER_TIME:
ordering = System.currentTimeMillis();
System.err.println("Note that tests will be run in random order using seed="+ordering+" (ie. current time)");
break;
default:
ordering = seed;
System.err.println("Note that tests will be run in random order using seed="+seed+" (ie. given value)");
break;
}
} catch (NumberFormatException nfe) {
// ordering value is over int range but is a valid long => keep the value
ordering = seed;
System.err.println("Note that tests will be run in random order using seed="+seed+" (ie. given value)");
}
}
catch (NumberFormatException nfe) {
System.err.println("Only integer or long values are allowed for 'ordering' system property: "+System.getProperty("ordering", "0")+" is not valid ! => no order will be finally used...");
ordering = NO_ORDER;
}
ORDERING = ordering;
}
/**
* Flag telling whether test execution must stop on failure or not.
* Default is true;
*/
protected boolean abortOnFailure = true;
// static variables for subsets tests
public static String TESTS_PREFIX = null; // prefix of test names to perform
public static String[] TESTS_NAMES = null; // list of test names to perform
public static int[] TESTS_NUMBERS = null; // list of test numbers to perform
public static int[] TESTS_RANGE = null; // range of test numbers to perform
public TestCase(String name) {
setName(name);
}
public static void assertEquals(String expected, String actual) {
assertEquals(null, expected, actual);
}
public static void assertEquals(String message, String expected, String actual) {
assertStringEquals(message, expected, actual, true);
}
public static void assertStringEquals(String expected, String actual, boolean showLineSeparators) {
assertStringEquals(null, expected, actual, showLineSeparators);
}
public static void assertStringEquals(String message, String expected, String actual, boolean showLineSeparators) {
if (expected == null && actual == null)
return;
if (expected != null && expected.equals(actual))
return;
final StringBuffer formatted;
if (message != null) {
formatted = new StringBuffer(message).append('.');
} else {
formatted = new StringBuffer();
}
if (showLineSeparators) {
final String expectedWithLineSeparators = showLineSeparators(expected);
final String actualWithLineSeparators = showLineSeparators(actual);
formatted.append("\n----------- Expected ------------\n"); //$NON-NLS-1$
formatted.append(expectedWithLineSeparators);
formatted.append("\n------------ but was ------------\n"); //$NON-NLS-1$
formatted.append(actualWithLineSeparators);
formatted.append("\n--------- Difference is ----------\n"); //$NON-NLS-1$
throw new ComparisonFailure(formatted.toString(),
expectedWithLineSeparators,
actualWithLineSeparators);
} else {
formatted.append("\n----------- Expected ------------\n"); //$NON-NLS-1$
formatted.append(expected);
formatted.append("\n------------ but was ------------\n"); //$NON-NLS-1$
formatted.append(actual);
formatted.append("\n--------- Difference is ----------\n"); //$NON-NLS-1$
throw new ComparisonFailure(formatted.toString(), expected, actual);
}
}
/**
* Same method as {@link #assertEquals(String, int, int)} if the flag
* {@link #abortOnFailure} has been set to <code>true</code>.
* Otherwise, the thrown exception {@link AssertionFailedError} is caught
* and its message is only displayed in the console hence producing no JUnit failure.
*/
protected void assumeEquals(String msg, int expected, int actual) {
try {
assertEquals(msg, expected, actual);
} catch (AssertionFailedError afe) {
if (abortOnFailure) {
throw afe;
}
printAssertionFailure(afe);
}
}
/**
* Same method as {@link #assertEquals(String, long, long)} if the flag
* {@link #abortOnFailure} has been set to <code>true</code>.
* Otherwise, the thrown exception {@link AssertionFailedError} is caught
* and its message is only displayed in the console hence producing no JUnit failure.
*/
protected void assumeEquals(String msg, long expected, long actual) {
try {
assertEquals(msg, expected, actual);
} catch (AssertionFailedError afe) {
if (abortOnFailure) {
throw afe;
}
printAssertionFailure(afe);
}
}
/**
* Same method as {@link #assertTrue(String, boolean)} if the flag
* {@link #abortOnFailure} has been set to <code>true</code>.
* Otherwise, the thrown exception {@link AssertionFailedError} is caught
* and its message is only displayed in the console hence producing no JUnit failure.
*/
protected void assumeTrue(String msg, boolean cond) {
try {
assertTrue(msg, cond);
} catch (AssertionFailedError afe) {
if (abortOnFailure) {
throw afe;
}
printAssertionFailure(afe);
}
}
private void printAssertionFailure(AssertionFailedError afe) {
System.out.println("\n!---!!---!!---!!---!!---!!---!!---!!---!!---!!---!!---!!---!!---!!---!!---!!---!");
System.out.println("Caught assertion failure while running test "+getName()+":");
System.out.println(" "+afe.getMessage());
System.out.println("--------------------------------------------------------------------------------\n");
}
/**
* Build a list of methods to run for a test suite.
* There's no recursion in given class hierarchy, methods are only
* public method starting with "test" of it.
* <p></p>
* Note that this list may be reduced using 2 different mechanism:
* <p></p>
* 1) TESTS* static variables:
* <ul>
* <li>{@link #TESTS_PREFIX}: only methods starting with this prefix (after "test" of course)
* will be put in test suite.
* </li>
* <li>{@link #TESTS_NAMES}: only methods with these names will be put in test suite.
* </li>
* <li>{@link #TESTS_NUMBERS}: only methods including these numbers will be put in test suite.<br>
* For example, <code>TESTS_NUMBERS = new int[] { 10, 100, 125678 };</code> will put
* <code>test010()</code>, <code>test100()</code> and <code>testBug125678()</code>
* methods in test suite.
* </li>
* <li>{@link #TESTS_RANGE}: only methods which numbers are between first and second value
* of this int array will be put in the suite.
* For example: <code>TESTS_RANGE = new int[] { 10, 12 };</code> will put
* <code>test010()</code>, <code>test011()</code> and <code>test012()</code>
* methods in test suite.<br>
* Note that -1 will clean min or max value, for example <code>TESTS_RANGE = new int[] { 10, -1 };</code>
* will put all methods after <code>test010()</code> in the test suite.
* </li>
* </ul>
* <p></p>
* 2) testONLY_ methods<br>
* As static variables needs a static initializer usually put at the beginning of the test suite,
* it could be a little be boring while adding tests at the end of the file to modify this static initializer.
* One solution to avoid this was to introduced specific methods name which will be only executed
* when test suite is run alone.
* For example:
* <pre>
* public class MyTest extends TestCase {
* public MyTest(String name) {
* super(name);
* }
* public test001() {
* ...
* }
* public test002() {
* ...
* }
* ...
* public testONLY_100() {
* ...
* }
* }
* </pre>
* This test suite will have only test "testONLY_100" put in test suite while running it.
*
* Note that these 2 mechanisms should be reset while executing "global" test suites.
* For example:
* <pre>
* public class TestAll extends junit.framework.TestCase {
* public TestAll(String testName) {
* super(testName);
* }
* public static Test suite() {
* TestCase.TESTS_PREFIX = null;
* TestCase.TESTS_NAMES = null;
* TestCase.TESTS_NUMBERS= null;
* TestCase.TESTS_RANGE = null;
* TestCase.RUN_ONLY_ID = null;
* return buildTestSuite(MyTest.class);
* }
* }
* </pre>
* This will insure you that all tests will be put in TestAll test suite, even if static variables
* values are set or some methods start as testONLY_...
*
* @param evaluationTestClass the test suite class
* @return a list ({@link List}) of tests ({@link Test}).
*/
public static List buildTestsList(Class evaluationTestClass) {
return buildTestsList(evaluationTestClass, 0/*only one level*/, ORDERING);
}
/**
* Build a list of methods to run for a test suite.
* <br>
* Differ from {@link #buildTestsList(Class)} in the fact that one
* can specify level of recursion in hierarchy to find additional tests.
*
* @param evaluationTestClass the test suite class
* @param inheritedDepth level of recursion in top-level hierarchy to find other tests
* @return a {@link List list} of {@link Test tests}.
*/
public static List buildTestsList(Class evaluationTestClass, int inheritedDepth) {
return buildTestsList(evaluationTestClass, inheritedDepth, ORDERING);
}
/**
* Build a list of methods to run for a test suite.
* <br>
* This list may be ordered in different ways using {@link #ORDERING}.
* <br>
* Example
* <pre>
* public class AbstractTest extends TestCase {
* public MyTest(String name) {
* super(name);
* }
* public testOne() {
* ...
* }
* public testTwo() {
* ...
* }
* }
* public class MyTest extends AbstractTest {
* public MyTest(String name) {
* super(name);
* }
* public test001() {
* ...
* }
* public test002() {
* ...
* }
* ...
* public testONLY_100() {
* ...
* }
* }
* </pre>
* Returned list will have 5 tests if inheritedDepth is equals to 1 instead of
* 3 if it was 0 as while calling by {@link #buildTestsList(Class)}.
*
* @see #buildTestsList(Class) for complete explanation of subsets mechanisms.
*
* @param evaluationTestClass the test suite class
* @param inheritedDepth level of recursion in top-level hierarchy to find other tests
* @param ordering kind of sort use for the list (see {@link #ORDERING} for possible values)
* @return a {@link List list } of {@link Test tests}
*/
public static List buildTestsList(Class evaluationTestClass, int inheritedDepth, long ordering) {
List tests = new ArrayList();
List testNames = new ArrayList();
List onlyNames = new ArrayList();
Constructor constructor = null;
try {
// Get class constructor
Class[] paramTypes = new Class[] { String.class };
constructor = evaluationTestClass.getConstructor(paramTypes);
}
catch (Exception e) {
// cannot get constructor, skip suite
return tests;
}
// Get all tests from "test%" methods
Method[] methods = evaluationTestClass.getDeclaredMethods();
Class evaluationTestSuperclass = evaluationTestClass.getSuperclass();
for (int i=0; i<inheritedDepth && !Flags.isAbstract(evaluationTestSuperclass.getModifiers()); i++) {
Method[] superMethods = evaluationTestSuperclass.getDeclaredMethods();
Method[] mergedMethods = new Method[methods.length+superMethods.length];
System.arraycopy(superMethods, 0, mergedMethods, 0, superMethods.length);
System.arraycopy(methods, 0, mergedMethods, superMethods.length, methods.length);
methods = mergedMethods;
evaluationTestSuperclass = evaluationTestSuperclass.getSuperclass();
}
// Build test names list
final int methodPrefixLength = METHOD_PREFIX.length();
nextMethod: for (int m = 0, max = methods.length; m < max; m++) {
int modifiers = methods[m].getModifiers();
if (Flags.isPublic(modifiers) && !Flags.isStatic(modifiers)) {
String methName = methods[m].getName();
if (methName.startsWith(METHOD_PREFIX)) {
// look if this is a run only method
boolean isOnly = RUN_ONLY_ID != null && methName.substring(methodPrefixLength).startsWith(RUN_ONLY_ID);
if (isOnly) {
if (!onlyNames.contains(methName)) {
onlyNames.add(methName);
}
continue;
}
// no prefix, no subsets => add method
if (TESTS_PREFIX == null && TESTS_NAMES == null && TESTS_NUMBERS == null && TESTS_RANGE == null) {
if (!testNames.contains(methName)) {
testNames.add(methName);
}
continue nextMethod;
}
// no prefix or method matches prefix
if (TESTS_PREFIX == null || methName.startsWith(TESTS_PREFIX)) {
int numStart = TESTS_PREFIX==null ? methodPrefixLength : TESTS_PREFIX.length();
// tests names subset
if (TESTS_NAMES != null) {
for (int i = 0, imax= TESTS_NAMES.length; i<imax; i++) {
if (methName.indexOf(TESTS_NAMES[i]) >= 0) {
if (!testNames.contains(methName)) {
testNames.add(methName);
}
continue nextMethod;
}
}
}
// look for test number
int length = methName.length();
if (numStart < length) {
// get test number
while (numStart<length && !Character.isDigit(methName.charAt(numStart))) numStart++; // skip to first digit
while (numStart<length && methName.charAt(numStart) == '0') numStart++; // skip to first non-nul digit
int n = numStart;
while (n<length && Character.isDigit(methName.charAt(n))) n++; // skip to next non-digit
if (n>numStart && n <= length) {
try {
int num = Integer.parseInt(methName.substring(numStart, n));
// tests numbers subset
if (TESTS_NUMBERS != null && !testNames.contains(methName)) {
for (int i = 0; i < TESTS_NUMBERS.length; i++) {
if (TESTS_NUMBERS[i] == num) {
testNames.add(methName);
continue nextMethod;
}
}
}
// tests range subset
if (TESTS_RANGE != null && TESTS_RANGE.length == 2 && !testNames.contains(methName)) {
if ((TESTS_RANGE[0]==-1 || num>=TESTS_RANGE[0]) && (TESTS_RANGE[1]==-1 || num<=TESTS_RANGE[1])) {
testNames.add(methName);
continue nextMethod;
}
}
} catch (NumberFormatException e) {
System.out.println("Method "+methods[m]+" has an invalid number format: "+e.getMessage());
}
}
}
// no subset, add all tests
if (TESTS_NAMES==null && TESTS_NUMBERS==null && TESTS_RANGE==null) {
if (!testNames.contains(methName)) {
testNames.add(methName);
}
}
}
}
}
}
// Order tests
List names = onlyNames.size() > 0 ? onlyNames : testNames;
if (ordering == ALPHA_REVERSE_SORT) {
Collections.sort(names, Collections.reverseOrder());
} else if (ordering == ALPHABETICAL_SORT) {
Collections.sort(names);
} else if (ordering != NO_ORDER) {
Collections.shuffle(names, new Random(ordering));
}
// Add corresponding tests
Iterator iterator = names.iterator();
while (iterator.hasNext()) {
String testName = (String) iterator.next();
try {
tests.add(constructor.newInstance(new Object[] { testName } ));
}
catch (Exception e) {
System.err.println("Method "+testName+" removed from suite due to exception: "+e.getMessage());
}
}
return tests;
}
/**
* Build a test suite with all tests computed from public methods starting with "test"
* found in the given test class.
* Test suite name is the name of the given test class.
*
* Note that this lis maybe reduced using some mechanisms detailed in {@link #buildTestsList(Class)} method.
*
* @param evaluationTestClass
* @return a {@link Test test suite}
*/
public static Test buildTestSuite(Class evaluationTestClass) {
return buildTestSuite(evaluationTestClass, null); //$NON-NLS-1$
}
/**
* Build a test suite with all tests computed from public methods starting with "test"
* found in the given test class.
* Test suite name is the given name.
*
* Note that this lis maybe reduced using some mechanisms detailed in {@link #buildTestsList(Class)} method.
*
* @param evaluationTestClass
* @param suiteName
* @return a test suite ({@link Test})
*/
public static Test buildTestSuite(Class evaluationTestClass, String suiteName) {
TestSuite suite = new TestSuite(suiteName==null?evaluationTestClass.getName():suiteName);
List tests = buildTestsList(evaluationTestClass);
for (int index=0, size=tests.size(); index<size; index++) {
suite.addTest((Test)tests.get(index));
}
return suite;
}
/*
* Shows the line separators in the given String.
*/
protected static String showLineSeparators(String string) {
if (string == null) return null;
StringBuffer buffer = new StringBuffer();
int length = string.length();
for (int i = 0; i < length; i++) {
char car = string.charAt(i);
switch (car) {
case '\n':
buffer.append("\\n\n"); //$NON-NLS-1$
break;
case '\r':
if (i < length-1 && string.charAt(i+1) == '\n') {
buffer.append("\\r\\n\n"); //$NON-NLS-1$
i++;
} else {
buffer.append("\\r\n"); //$NON-NLS-1$
}
break;
default:
buffer.append(car);
break;
}
}
return buffer.toString();
}
}