Package org.springframework.osgi.test

Source Code of org.springframework.osgi.test.AbstractOsgiTests

/*
* Copyright 2006-2008 the original author or authors.
*
* 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 org.springframework.osgi.test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;

import junit.framework.Protectable;
import junit.framework.TestCase;
import junit.framework.TestResult;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.osgi.io.OsgiBundleResourceLoader;
import org.springframework.osgi.test.internal.holder.OsgiTestInfoHolder;
import org.springframework.osgi.test.internal.util.TestUtils;
import org.springframework.osgi.test.platform.OsgiPlatform;
import org.springframework.osgi.util.OsgiBundleUtils;
import org.springframework.osgi.util.OsgiPlatformDetector;
import org.springframework.osgi.util.OsgiStringUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

/**
* Base test for OSGi environments. Takes care of configuring the chosen OSGi
* platform, starting it, installing a number of bundles and delegating the test
* execution to a test copy that runs inside OSGi.
*
* @author Costin Leau
*/
public abstract class AbstractOsgiTests extends AbstractOptionalDependencyInjectionTests {

  private static final String UTF_8_CHARSET = "UTF-8";

  // JVM shutdown hook
  private static Thread shutdownHook;

  // the OSGi fixture
  private static OsgiPlatform osgiPlatform;

  // OsgiPlatform bundle context
  private static BundleContext platformContext;

  // JUnit Service
  private static Object service;

  // JUnitService trigger
  private static Method serviceTrigger;

  // the test results used by the triggering test runner
  private TestResult originalResult;

  // OsgiResourceLoader
  protected ResourceLoader resourceLoader;

  /**
   * Hook for JUnit infrastructures which can't reuse this class hierarchy.
   * This instance represents the test which will be executed by
   * AbstractOsgiTests & co.
   */
  private TestCase osgiJUnitTest = this;

  private static final String ACTIVATOR_REFERENCE = "org.springframework.osgi.test.JUnitTestActivator";


  /**
   * Default constructor. Constructs a new <code>AbstractOsgiTests</code>
   * instance.
   */
  public AbstractOsgiTests() {
    super();
  }

  /**
   * Constructs a new <code>AbstractOsgiTests</code> instance.
   *
   * @param name test name
   */
  public AbstractOsgiTests(String name) {
    super(name);
  }

  /**
   * Returns the test framework bundles (part of the test setup). Used by the
   * test infrastructure. Override this method <i>only</i> if you want to
   * change the jars used by default, by the testing infrastructure.
   *
   * User subclasses should use {@link #getTestBundles()} instead.
   *
   * @return the array of test framework bundle resources
   */
  protected abstract Resource[] getTestFrameworkBundles();

  /**
   * Returns the bundles required for the test execution.
   *
   * @return the array of bundles to install
   */
  protected abstract Resource[] getTestBundles();

  /**
   * Creates (and configures) the OSGi platform.
   *
   * @return OSGi platform instance
   * @throws Exception if the platform creation fails
   */
  protected abstract OsgiPlatform createPlatform() throws Exception;

  /**
   * Pre-processes the bundle context. This call back gives access to the
   * platform bundle context before any bundles have been installed. The
   * method is invoked <b>after</b> starting the OSGi environment but
   * <b>before</b> any bundles are installed in the OSGi framework.
   *
   * <p/> Normally, this method is called only once during the lifecycle of a
   * test suite.
   *
   * @param platformBundleContext the platform bundle context
   * @throws Exception if processing the bundle context fails
   * @see #postProcessBundleContext(BundleContext)
   *
   */
  protected void preProcessBundleContext(BundleContext platformBundleContext) throws Exception {
  }

  /**
   * Post-processes the bundle context. This call back gives access to the
   * platform bundle context after the critical test infrastructure bundles
   * have been installed and started. The method is invoked <b>after</b>
   * preparing the OSGi environment for the test execution but <b>before</b>
   * any test is executed.
   *
   * The given <code>BundleContext</code> belongs to the underlying OSGi
   * framework.
   *
   * <p/> Normally, this method is called only one during the lifecycle of a
   * test suite.
   *
   * @param platformBundleContext the platform bundle context
   * @see #preProcessBundleContext(BundleContext)
   */
  protected void postProcessBundleContext(BundleContext platformBundleContext) throws Exception {
  }

  //
  // JUnit overridden methods.
  //

  /**
   * {@inheritDoc}
   *
   * <p/> Replacement run method. Gets a hold of the TestRunner used for
   * running this test so it can populate it with the results retrieved from
   * OSGi.
   */
  public final void run(TestResult result) {

    // get a hold of the test result
    originalResult = result;

    result.startTest(osgiJUnitTest);
    result.runProtected(osgiJUnitTest, new Protectable() {

      public void protect() throws Throwable {
        AbstractOsgiTests.this.runBare();
      }
    });
    result.endTest(osgiJUnitTest);

    // super.run(result);
  }

