blob: 7bd7c862951c85182001079910d356731eca566a [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.felix.ipojo.junit4osgi.plugin;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestFailure;
import junit.framework.TestListener;
import junit.framework.TestResult;
import org.apache.felix.framework.Felix;
import org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner;
import org.apache.felix.ipojo.junit4osgi.plugin.log.LogServiceImpl;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
/**
* Goal starting Felix and executing junit4osgi tests.
*
* @goal test
* @phase integration-test
* @requiresDependencyResolution runtime
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*
*/
public class Junit4osgiPlugin extends AbstractMojo {
/**
* The Maven project.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject m_project;
/**
* Dependencies of the current plugin.
* @parameter expression="${plugin.artifacts}"
*/
private java.util.List m_pluginArtifacts;
/**
* Base directory where all reports are written to.
*
* @parameter expression="${project.build.directory}/surefire-reports"
*/
private File m_reportsDirectory;
/**
* Base directory where all reports are written to.
*
* @parameter expression="${project.build.directory}"
*/
private File m_targetDir;
/**
* Must the current artifact be deployed?
*
* @parameter expression="${deployProjectArtifact}" default-value="true"
*/
private boolean m_deployProjectArtifact;
/**
* Required bundles.
*
* @parameter
*/
private List bundles;
/**
* Felix configuration.
*
* @parameter
*/
private Map configuration;
/**
* Enables / Disables the log service provided by the plugin.
*
* @parameter expression="${logService}" default-value="true"
*/
private boolean m_logEnable;
/**
* Number of executed test case.
*/
private int m_total;
/**
* Number of failing test case.
*/
private int m_totalFailures;
/**
* Number of test case in error .
*/
private int m_totalErrors;
/**
* Test results in error.
*/
private List m_errors = new ArrayList();
/**
* Failing test results.
*/
private List m_failures = new ArrayList();
/**
* Test results.
*/
private List m_results = new ArrayList();
/**
* Log Service exposed by the plug-in framework.
*/
private LogServiceImpl m_logService;
/**
* Set this to 'true' to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if you
* enable it using the "maven.test.skip" property, because maven.test.skip disables both running the
* tests and compiling the tests. Consider using the skipTests parameter instead.
*
* @parameter expression="${maven.test.skip}"
*/
private boolean skip;
/**
* Set this to true to ignore a failure during testing. Its use is NOT RECOMMENDED, but quite convenient on
* occasion.
*
* @parameter expression="${maven.test.failure.ignore}"
*/
private boolean testFailureIgnore;
/**
* Set this to avoid printing test execution trace on System.out and System.err. This will be written in the
* reports.
* @parameter
*/
private boolean hideOutputs;
/**
* Felix configuration.
*/
private Map felixConf;
/**
* Executes the plug-in.
* @throws MojoFailureException when the test execution failed.
* @see org.apache.maven.plugin.AbstractMojo#execute()
*/
public void execute() throws MojoFailureException {
if (skip) {
getLog().info("Tests are skipped");
return;
}
List bundles = parseBundleList();
bundles.addAll(getTestBundle());
List activators = new ArrayList();
m_logService = new LogServiceImpl();
if (m_logEnable) { // Starts the log service if enabled
activators.add(m_logService);
} else {
getLog().info("Log Service disabled");
}
activators.add(new Installer(m_pluginArtifacts, bundles, m_project, m_deployProjectArtifact));
felixConf = new HashMap();
felixConf.put("felix.systembundle.activators", activators);
felixConf.put("org.osgi.framework.storage.clean", "onFirstInit");
felixConf.put("ipojo.log.level", "WARNING");
// Use a boot delagation to share classes between the host and the embedded Felix.
// The cobertura package is used during code coverage collection
//felixConf.put("org.osgi.framework.bootdelegation", "net.sourceforge.cobertura.coveragedata");
felixConf.put("org.osgi.framework.system.packages.extra", "org.osgi.service.log;version=1.3, junit.framework;version=1.3");
//felixConf.put("org.osgi.framework.system.packages.extra", "org.osgi.service.log, junit.framework");
felixConf.put("org.osgi.framework.storage", m_targetDir.getAbsolutePath() + "/felix-cache");
felixConf.put(Constants.FRAMEWORK_BUNDLE_PARENT, Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK);
if (configuration != null) {
felixConf.putAll(configuration);
// Check boot delegation
// String bd = (String) felixConf.get("org.osgi.framework.bootdelegation");
//// if (bd.indexOf("junit.framework") == -1) {
//// bd.concat(", junit.framework");
//// }
//// if (bd.indexOf("org.osgi.service.log") == -1) {
//// bd.concat(", org.osgi.service.log");
//// }
// if (bd.indexOf("net.sourceforge.cobertura.coveragedata") == -1) {
// bd.concat(", net.sourceforge.cobertura.coveragedata");
// }
}
System.out.println("");
System.out.println("-------------------------------------------------------");
System.out.println(" T E S T S");
System.out.println("-------------------------------------------------------");
Felix felix = new Felix(felixConf);
try {
felix.start();
} catch (BundleException e) {
e.printStackTrace();
}
getLog().info("Felix started - Waiting for stability");
waitForStability(felix.getBundleContext());
getLog().info("Bundle Stability Reached - Waiting for runner service");
Object runner = waitForRunnerService(felix.getBundleContext());
if (runner == null) {
throw new MojoFailureException("Cannot intialize the testing framework");
}
getLog().info("Runner Service available");
invokeRun(runner, felix.getBundleContext());
try {
felix.stop();
felix.waitForStop(5000);
// Delete felix-cache
File cache = new File(m_targetDir.getAbsolutePath() + "/felix-cache");
cache.delete();
} catch (Exception e) {
getLog().error(e);
}
if (m_totalErrors > 0 || m_totalFailures > 0) {
if (! testFailureIgnore) {
throw new MojoFailureException("There are test failures. \n\n"
+ "Please refer to " + m_reportsDirectory.getAbsolutePath()
+ " for the individual test results.");
} else {
getLog().warn("There are test failures. \n\n"
+ "Please refer to " + m_reportsDirectory.getAbsolutePath()
+ " for the individual test results.");
}
}
}
/**
* Waits for stability:
* <ul>
* <li>all bundles are activated
* <li>service count is stable
* </ul>
* If the stability can't be reached after a specified time,
* the method throws a {@link MojoFailureException}.
* @param context the bundle context
* @throws MojoFailureException when the stability can't be reach after a several attempts.
*/
private void waitForStability(BundleContext context) throws MojoFailureException {
// Wait for bundle initialization.
boolean bundleStability = getBundleStability(context);
int count = 0;
while (!bundleStability && count < 500) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// Interrupted
}
count++;
bundleStability = getBundleStability(context);
}
if (count == 500) {
getLog().error("Bundle stability isn't reached after 500 tries");
dumpBundles(context);
throw new MojoFailureException("Cannot reach the bundle stability");
}
//DEBUG
Bundle[] bundles = context.getBundles();
getLog().debug("Bundles List");
for (int i = 0; i < bundles.length; i++) {
getLog().debug(bundles[i].getSymbolicName() + " - " + bundles[i].getVersion() + " - " + bundles[i].getState());
}
getLog().debug("--------------");
// END DEBUG
boolean serviceStability = false;
count = 0;
int count1 = 0;
int count2 = 0;
while (! serviceStability && count < 500) {
try {
ServiceReference[] refs = context.getServiceReferences((String) null, null);
count1 = refs.length;
Thread.sleep(500);
refs = context.getServiceReferences((String) null, null);
count2 = refs.length;
serviceStability = count1 == count2;
} catch (Exception e) {
getLog().error(e);
serviceStability = false;
// Nothing to do, while recheck the condition
}
count++;
}
if (count == 500) {
getLog().error("Service stability isn't reached after 500 tries (" + count1 + " != " + count2);
dumpBundles(context);
throw new MojoFailureException("Cannot reach the service stability");
}
try {
ServiceReference[] refs = context.getServiceReferences((String) null, null);
getLog().debug("Service List");
for (int i = 0; i < refs.length; i++) {
String[] itfs = (String[]) refs[i].getProperty(Constants.OBJECTCLASS);
List list = Arrays.asList(itfs);
if (list.contains("org.apache.felix.ipojo.architecture.Architecture")) {
getLog().debug(list.toString() + " - " + refs[i].getProperty("architecture.instance"));
} else {
getLog().debug(list.toString());
}
}
getLog().debug("--------------");
} catch (Exception e) {}
}
/**
* Are bundle stables.
* @param bc the bundle context
* @return <code>true</code> if every bundles are activated.
*/
private boolean getBundleStability(BundleContext bc) {
boolean stability = true;
Bundle[] bundles = bc.getBundles();
for (int i = 0; i < bundles.length; i++) {
stability = stability && (bundles[i].getState() == Bundle.ACTIVE);
}
return stability;
}
/**
* Computes the URL list of bundles to install from
* the <code>bundles</code> parameter.
* @return the list of url of bundles to install.
*/
private List parseBundleList() {
List toDeploy = new ArrayList();
if (bundles == null) {
return toDeploy;
}
for (int i = 0; i < bundles.size(); i++) {
String bundle = (String) bundles.get(i);
try {
URL url = new URL(bundle);
toDeploy.add(url);
} catch (MalformedURLException e) {
// Not a valid url,
getLog().error(bundle + " is not a valid url, bundle ignored");
}
}
return toDeploy;
}
/**
* Computes the URL list of bundles to install from
* <code>test</code> scoped dependencies.
* @return the list of url of bundles to install.
*/
private List getTestBundle() {
List toDeploy = new ArrayList();
Set dependencies = m_project.getDependencyArtifacts();
for (Iterator artifactIterator = dependencies.iterator(); artifactIterator.hasNext();) {
Artifact artifact = (Artifact) artifactIterator.next();
if (artifact.getScope() != null) { // Select not null scope... [Select every bundles with a scope TEST, COMPILE and RUNTIME]
File file = artifact.getFile();
try {
if (file.exists()) {
if (file.getName().endsWith("jar") && ! file.getName().startsWith("org.apache.felix.ipojo-")) {
JarFile jar = new JarFile(file);
if (jar.getManifest().getMainAttributes().getValue("Bundle-ManifestVersion") != null) {
toDeploy.add(file.toURI().toURL());
}
} // else {
// getLog().info("The artifact " + artifact.getFile().getName() + " is not a Jar file.");
// }
} else {
getLog().info("The artifact " + artifact.getFile().getName() + " does not exist.");
}
} catch (Exception e) {
getLog().error(file + " is not a valid bundle, this artifact is ignored");
}
}
}
return toDeploy;
}
/**
* Waits until the {@link OSGiJunitRunner} service
* is published.
* @param bc the bundle context
* @return the {@link OSGiJunitRunner} service object.
*/
private Object waitForRunnerService(BundleContext bc) {
ServiceReference ref = bc.getServiceReference(org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner.class.getName());
int count = 0;
while (ref == null && count < 1000) {
try {
Thread.sleep(5);
count++;
} catch (InterruptedException e) {
// Nothing to do
}
ref = bc.getServiceReference(org.apache.felix.ipojo.junit4osgi.OSGiJunitRunner.class.getName());
}
if (ref != null) {
return bc.getService(ref);
}
getLog().error("Junit Runner service unavailable");
dumpServices(bc);
return null;
}
/**
* Executes tests by using reflection.
* @param runner the {@link OSGiJunitRunner} service object
* @param bc the bundle context
*/
private void invokeRun(Object runner, BundleContext bc) {
Method getTest;
try {
getTest = runner.getClass().getMethod("getTests", new Class[0]);
List tests = (List) getTest.invoke(runner, new Object[0]);
Method run = getRunMethod(runner);
for (int i = 0; i < tests.size(); i++) {
executeTest(runner, (Test) tests.get(i), run, bc);
}
System.out.println("\nResults :");
if (m_failures.size() > 0) {
System.out.println("\nFailed tests:");
for (int i = 0; i < m_failures.size(); i++) {
TestResult tr = (TestResult) m_failures.get(i);
Enumeration e = tr.failures();
while (e.hasMoreElements()) {
TestFailure tf = (TestFailure) e.nextElement();
System.out.println(" " + tf.toString());
}
}
}
if (m_failures.size() > 0) {
System.out.println("\nTests in error:");
for (int i = 0; i < m_errors.size(); i++) {
TestResult tr = (TestResult) m_errors.get(i);
Enumeration e = tr.errors();
while (e.hasMoreElements()) {
TestFailure tf = (TestFailure) e.nextElement();
System.out.println(" " + tf.toString());
}
}
}
System.out.println("\nTests run: " + m_total + ", Failures: " + m_totalFailures + ", Errors:" + m_totalErrors + "\n");
} catch (Exception e) {
getLog().error(e);
}
}
/**
* Gets the {@link OSGiJunitRunner#run(long)} method from the
* {@link OSGiJunitRunner} service object.
* @param runner the {@link OSGiJunitRunner} service object.
* @return the Method object for the <code>run</code> method.
*/
private Method getRunMethod(Object runner) {
Method[] methods = runner.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals("run")
&& methods[i].getParameterTypes().length == 1
&& ! methods[i].getParameterTypes()[0].equals(Long.TYPE)) {
return methods[i];
}
}
getLog().error("Cannot find the run method");
return null;
}
/**
* Computes the name of the given test.
* This method calls the {@link TestCase#getName()}
* method by reflection. If no success, invokes the
* {@link Object#toString()} method.
* @param test the test object.
* @return the name of the given test.
*/
private String getTestName(Object test) {
try {
Method getName = test.getClass().getMethod("getName", new Class[0]);
String name = (String) getName.invoke(test, new Object[0]);
if (name == null) {
name = test.toString();
}
return name;
} catch (Exception e) {
getLog().error(e);
return null;
}
}
/**
* Executes the given test.
* @param runner the {@link OSGiJunitRunner} service object
* @param test the test to run
* @param run the {@link OSGiJunitRunner#run(long)} method
* @param bc the bundle context
*/
private void executeTest(Object runner, Test test, Method run,
BundleContext bc) {
try {
XMLReport report = new XMLReport();
String name = getTestName(test);
System.out.println("Running " + name);
TestResult tr = new TestResult();
tr.addListener(new ResultListener(report));
test.run(tr);
m_results.add(tr);
if (tr.wasSuccessful()) {
System.out.println("Tests run: "
+ tr.runCount()
+ ", Failures: "
+ tr.failureCount()
+ ", Errors: "
+ tr.errorCount()
+ ", Time elapsed: "
+ report.elapsedTimeAsString(report.m_endTime
- report.m_endTime) + " sec");
} else {
System.out.println("Tests run: "
+ tr.runCount()
+ ", Failures: "
+ tr.failureCount()
+ ", Errors: "
+ tr.errorCount()
+ ", Time elapsed: "
+ report.elapsedTimeAsString(report.m_endTime
- report.m_endTime) + " sec <<< FAILURE!");
if (tr.errorCount() > 0) {
m_errors.add(tr);
}
if (tr.failureCount() > 0) {
m_failures.add(tr);
}
}
m_total += tr.runCount();
m_totalFailures += tr.failureCount();
m_totalErrors += tr.errorCount();
report.generateReport(test, tr, m_reportsDirectory, bc, felixConf);
} catch (Exception e) {
getLog().error(e);
}
}
/**
* Prints the bundle list.
* @param bc the bundle context.
*/
public void dumpBundles(BundleContext bc) {
getLog().info("Bundles:");
Bundle[] bundles = bc.getBundles();
for (int i = 0; i < bundles.length; i++) {
getLog().info(bundles[i].getSymbolicName() + " - " + bundles[i].getState());
}
}
/**
* Prints the service list.
* @param bc the bundle context.
*/
public void dumpServices(BundleContext bc) {
getLog().info("Services:");
ServiceReference[] refs = null;
try {
refs = bc.getAllServiceReferences(null, null);
} catch (InvalidSyntaxException e) {
e.printStackTrace();
}
for (int i = 0; i < refs.length; i++) {
String[] itfs = (String[]) refs[i].getProperty(Constants.OBJECTCLASS);
String bundle = refs[i].getBundle().getSymbolicName();
getLog().info(bundle + " : " + Arrays.toString(itfs));
}
}
public LogServiceImpl getLogService() {
return m_logService;
}
private class ResultListener implements TestListener {
/**
* The XML Report.
*/
private XMLReport m_report;
/**
* Check if the test has failed or thrown an
* error.
*/
private boolean m_abort;
/**
* Backup of the {@link System#out} stream.
*/
private PrintStream m_outBackup = System.out;
/**
* Backup of the {@link System#err} stream.
*/
private PrintStream m_errBackup = System.err;
/**
* The output stream used during the test execution.
*/
private StringOutputStream m_out = new StringOutputStream();
/**
* The error stream used during the test execution.
*/
private StringOutputStream m_err = new StringOutputStream();;
/**
* Creates a ResultListener.
* @param report the XML report
*/
public ResultListener(XMLReport report) {
this.m_report = report;
}
/**
* An error occurs during the test execution.
* @param test the test in error
* @param throwable the thrown error
* @see junit.framework.TestListener#addError(junit.framework.Test, java.lang.Throwable)
*/
public void addError(Test test, Throwable throwable) {
m_report.testError(test, throwable, m_out.toString(), m_err.toString(), getLogService().getLoggedMessages());
m_abort = true;
}
/**
* An failure occurs during the test execution.
* @param test the failing test
* @param assertionfailederror the failure
* @see junit.framework.TestListener#addFailure(junit.framework.Test, junit.framework.AssertionFailedError)
*/
public void addFailure(Test test,
AssertionFailedError assertionfailederror) {
m_report.testFailed(test, assertionfailederror, m_out.toString(), m_err.toString(), getLogService().getLoggedMessages());
m_abort = true;
}
/**
* The test ends.
* @param test the test
* @see junit.framework.TestListener#endTest(junit.framework.Test)
*/
public void endTest(Test test) {
if (!m_abort) {
m_report.testSucceeded(test);
}
System.setErr(m_errBackup);
System.setOut(m_outBackup);
getLogService().reset();
}
/**
* The test starts.
* @param test the test
* @see junit.framework.TestListener#startTest(junit.framework.Test)
*/
public void startTest(Test test) {
m_abort = false;
m_report.testStarting();
System.setErr(new ReportPrintStream(m_err,m_errBackup, hideOutputs));
System.setOut(new ReportPrintStream(m_out, m_outBackup, hideOutputs));
getLogService().enableOutputStream();
}
}
}