| /* |
| * 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(); |
| } |
| |
| } |
| |
| } |