  public void runBare() throws Throwable {
    // add ConditionalTestCase behaviour

    // getName will return the name of the method being run
    if (isDisabledInThisEnvironment(getName())) {
      recordDisabled();
      logger.warn("**** " + getClass().getName() + "." + getName() + " disabled in this environment: "
          + "Total disabled tests=" + getDisabledTestCount());
      return;
    }
    else {
      prepareTestExecution();
      try {
        // invoke OSGi test run
        invokeOSGiTestExecution();
        readTestResult();
      }
      finally {
        // nothing to clean up
      }
    }
  }

  //
  // OSGi testing infrastructure setup.
  //

  /**
   * Starts the OSGi platform and install/start the bundles (happens once for
   * the all test runs)
   *
   * @throws Exception
   */
  private void startup() throws Exception {
    if (osgiPlatform == null) {

      boolean debug = logger.isDebugEnabled();

      // make sure the platform is closed properly
      registerShutdownHook();

      osgiPlatform = createPlatform();
      // start platform
      if (debug)
        logger.debug("About to start " + osgiPlatform);
      osgiPlatform.start();
      // platform context
      platformContext = osgiPlatform.getBundleContext();

      // log platform name and version
      logPlatformInfo(platformContext);

      // hook before the OSGi platform is setup but right after is has
      // been started
      preProcessBundleContext(platformContext);

      // install bundles (from the local system/classpath)
      Resource[] bundleResources = locateBundles();

      Bundle[] bundles = new Bundle[bundleResources.length];
      for (int i = 0; i < bundles.length; i++) {
        bundles[i] = installBundle(bundleResources[i]);
      }
     
      // start bundles
      for (int i = 0; i < bundles.length; i++) {
        startBundle(bundles[i]);
      }

      // hook after the OSGi platform has been setup
      postProcessBundleContext(platformContext);

      initializeServiceRunnerInvocationMethods();
    }
  }

  // concatenate bundles to install
  private Resource[] locateBundles() {
    Resource[] testFrameworkBundles = getTestFrameworkBundles();
    Resource[] testBundles = getTestBundles();

    if (testFrameworkBundles == null)
      testFrameworkBundles = new Resource[0];
    if (testBundles == null)
      testBundles = new Resource[0];

    Resource[] allBundles = new Resource[testFrameworkBundles.length + testBundles.length];
    System.arraycopy(testFrameworkBundles, 0, allBundles, 0, testFrameworkBundles.length);
    System.arraycopy(testBundles, 0, allBundles, testFrameworkBundles.length, testBundles.length);
    return allBundles;
  }

  /**
   * Logs the underlying OSGi information (which can be tricky).
   *
   */
  private void logPlatformInfo(BundleContext context) {
    StringBuffer platformInfo = new StringBuffer();

    // add platform information
    platformInfo.append(osgiPlatform);
    platformInfo.append(" [");
    // Version
    platformInfo.append(OsgiPlatformDetector.getVersion(context));
    platformInfo.append("]");
    logger.info(platformInfo + " started");
  }

  /**
   * Installs an OSGi bundle from the given location.
   *
   * @param location
   * @return
   * @throws Exception
   */
  private Bundle installBundle(Resource location) throws Exception {
    Assert.notNull(platformContext);
    Assert.notNull(location);
    if (logger.isDebugEnabled())
      logger.debug("Installing bundle from location " + location.getDescription());

    String bundleLocation;

    try {
      bundleLocation = URLDecoder.decode(location.getURL().toExternalForm(), UTF_8_CHARSET);
    }
    catch (Exception ex) {
      // the URL cannot be created, fall back to the description
      bundleLocation = location.getDescription();
    }

    return platformContext.installBundle(bundleLocation, location.getInputStream());
  }

  /**
   * Starts a bundle and prints a nice logging message in case of failure.
   *
   * @param bundle
   * @return
   * @throws BundleException
   */
  private void startBundle(Bundle bundle) throws BundleException {
    boolean debug = logger.isDebugEnabled();
    String info = "[" + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "|" + bundle.getLocation() + "]";

    if (!OsgiBundleUtils.isFragment(bundle)) {
      if (debug)
        logger.debug("Starting " + info);
      try {
        bundle.start();
      }
      catch (BundleException ex) {
        logger.error("cannot start bundle " + info, ex);
        throw ex;
      }
    }

    else if (debug)
      logger.debug(info + " is a fragment; start not invoked");
  }

  //
  // Delegation methods for OSGi execution and initialization
  //

  // runs outside OSGi
  /**
   * Prepares test execution - the OSGi platform will be started (if needed)
   * and cached for the test suite execution.
   */
  private void prepareTestExecution() throws Exception {

    if (getName() == null)
      throw new IllegalArgumentException("no test specified");

    // clear test results
    OsgiTestInfoHolder.INSTANCE.clearResults();
    // set test class
    OsgiTestInfoHolder.INSTANCE.setTestClassName(osgiJUnitTest.getClass().getName());

    // start OSGi platform (the caching is done inside the method).
    try {
      startup();
    }
    catch (Exception e) {
      logger.debug("Caught exception starting up", e);
      throw e;
    }

    if (logger.isTraceEnabled())
      logger.trace("Writing test name [" + getName() + "] to OSGi");

    // write test name to OSGi
    // set test method name
    OsgiTestInfoHolder.INSTANCE.setTestMethodName(getName());
  }

