/* | |
* 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.felix.ipojo.junit4osgi.impl; | |
import java.io.PrintStream; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Map.Entry; | |
import junit.framework.Test; | |
import junit.framework.TestResult; | |
import junit.framework.TestSuite; | |
import org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner; | |
import org.apache.felix.ipojo.junit4osgi.OSGiTestCase; | |
import org.apache.felix.ipojo.junit4osgi.OSGiTestSuite; | |
import org.apache.felix.ipojo.parser.ParseUtils; | |
import org.osgi.framework.Bundle; | |
import org.osgi.framework.BundleContext; | |
import org.osgi.service.log.LogService; | |
/** | |
* Detect test suite from installed bundles. | |
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> | |
*/ | |
public class JunitExtender implements OSGiJunitRunner { | |
/** | |
* Suite method name. | |
*/ | |
public static final String SUITE_METHODNAME = "suite"; | |
/** | |
* List of found suites (Bundle-> List of Class). | |
*/ | |
private Map/*<Bundle, List<Class>>*/ m_suites = new HashMap/*<Bundle, List<Class>>*/(); | |
/** | |
* The result printer. | |
* By default, prints result on {@link System#out} | |
*/ | |
private ResultPrinter m_printer = new ResultPrinter(System.out); | |
/** | |
* The log service used to log messages. | |
* If not provided, a default implementation | |
* printing messages on the console is used. | |
*/ | |
private LogService m_log; | |
/** | |
* A new matching bundle arrives. | |
* @param bundle the matching bundle | |
* @param header the looked header value | |
*/ | |
void onBundleArrival(Bundle bundle, String header) { | |
String[] tss = ParseUtils.split(header, ","); | |
for (int i = 0; i < tss.length; i++) { | |
try { | |
if (tss[i].length() != 0) { | |
m_log.log(LogService.LOG_INFO, "Loading " + tss[i]); | |
Class/*<? extends Test>*/ clazz = bundle.loadClass(tss[i].trim()); | |
addTestSuite(bundle, clazz); | |
} | |
} catch (ClassNotFoundException e) { | |
m_log.log(LogService.LOG_ERROR, "The test suite " + tss[i] + " is not in the bundle " + bundle.getBundleId() + " : " + e.getMessage()); | |
} | |
} | |
} | |
/** | |
* Adds a test suite. | |
* @param bundle the bundle declaring the test suite. | |
* @param test the test class. | |
*/ | |
private synchronized void addTestSuite(Bundle bundle, Class/*<? extends Test>*/ test) { | |
List/*<Class>*/ list = (List) m_suites.get(bundle); | |
if (list == null) { | |
list = new ArrayList/*<Class>*/(); | |
list.add(test); | |
m_suites.put(bundle, list); | |
} else { | |
list.add(test); | |
} | |
} | |
/** | |
* Removes the test suites provided by the given bundles. | |
* @param bundle the leaving bundles. | |
*/ | |
private synchronized void removeTestSuites(Bundle bundle) { | |
List list = (List) m_suites.remove(bundle); | |
m_log.log(LogService.LOG_INFO, "Unload test suites " + list); | |
} | |
/** | |
* A matching bundle is leaving. | |
* @param bundle the leaving bundle. | |
*/ | |
void onBundleDeparture(Bundle bundle) { | |
removeTestSuites(bundle); | |
} | |
/** | |
* Set the result printer. | |
* @param pw the stream to use. | |
* @see org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner#setResultPrinter(java.io.PrintStream) | |
*/ | |
public void setResultPrinter(PrintStream pw) { | |
m_printer = new ResultPrinter(pw); | |
} | |
/** | |
* Runs tests. | |
* @return the list of {@link TestResult} | |
* @see org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner#run() | |
*/ | |
public synchronized List/*<TestResult>*/ run() { | |
List/*<TestResult>*/ results = new ArrayList/*<TestResult>*/(m_suites.size()); | |
Iterator/*<Entry<Bundle, List<Class>>>*/ it = m_suites.entrySet().iterator(); | |
while (it.hasNext()) { | |
Entry/*<Bundle, List<Class>>*/ entry = (Entry) it.next(); | |
Bundle bundle = (Bundle) entry.getKey(); | |
List/*<Class>*/ list = (List) m_suites.get(bundle); | |
for (int i = 0; i < list.size(); i++) { | |
Test test = createTestFromClass((Class) list.get(i), bundle); | |
TestResult tr = doRun(test); | |
results.add(tr); | |
} | |
} | |
return results; | |
} | |
/** | |
* Internal methods executing tests. | |
* @param test the test to execute | |
* @return the result | |
*/ | |
private TestResult doRun(Test test) { | |
TestResult result = new TestResult(); | |
result.addListener(m_printer); | |
long startTime = System.currentTimeMillis(); | |
test.run(result); | |
long endTime = System.currentTimeMillis(); | |
long runTime = endTime - startTime; | |
m_printer.print(result, runTime); | |
return result; | |
} | |
/** | |
* Creates a {@link Test} object from the | |
* given class from the given bundle. | |
* This method creates {@link OSGiTestCase} and | |
* {@link OSGiTestSuite} when required. | |
* @param clazz the class | |
* @param bundle the bundle | |
* @return the resulting Test object. | |
*/ | |
private Test createTestFromClass(Class/*<?>*/ clazz, Bundle bundle) { | |
Method suiteMethod = null; | |
boolean bc = false; | |
try { | |
suiteMethod = clazz.getMethod(SUITE_METHODNAME, new Class[0]); | |
} catch (Exception e) { | |
// try to use a suite method receiving a bundle context | |
try { | |
suiteMethod = clazz.getMethod(SUITE_METHODNAME, new Class[] { BundleContext.class }); | |
bc = true; | |
} catch (Exception e2) { | |
// try to extract a test suite automatically | |
if (OSGiTestSuite.class.isAssignableFrom(clazz)) { | |
OSGiTestSuite ts = new OSGiTestSuite(clazz, getBundleContext(bundle)); | |
return ts; | |
} else if (OSGiTestCase.class.isAssignableFrom(clazz)) { | |
OSGiTestSuite ts = new OSGiTestSuite(clazz, getBundleContext(bundle)); | |
return ts; | |
} else { | |
return new TestSuite(clazz); | |
} | |
} | |
} | |
if (!Modifier.isStatic(suiteMethod.getModifiers())) { | |
m_log.log(LogService.LOG_ERROR, "Suite() method must be static"); | |
return null; | |
} | |
Test test = null; | |
try { | |
if (bc) { | |
test = (Test) suiteMethod.invoke(null, new Object[] { getBundleContext(bundle) }); // static method injection the bundle context | |
} else { | |
test = (Test) suiteMethod.invoke(null, (Object[]) new Class[0]); // static method | |
} | |
} catch (InvocationTargetException e) { | |
m_log.log(LogService.LOG_ERROR, "Failed to invoke suite():" + e.getTargetException().toString()); | |
return null; | |
} catch (IllegalAccessException e) { | |
m_log.log(LogService.LOG_ERROR, "Failed to invoke suite():" + e.toString()); | |
return null; | |
} | |
return test; | |
} | |
/** | |
* Gets the list of {@link Test}. | |
* @return the list of {@link Test} | |
* @see org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner#getTests() | |
*/ | |
public synchronized List/*<Test>*/ getTests() { | |
List/*<Test>*/ results = new ArrayList/*<Test>*/(); | |
Iterator/*<Entry<Bundle, List<Class>>>*/ it = m_suites.entrySet().iterator(); | |
while (it.hasNext()) { | |
Entry/*<Bundle, List<Class>>*/ entry = (Entry) it.next(); | |
Bundle bundle = (Bundle) entry.getKey(); | |
List/*<Class>*/ list = (List) m_suites.get(bundle); | |
for (int i = 0; i < list.size(); i++) { | |
Test test = createTestFromClass((Class) list.get(i), bundle); | |
results.add(test); | |
} | |
} | |
return results; | |
} | |
/** | |
* Runs the given tests. | |
* @param test the test to execute | |
* @return the result | |
* @see org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner#run(junit.framework.Test) | |
*/ | |
public TestResult run(Test test) { | |
return doRun(test); | |
} | |
/** | |
* Gets the list of {@link Test} from the bundle (specified | |
* by using the bundle id). | |
* @param bundleId the bundle id | |
* @return the list of {@link Test} declared in this bundle. | |
* @see org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner#getTests(long) | |
*/ | |
public synchronized List/*<Test>*/ getTests(long bundleId) { | |
Iterator/*<Entry<Bundle, List<Class>>>*/ it = m_suites.entrySet().iterator(); | |
while (it.hasNext()) { | |
Entry/*<Bundle, List<Class>>*/ entry = (Entry) it.next(); | |
Bundle bundle = (Bundle) entry.getKey(); | |
if (bundle.getBundleId() == bundleId) { | |
List/*<Test>*/ results = new ArrayList/*<Test>*/(); | |
List/*<Class>*/ list = (List) m_suites.get(bundle); | |
for (int i = 0; i < list.size(); i++) { | |
Test test = createTestFromClass((Class) list.get(i), bundle); | |
results.add(test); | |
} | |
return results; | |
} | |
} | |
return null; | |
} | |
/** | |
* Runs the tests declared in the bundle | |
* (specified by the bundle id). | |
* @param bundleId the bundle id | |
* @return the List of {@link TestResult} | |
* @see org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner#run(long) | |
*/ | |
public synchronized List/*<TestResult>*/ run(long bundleId) { | |
Iterator/*<Entry<Bundle, List<Class>>>*/ it = m_suites.entrySet().iterator(); | |
while (it.hasNext()) { | |
Entry/*<Bundle, List<Class>>*/ entry = (Entry) it.next(); | |
Bundle bundle = (Bundle) entry.getKey(); | |
if (bundle.getBundleId() == bundleId) { | |
List/*<TestResult>*/ results = new ArrayList/*<TestResult>*/(); | |
List/*<Class>*/ list = (List) m_suites.get(bundle); | |
for (int i = 0; i < list.size(); i++) { | |
Test test = createTestFromClass((Class) list.get(i), bundle); | |
TestResult tr = doRun(test); | |
results.add(tr); | |
} | |
return results; | |
} | |
} | |
return null; | |
} | |
/** | |
* Stop method. | |
* Clears test suites. | |
*/ | |
public synchronized void stopping() { | |
m_log.log(LogService.LOG_INFO, "Cleaning test suites ..."); | |
m_suites.clear(); | |
} | |
/** | |
* Start method. | |
*/ | |
public void starting() { | |
m_log.log(LogService.LOG_INFO, "Junit Extender starting ..."); | |
} | |
/** | |
* Helper method analyzing the {@link Bundle} object | |
* to get the {@link BundleContext} object. | |
* @param bundle the Bundle | |
* @return the BundleContext of <code>null</code> if | |
* the BundleContext cannot be collected. | |
*/ | |
private BundleContext getBundleContext(Bundle bundle) { | |
if (bundle == null) { return null; } | |
// getBundleContext (OSGi 4.1) | |
Method meth = null; | |
try { | |
meth = bundle.getClass().getMethod("getBundleContext", new Class[0]); | |
} catch (SecurityException e) { | |
// Nothing do to, will try the Equinox method | |
} catch (NoSuchMethodException e) { | |
// Nothing do to, will try the Equinox method | |
} | |
// try Equinox getContext if not found. | |
if (meth == null) { | |
try { | |
meth = bundle.getClass().getMethod("getContext", new Class[0]); | |
} catch (SecurityException e) { | |
// Nothing do to, will try field inspection | |
} catch (NoSuchMethodException e) { | |
// Nothing do to, will try field inspection | |
} | |
} | |
if (meth != null) { | |
if (! meth.isAccessible()) { | |
meth.setAccessible(true); | |
} | |
try { | |
return (BundleContext) meth.invoke(bundle, new Object[0]); | |
} catch (IllegalArgumentException e) { | |
m_log.log(LogService.LOG_ERROR, "Cannot get the BundleContext by invoking " + meth.getName(), e); | |
return null; | |
} catch (IllegalAccessException e) { | |
m_log.log(LogService.LOG_ERROR, "Cannot get the BundleContext by invoking " + meth.getName(), e); | |
return null; | |
} catch (InvocationTargetException e) { | |
m_log.log(LogService.LOG_ERROR, "Cannot get the BundleContext by invoking " + meth.getName(), e); | |
return null; | |
} | |
} | |
// Else : Field inspection (KF and Prosyst) | |
Field[] fields = bundle.getClass().getDeclaredFields(); | |
for (int i = 0; i < fields.length; i++) { | |
if (BundleContext.class.isAssignableFrom(fields[i].getType())) { | |
if (! fields[i].isAccessible()) { | |
fields[i].setAccessible(true); | |
} | |
try { | |
return (BundleContext) fields[i].get(bundle); | |
} catch (IllegalArgumentException e) { | |
m_log.log(LogService.LOG_ERROR, "Cannot get the BundleContext by reflecting on " + fields[i].getName(), e); | |
return null; | |
} catch (IllegalAccessException e) { | |
m_log.log(LogService.LOG_ERROR, "Cannot get the BundleContext by reflecting on " + fields[i].getName(), e); | |
return null; | |
} | |
} | |
} | |
m_log.log(LogService.LOG_ERROR, "Cannot find the BundleContext for " + bundle.getSymbolicName(), null); | |
return null; | |
} | |
} |