/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed 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 net.java.sip.communicator.slick.runner; import java.io.*; import java.util.*; import junit.framework.*; import net.java.sip.communicator.util.*; import org.osgi.framework.*; /** * Detects and runs all Service Implementation Compatibility Kits (SLICKs)inside * the current OSGI instance. The SipCommunicatorSlickRunner produces an xml log * file following ant format rules (so that it could be used by CruiseControl) * and stores it inside the directory indicated in the * net.java.sip.communicator.slick.runner.OUTPUT_DIR property (default is * test-reports). * <p> * In order for the SipCommunicatorSlickRunner to detect all SLICKs they * needs to be registered as services in the OSGI environment prior to the * actication of the runner, and their names need to be specified in a * whitespace separated list registered against the * net.java.sip.communicator.slick.runner.TEST_LIST system property. * <p> * After running all unit tests the SipcCommunicatorSlickRunner will try to * gracefully shutdown the Felix OSGI framework (if it fails it'll shut it * down rudely ;) ) and will System.exit() with an error code in case any * test failures occurred or with 0 if all tests passed. * * @author Emil Ivov */ public class SipCommunicatorSlickRunner extends TestSuite implements BundleActivator { private Logger logger = Logger.getLogger(getClass().getName()); /** * The name of the property indicating the Directory where test reports * should be stored. */ private static final String OUTPUT_DIR_PROPERTY_NAME = "net.java.sip.communicator.slick.runner.OUTPUT_DIR"; /** * A default name for the Directory where test reports should be stored. */ private static final String DEFAULT_OUTPUT_DIR = "test-reports"; /** * The name of the property indicating the name of the file where test * reports should be stored. */ private static final String OUTPUT_FILE_NAME = "sip-communicator.unit.test.reports.xml"; /** * The name of the property that contains the list of Service ICKs that * we'd have to run. */ private static final String TEST_LIST_PROPERTY_NAME = "net.java.sip.communicator.slick.runner.TEST_LIST"; /** * A reference to the bundle context received when activating the test * runner. */ private BundleContext bundleContext = null; /** * The number of failures and errors that occurred during unit testing. */ private int errCount = 0; /** * The number of unit tests run by the slick runner. */ private int runCount = 0; /** * Starts the slick runner, runs all unit tests indicated in the * TEST_LIST property, and exits with an error code corresponding to whether * or there were failure while running the tests. * @param bc BundleContext * @throws Exception */ public void start(BundleContext bc) throws Exception { logger.logEntry(); try { bundleContext = bc; setName(getClass().getName()); //Let's now see what tests have been scheduled for execution. String tests = System.getProperty(TEST_LIST_PROPERTY_NAME); if (tests == null || tests.trim().length() == 0) { tests = ""; } logger.debug("specfied test list is: " + tests); StringTokenizer st = new StringTokenizer(tests); String[] ids = new String[st.countTokens()]; int n = 0; while (st.hasMoreTokens()) { ids[n++] = st.nextToken().trim(); } //Determine the file specified for storing test results. String outputDirName = System.getProperty(OUTPUT_DIR_PROPERTY_NAME); if (outputDirName == null || outputDirName.trim().length() == 0) { outputDirName = DEFAULT_OUTPUT_DIR; } File outputDir = new File(outputDirName); if (!outputDir.exists()) { outputDir.mkdirs(); } for (int i = 0; i < ids.length; i++) { logger.info("=========== Running tests in : " + ids[i] + " ==========="); TestSuite slick = getTestSuite(bc, ids[i]); logger.debug("with " + slick.countTestCases() + " tests."); File outputFile = new File(outputDir, "SC-TEST-" + ids[i] + ".xml"); if (!outputFile.exists()) { outputFile.createNewFile(); } logger.debug("specified reports file: " + outputFile.getCanonicalFile()); OutputStream out = new FileOutputStream(outputFile); XmlFormatter fmtr = new XmlFormatter(new PrintStream(out)); TestResult res = ScTestRunner.run(slick, fmtr); errCount += res.errorCount() + res.failureCount(); runCount += res.runCount(); out.flush(); out.close(); } //output results logger.info(""); logger.info("===================================================="); logger.info("We ran " + runCount + " tests and encountered " + errCount + " errors and failures."); logger.info("===================================================="); logger.info(""); //in order to shutdown felix we'd first need to wait for it to //complete it's start process, so we'll have to implement shutdown //in a framework listener. bc.addFrameworkListener(new FrameworkListener(){ public void frameworkEvent(FrameworkEvent event){ if( event.getType() == FrameworkEvent.STARTED) { try { //first stop the system bundle thus causing oscar //to stop all user bundles and shut down. bundleContext.getBundle(0).stop(); } catch (BundleException ex) { logger.error("Failed to gently shutdown Felix",ex); } //if everything is ok then the stop call shouldn't have //exited the the program since we must have set the //"felix.embedded.execution" property to true //we could therefore now System.exit() with a code //indicating whether or not all unit tests went wrong // After updating to Felix 3.2.2, System.exit locks // the tests and it never stop, so it has to be removed // or in new thread. new Thread(new Runnable() { public void run() { System.exit(errCount > 0? -1: 0); } }).start(); } } }); } finally { logger.logExit(); } } /** * Dummy impl * @param bc BundleContext */ public void stop(BundleContext bc) { logger.debug("Stopping!"); } /** * Looks through the osgi framework for a service with a "service.pid" * property set to <tt>id</tt>. * @param bc the BundleContext where the service is to be looked for. * @param id the value of the "service.pid" property for the specified * service. * @return a TestSuite service corresponding the specified <tt>id</tt> * or a junit TestCase impl wrapping an exception in case we failed to * retrieve the service for some reason. */ public TestSuite getTestSuite(BundleContext bc, final String id) { Object obj = null; try { ServiceReference[] srl = bc.getServiceReferences( (String) null, "(service.pid=" + id + ")"); if (srl == null || srl.length == 0) { obj = new TestCase("No id=" + id) { @Override public void runTest() { throw new IllegalArgumentException("No test with id=" + id); } }; } if (srl != null && srl.length != 1) { obj = new TestCase("Multiple id=" + id) { @Override public void runTest() { throw new IllegalArgumentException( "More than one test with id=" + id); } }; } if (obj == null) { obj = bc.getService(srl[0]); } } catch (Exception e) { obj = new TestCase("Bad filter syntax id=" + id) { @Override public void runTest() { throw new IllegalArgumentException("Bad syntax id=" + id); } }; } if (! (obj instanceof Test)) { final Object oldObj = obj; obj = new TestCase("ClassCastException") { @Override public void runTest() { throw new ClassCastException("Service implements " + oldObj.getClass().getName() + " instead of " + Test.class.getName()); } }; } Test test = (Test) obj; TestSuite suite; if (test instanceof TestSuite) { suite = (TestSuite) test; } else { suite = new TestSuite(id); suite.addTest(test); } return suite; } }