/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.aggregate.test;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.JUnit4TestAdapter;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.junit.runners.Suite.SuiteClasses;
import org.mockito.configuration.MockitoConfiguration;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
/**
* Gather all the tests in the test fragments to run in a single
* aggregated test suite
*/
public class TestDesignerTestGatherer extends TestCase {
private static final String ALL_PLUGIN_TESTS = "All Plugin Tests"; //$NON-NLS-1$
private static final String BUNDLE_FILTER = "org\\.teiid.*"; //$NON-NLS-1$
private static final String ALL_TESTS_CLASS = "AllTests.class"; //$NON-NLS-1$
private static final String BUNDLE_ROOT = "/"; //$NON-NLS-1$
private static final String PACKAGE_SEPARATOR = "."; //$NON-NLS-1$
private static final String ORG = "org"; //$NON-NLS-1$
private static final String TEIID = "teiid"; //$NON-NLS-1$
private static final String TEST = "test"; //$NON-NLS-1$
private static final String NEWLINE = "\n"; //$NON-NLS-1$
private static Map<String, URL> testCache = new HashMap<String, URL>();
private static Map<String, Integer> testsInPackage = new HashMap<String, Integer>();
private static Map<String, Integer> testsInClass = new HashMap<String, Integer>();
/**
* @param string
*/
private static void log(String message) {
TestDesignerPlugin.logInfo(message);
}
/**
* Assemble a junit 3 test suite
*
* @return test suite containing all found test classes
*/
public static Test suite() {
// Turn off mockito's objenesis cache as it can return the
// wrong Admin interface in the runtime client tests
MockitoConfiguration.setEnableClassCache(false);
TestSuite aggregateSuite = new TestSuite(ALL_PLUGIN_TESTS);
BundleContext context = TestDesignerPlugin.getPlugin().getBundleContext();
for (Bundle bundle : context.getBundles()) {
if (Platform.isFragment(bundle)) {
// Ignore test fragments since AllTests classes loaded from their
// host bundles
log("Ignoring Test fragment " + bundle.getSymbolicName() + " since it is accessible from its host bundle"); //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
if (bundle.getSymbolicName().contains(TEST)) {
// Ignore the test framework plugins
log("Ignoring Test framework plugin " + bundle.getSymbolicName()); //$NON-NLS-1$
continue;
}
if (!bundle.getSymbolicName().matches(BUNDLE_FILTER)) {
log("Ignoring non-teiid plugin " + bundle.getSymbolicName()); //$NON-NLS-1$
continue;
}
collectTests(aggregateSuite, bundle);
}
outputCounts("=== Number of Tests found per Class ===", testsInClass); //$NON-NLS-1$
outputCounts("=== Number of Tests found per Package ===", testsInPackage); //$NON-NLS-1$
if (aggregateSuite.countTestCases() == 0) {
aggregateSuite.addTest(TestSuite.warning("Cannot find any tests conforming to the filter " + BUNDLE_FILTER)); //$NON-NLS-1$
}
return aggregateSuite;
}
/**
* @param parentSuite
* @param bundle
*/
private static void collectTests(TestSuite parentSuite, Bundle bundle) {
Enumeration<URL> entries = bundle.findEntries(BUNDLE_ROOT, ALL_TESTS_CLASS, true);
if (entries == null || !entries.hasMoreElements()) {
log("No AllTest class found in plugin " + bundle.getSymbolicName()); //$NON-NLS-1$
return;
}
StringBuffer buffer = new StringBuffer("Collecting Tests for " + bundle.getSymbolicName() + PACKAGE_SEPARATOR + TEST + NEWLINE); //$NON-NLS-1$
TestSuite suite = new TestSuite(bundle.getSymbolicName() + PACKAGE_SEPARATOR + TEST);
int totalTestClasses = 0;
while (entries.hasMoreElements()) {
URL element = entries.nextElement();
try {
URL url = FileLocator.toFileURL(element);
String className = convertToClassName(url);
/*
* If there are classes in both the bin and target/classes directories
* then both eclipse and maven have compiled the test plugin. We only
* need to test one of them so cache them accordingly.
*/
if (testCache .containsKey(className))
continue;
if (AllTests.class.equals(className)) {
continue;
}
testCache.put(className, url);
Class<?> klazz = loadClassFromBundle(bundle, className);
SuiteClasses suiteClasses = klazz.getAnnotation(SuiteClasses.class);
for (Class<?> suiteKlazz : suiteClasses.value()) {
if (TestCase.class.isAssignableFrom(suiteKlazz)) {
addJUnit3TestClass(suite, (Class<? extends TestCase>) suiteKlazz);
}
else {
addJUnit4TestClass(suite, suiteKlazz);
}
}
totalTestClasses += suite.testCount();
parentSuite.addTest(suite);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
buffer.append("Collected " + totalTestClasses + " test classes for " + bundle.getSymbolicName() + PACKAGE_SEPARATOR + TEST + NEWLINE); //$NON-NLS-1$ //$NON-NLS-2$
log(buffer.toString());
}
/**
* @param title
* @param counts
*/
private static void outputCounts(String title, Map<String, Integer> counts) {
int total = 0;
StringBuffer buffer = new StringBuffer(title);
List<String> outLines = new ArrayList<String>();
for (Map.Entry<String, Integer> entry : counts.entrySet()) {
outLines.add(entry.getKey() + "\t\t\t" + entry.getValue()); //$NON-NLS-1$
total += entry.getValue();
}
Collections.sort(outLines);
buffer.append(NEWLINE);
for (String outLine : outLines) {
buffer.append(outLine);
buffer.append(NEWLINE);
}
buffer.append("Total Number of Tests = " + total); //$NON-NLS-1$
buffer.append(NEWLINE);
log(buffer.toString());
}
private static void count(Class<?> testClass, int noTestCases) {
String className = testClass.getCanonicalName();
Integer classCount = testsInClass.get(className);
if (classCount == null) {
classCount = 0;
}
testsInClass.put(className, (classCount + noTestCases));
String pkgName = testClass.getPackage().getName();
Integer pkgCount = testsInPackage.get(pkgName);
if (pkgCount == null) {
pkgCount = 0;
}
testsInPackage.put(pkgName, (pkgCount + noTestCases));
}
/**
* Such classes do not extend {@link TestCase} and simply use the Test annotation
* to denote their tests.
*
* @param suite
* @param suiteKlazz
*/
private static void addJUnit4TestClass(TestSuite suite, Class<?> klazz) {
if (! hasJUnit4AnnotateTestMethods(klazz)) {
return;
}
JUnit4TestAdapter testAdapter = new JUnit4TestAdapter(klazz);
suite.addTest(testAdapter);
count(klazz, testAdapter.countTestCases());
}
/**
* @param klazz
* @return
*/
private static boolean hasJUnit4AnnotateTestMethods(Class<?> klazz) {
if (klazz == null)
return false;
for (Method method : klazz.getDeclaredMethods()) {
if (method.getAnnotation(org.junit.Test.class) != null) {
return true;
}
}
// Class may not contain annotated methods but its parent classes might
Class<?> parent = klazz.getSuperclass();
if (hasJUnit4AnnotateTestMethods(parent))
return true;
return false;
}
/**
* @param suite
* @param suiteKlazz
*/
private static void addJUnit3TestClass(TestSuite suite, Class<? extends TestCase> suiteKlazz) {
try {
// JUnit 3 tests can have a static suite() method that creates
// the suite and has its own setup and teardown. Should prefer
// to use these.
Method declaredMethod = suiteKlazz.getDeclaredMethod("suite"); //$NON-NLS-1$
Object object = declaredMethod.invoke(suiteKlazz, new Object[0]);
Test testCase = (Test) object;
suite.addTest(testCase);
count(suiteKlazz, testCase.countTestCases());
}
catch (Exception noSuchMethodException) {
TestSuite testSuite = new TestSuite(suiteKlazz);
suite.addTest(testSuite);
count(suiteKlazz, testSuite.countTestCases());
}
}
/**
* @param bundle
* @param className
* @return
*/
private static Class<?> loadClassFromBundle(Bundle bundle, String className) throws ClassNotFoundException {
Class<?> klazz = null;
if (Platform.isFragment(bundle)) {
// Need to use the bundle's host for loading the class
Bundle[] hosts = Platform.getHosts(bundle);
if (hosts == null) {
return klazz;
}
for (Bundle host : hosts) {
klazz = host.loadClass(className);
if (klazz != null) {
break;
}
}
}
else {
klazz = bundle.loadClass(className);
}
return klazz;
}
/**
* @param url
*/
private static String convertToClassName(URL url) throws Exception {
IPath fullPath = new Path(url.getPath());
IPath classPath = null;
for (int i = 0; i < fullPath.segmentCount(); ++i) {
String segment = fullPath.segment(i);
String segment2 = fullPath.segment(i + 1);
if (segment == null || segment2 == null)
continue;
if (! ORG.equals(segment))
continue;
if (! segment2.startsWith(TEIID))
continue;
classPath = fullPath.removeFirstSegments(i);
break;
}
if (classPath == null) {
// this has been compiled in some other directory that we did not expect
throw new Exception("Cannot process unsupported compiled class: " + fullPath); //$NON-NLS-1$
}
// Drop .class suffix
classPath = classPath.removeFileExtension();
// Replace / with .
StringBuilder classNameBuilder = new StringBuilder();
for (int i = 0; i < classPath.segmentCount(); ++i) {
String segment = classPath.segment(i);
classNameBuilder.append(segment);
if (i + 1 < classPath.segmentCount())
classNameBuilder.append(PACKAGE_SEPARATOR);
}
return classNameBuilder.toString();
}
}