  /**
   * Delegates the test execution to the OSGi copy.
   *
   * @throws Exception
   */
  private void invokeOSGiTestExecution() throws Exception {
    Assert.notNull(serviceTrigger, "no executeTest() method found on: " + service.getClass());
    try {
      serviceTrigger.invoke(service, null);
    }
    catch (InvocationTargetException ex) {
      Throwable th = ex.getCause();
      if (th instanceof Exception)
        throw ((Exception) th);
      else
        throw ((Error) th);
    }
  }

  /**
   * Determines through reflection the methods used for invoking the
   * TestRunnerService.
   *
   * @throws Exception
   */
  private void initializeServiceRunnerInvocationMethods() throws Exception {
    // get JUnit test service reference
    // this is a loose reference - update it if the JUnitTestActivator
    // class is
    // changed.

    BundleContext ctx = getRuntimeBundleContext();

    ServiceReference reference = ctx.getServiceReference(ACTIVATOR_REFERENCE);
    Assert.notNull(reference, "no OSGi service reference found at " + ACTIVATOR_REFERENCE);

    service = ctx.getService(reference);
    Assert.notNull(service, "no service found for reference: " + reference);

    serviceTrigger = service.getClass().getDeclaredMethod("executeTest", null);
    ReflectionUtils.makeAccessible(serviceTrigger);
    Assert.notNull(serviceTrigger, "no executeTest() method found on: " + service.getClass());
  }

  /**
   * Tries to get the bundle context for spring-osgi-test-support bundle. This
   * is useful on platform where the platformContext or system BundleContext
   * doesn't behave like a normal context.
   *
   * Will fallback to {@link #platformContext}.
   *
   * @return
   */
  private BundleContext getRuntimeBundleContext() {

    // read test bundle id property
    Long id = OsgiTestInfoHolder.INSTANCE.getTestBundleId();

    BundleContext ctx = null;
    if (id != null)
      try {
        ctx = OsgiBundleUtils.getBundleContext(platformContext.getBundle(id.longValue()));
      }
      catch (RuntimeException ex) {
        logger.trace("cannot determine bundle context for bundle " + id, ex);
      }

    return (ctx == null ? platformContext : ctx);
  }

  // runs outside OSGi
  private void readTestResult() {
    if (logger.isTraceEnabled())
      logger.trace("Reading OSGi results for test [" + getName() + "]");

    // copy results from OSGi into existing test result
    TestUtils.cloneTestResults(OsgiTestInfoHolder.INSTANCE, originalResult, osgiJUnitTest);

    if (logger.isTraceEnabled())
      logger.debug("Test[" + getName() + "]'s result read");
  }

  /**
   * Special shutdown hook.
   */
  private void registerShutdownHook() {
    if (shutdownHook == null) {
      // No shutdown hook registered yet.
      shutdownHook = new Thread() {

        public void run() {
          shutdownTest();
        }
      };
      Runtime.getRuntime().addShutdownHook(shutdownHook);
    }
  }

  /**
   * Cleanup for the test suite.
   */
  private void shutdownTest() {
    logger.info("Shutting down OSGi platform");
    if (osgiPlatform != null) {
      try {
        osgiPlatform.stop();
      }
      catch (Exception ex) {
        // swallow
        logger.warn("Shutdown procedure threw exception " + ex);
      }
      osgiPlatform = null;
    }
  }

  //
  // OsgiJUnitTest execution hooks. Used by the test framework.
  //

  /**
   * Set the bundle context to be used by this test.
   *
   * <p/> This method is called automatically by the test infrastructure after
   * the OSGi platform is being setup.
   */
  private void injectBundleContext(BundleContext bundleContext) {
    this.bundleContext = bundleContext;
    // instantiate ResourceLoader
    this.resourceLoader = new OsgiBundleResourceLoader(bundleContext.getBundle());
  }

  /**
   * Set the underlying OsgiJUnitTest used for the test delegation.
   *
   * <p/> This method is called automatically by the test infrastructure after
   * the OSGi platform is being setup.
   *
   * @param test
   */
  private void injectOsgiJUnitTest(TestCase test) {
    this.osgiJUnitTest = test;
  }

  /**
   * the setUp version for the OSGi environment.
   *
   * @throws Exception
   */
  private void osgiSetUp() throws Exception {
    // call the normal onSetUp
    setUp();
  }

  private void osgiTearDown() throws Exception {
    // call the normal tearDown
    tearDown();
  }

  /**
   * Actual test execution (delegates to the superclass implementation).
   *
   * @throws Throwable
   */
  private void osgiRunTest() throws Throwable {
    super.runTest();
  }

}
TOP

Related Classes of org.springframework.osgi.test.AbstractOsgiTests

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.