7 Replies Latest reply on Jun 24, 2010 11:00 PM by Thomas Diesler

    Arquillian support for OSGi

    Thomas Diesler Master

      Folks,

       

      I took a look at Arquillian to see how our OSGi effort would fit with the Arquillian testing strategy. Writing OSGi test cases that interact with the framework infrastructure and services provided by other (bundle) deployments is very simmilar to the "in-container" testing notion of Arquillian.

       

      First of all I'd like to say that, the API design, the documentation and last but not least the quality of the code base is really impressive - so it was very easy for me to do the first cut. Well done!

       

      In JBoss OSGi we have the Husky Test Framework for embedded and remote OSGi bundle testing. Many of the concepts in Husky are mirrored in Arquillian. To provide good support for OSGi in Arquillian would essentially mean to port the Husky implementation to the Arquillian API/SPI.

       

      The first cut of this effort provides OSGi bundle testing in an embedded framework.

       

      A sample test case would look like this:

       

      @RunWith(Arquillian.class)
      public class OSGiEmbeddedIntegrationTestCase
      {
         @Deployment
         public static JavaArchive createdeployment()
         {
            final JavaArchive archive = ShrinkWrap.create("test.jar", JavaArchive.class);
            archive.setManifest(new Asset()
            {
               public InputStream openStream()
               {
                  OSGiManifestBuilder builder = OSGiManifestBuilder.newInstance();
                  builder.addBundleSymbolicName(archive.getName());
                  builder.addBundleManifestVersion(2);
                  return builder.openStream();
               }
            });
            return archive.addClasses(OSGiEmbeddedIntegrationTestCase.class);
         } 
       
         @Inject
         Framework framework;
         
         @Test
         public void testFrameworkInjection() throws Exception
         {
            assertNotNull("Framework injected", framework);
         }
       
         @Inject
         Bundle bundle;
         
         @Test
         public void testBundleInjection() throws Exception
         {
            assertNotNull("Bundle injected", bundle);
            assertEquals("Bundle INSTALLED", Bundle.INSTALLED, bundle.getState());
            
            bundle.start();
            assertEquals("Bundle ACTIVE", Bundle.ACTIVE, bundle.getState());
            
            bundle.stop();
            assertEquals("Bundle RESOLVED", Bundle.RESOLVED, bundle.getState());
         }
      }
      
      
      
      

       

      The OSGiEmbeddedContainer bootstraps the Framework and deployes the Bundle. The OSGiTestEnricher injects the Framework and Bundle instances if required.

       

      Stuff that still needs to be done would include

       

      #1 install the test runner as a bundle, so that types (i.e. services) can be accessed from the test case

      #2 support remote target containers

      #3 support multiple bundle deployments

      #4 support bundle deployment during test method execution

      #5 ... other?

       

      In case you agree, I could merge my github osgi branch to Arquillion svn/trunk - just in time for you to talk about it @JBW

       

      Initial support for OSGi bundle testing

      https://jira.jboss.org/browse/ARQ-188

       

      cheers

      -thomas

        • 1. Re: Arquillian support for OSGi
          Aslak Knutsen Master

          from #jbosstesting

           

           

          <aslak> tdiesler, very cool..
          tdiesler, i'm not to familiar with OSGi and JBoss OSGi, but looking at your 'todo' list, that's about what i could think of
          <tdiesler> aslak, with osgi even embedded is somewhat like remote testing because you don't have a shared class space
          <aslak> tdiesler, you have the same requirement as Portal,  being able to control additional deployments during test execution. so i'll figure out a way to expose a Deployer api
          <tdiesler> aslak, my next step would be to bring the test runner in the framework so it can wire to the test case and run it
          aslak, I have ideas about that too
          <aslak> tdiesler, yes, exactly. that must be the main goal. to test from the inside
          tdiesler, JBoss OSGi is just a wrapper on top of 'any' other OSGi impl right, e.g. felix? or is it a OSGi impl ?
          <tdiesler> aslak, the test code I show in the forum only works because it uses types from the system CP only (i.e. not from any bundle) - but I guess you know that
          <aslak> tdiesler, yea, i figured that..
          <tdiesler> aslak, not quite. we have our own osgi core framework
          aslak, which eventually will be the modularity layer for *all* AS deployments
          aslak, i.e. everyting will be turned into a bundle.
          aslak, this is still somewhat secret because I want to avoid the "osgi is crap" arguments ;-)
          <aslak> tdiesler, just thinking how much of this can be made OSGi agnostic and what are JBoss OSGi specific
          <tdiesler> aslak, it should all be osgi framework agnostic
          <tdiesler> aslak, the bootstrap that you see is from the JBossOSGi SPI, which bootstraps any framework in an agnostic way
          <aslak> tdiesler, hmm.. someone else who was looking at creating a OSGi container said something about a OSGI api for creating a framework.. that was part of osgi as far as i understood it
          <aslak> tdiesler, right, how is this different from JBossOSGi  OSGiBootstrap ?
          <tdiesler> aslak, it does however not abstract how you get the FrameworkFactory nor how you get the framework configuration
          theute tsurdilo tdiesler
          <aslak> tdiesler, aa ok
          <aslak> tdiesler, i guess my question is.. if we say Arquillian OSGi contianer use the JBoss OSGi to help with bootstrap etc. will it be just as true as booting felix standalone ?
          <tdiesler> aslak, it uses a ServiceLoader to get the FrameworkFactory and by default looks for a framework-config.properties
          <tdiesler> aslak, our MC based framework of course needs a beans.xml, but you don't see that through the API
          <aslak> tdiesler, MC based? you mean when running in MC or doe sit run MC ?
          brb
          <tdiesler> aslak, MC has the notion of module, capabiliy, requirement that come from classloading.xml. Those concepts mapp nicely to bundle, ExportPackage, ImportPackage, etc
          <tdiesler> aslak, so we implement our core framework using the MC
          aslak, a bundle is an MC deployment
          aslak, anyhow you would not see any of this in arquillian. A dependency on http://www.osgi.org/javadoc/r4v42/ is required however
          aslak, where the standard API falls short, but definitely not any framework specific stuff
          • 2. Re: Arquillian support for OSGi
            Aslak Knutsen Master
            <aslak> tdiesler, my questions really boils down to naming of the container, should i tbe jboss-osgi-4_2 or osgi-4_2
            tdiesler, if from a Felix developer/user perspective, JBoss OSGi runniner Felix == Felix, then i think we can call it osgi-4_2. but if there are any quirks then we should call it jboss-osgi-4_2
            runniner/running
            <tdiesler> aslak, the container must have a dependency on the framework impl
            <aslak> tdiesler, to run sure, but the Arquillian container should not export those dependencies
            <tdiesler> aslak, we could have: embedded-osgi-felix, embedded osgi-equinox, embedded-osgi-[jboss name currently with legal]
            <aslak> tdiesler, it should be up to the user to define Felix 1.2 or 1.3 or what ever versions they have
            tdiesler, so you only need one, osgi-embedded-4_2, the users dep tree should then be enough to fire up Felix or equinox right ?
            <-- michaelschuetz has quit (Quit: Leaving.)
            <aslak> tdiesler, yea, that looks right to me
            <tdiesler> aslak, the dependency in <scope>test</scope>
            <aslak> tdiesler, so to use this.. i would have to add a dep to jboss-osgi and felix impl ?
            <aslak> tdiesler, 4.2 is the lowest version you need right ? that's where the Framework stuff came in ?
            --- jganoff is now known as jganoff|away
            <tdiesler> aslak, yes you plug in your framework impl to the jbosgi-spi
            aslak, yes 4.2.0
            <aslak> ok.. name it osgi-embedded-4_2
            <tdiesler> aslak, ok can do
            tdiesler, i see you export the Archive to disk first.. doesn't installBundle support a input stream ?
            aa right.. for the bundle info creation
            tdiesler, i started some ShrinkWrap BundleArchive implementation that does that runtime a while ago.. I don't have the code on this computer tho, but we can have a look at it when i get back to Oslo. might be something we can reuse
            <tdiesler> aslak, yes. I wanted to discuss that. Ideally, BundleInfo can be created from an in memory VirtualFile obtained from the Archive. The packager could also attach the BundleInfo to the context, instead of me wrapping BI and Archive in a seudo archive
            <aslak> yea, but I'm wondering if we should expose BundleArchive out to the user instead of just the JavaArchive
            tdiesler, this was my attempt
            <tdiesler> aslak, its a hack that allowed me to reuse the VirtualFile
            aslak, yes somethink like that
            <aslak> tdiesler, this uses the BND tool in the background and expose the Archive as a VirtualFile if i remember correctly. pluss adding some convenience methods for setting specific MANIFEST.MF properties
            MANIFEST.MF is generated on export
            <tdiesler> aslak, yes thats what we need
            uses some fancy multi threading stream pipes to write out to a input stream
            <tdiesler> aslak, ok. I'll merge tomorrow if you don't mind. BTW, the github svn mirror should get updated every 30min http://github.com/jbosgi/arquillian
            aslak, in case you need to work/branch/merge etc without svn access
            <aslak> cool..
            • 3. Re: Arquillian support for OSGi
              Thomas Diesler Master

              I merged my stuff to svn/trunk

               

              http://github.com/jbosgi/arquillian/commit/ecacaa19edde26cc338cd269cd0f693a5f9bab3f

               

              Here a few key-points on OSGi integration:

               

              #1 Arquillian must be installed in the OSGi framwork in order to run the tests "in-framework"

               

              Have a look at bundle/pom.xml It aggregates a few arquillian artifacts and exports these packages

               

                            javax.inject
                            org.jboss.arquillian.junit
                            org.jboss.shrinkwrap.api
                            org.jboss.shrinkwrap.api.spec
                            org.junit.runner
                            org.junit
              
              

               

              Because Archillian is run in-framework the test case can now access types from the test bundle

               

               

               @Test
                 public void testBundleInjection() throws Exception
                 {
                    // Assert that the bundle is injected
                    assertNotNull("Bundle injected", bundle);
                    
                    // Assert that the bundle is in state RESOLVED
                    // Note when the test bundle contains the test case it 
                    // must be resolved already when this test method is called
                    assertEquals("Bundle RESOLVED", Bundle.RESOLVED, bundle.getState());
                    
                    // Start the bundle
                    bundle.start();
                    assertEquals("Bundle ACTIVE", Bundle.ACTIVE, bundle.getState());
                    
                    // Assert the bundle context
                    BundleContext context = bundle.getBundleContext();
                    assertNotNull("BundleContext available", context);
                    
                    // Get the service reference
                    ServiceReference sref = context.getServiceReference(SimpleService.class.getName());
                    assertNotNull("ServiceReference not null", sref);
                    
                    // Get the service for the reference
                    SimpleService service = (SimpleService)context.getService(sref);
                    assertNotNull("Service not null", service);
                    
                    // Invoke the service 
                    int sum = service.sum(1, 2, 3);
                    assertEquals(6, sum);
                    
                    // Stop the bundle
                    bundle.stop();
                    assertEquals("Bundle RESOLVED", Bundle.RESOLVED, bundle.getState());
                 }
              
              

               

              #2 The client communicates with the in-framework Arquillian instance via serialized JMX protocol

               

              Have a look at protocols/jmx

               

              When the archillian-bundle.jar is started by the framework

               

              <bean name="OSGiAutoInstallPlugin">
                  <constructor><parameter><inject bean="OSGiBundleManager" /></parameter></constructor>
                  <property name="autoInstall">
                   <list elementClass="java.net.URL">
                    <value>./target/test-libs/bundles/org.osgi.compendium.jar</value>
                   </list>
                  </property>
                  <property name="autoStart">
                   <list elementClass="java.net.URL">
                    <value>./target/test-libs/bundles/arquillian-bundle.jar</value>
                   </list>
                  </property>
                </bean>
              
              

               

              it registers the JMXTestRunnerMBean

               

              /**
               * An MBean to run test methods in container.
               * 
               * @author thomas.diesler@jboss.com
               * @version $Revision: $
               */
              public interface JMXTestRunnerMBean
              {
                 /** The ObjectName for this service: jboss.arquillian:service=jmx-test-runner */
                 String OBJECT_NAME = "jboss.arquillian:service=jmx-test-runner";
                 
                 /**
                  * Runs a test method on the given test class
                  *
                  * @param className the test class name
                  * @param methodName the test method name
                  * @return the input stream to read the {@link TestResult} from
                  */
                 InputStream runTestMethodRemote(String className, String methodName);
                 
                 /**
                  * Runs a test method on the given test class
                  *
                  * @param className the test class name
                  * @param methodName the test method name
                  * @return the {@link TestResult} 
                  */
                 TestResult runTestMethodLocal(String className, String methodName);
              }
              
              

               

              In the framework, the JMXTestRunner loads the test class from its own classloader, which is the arquillian-bundle's class loader. This works because the arquillian-bundle has "DynamicImport-Package: *" defined.

               

              #3 The OSGiTestEnricher injects the system BundleContext and the test Bundle

               

                 public void enrich(Context context, Object testCase)
                 {
                    Class<? extends Object> testClass = testCase.getClass();
                    for (Field field : testClass.getDeclaredFields())
                    {
                       if (field.isAnnotationPresent(Inject.class))
                       {
                          if (field.getType().isAssignableFrom(BundleContext.class))
                          {
                             injectBundleContext(context, testCase, field);
                          }
                          if (field.getType().isAssignableFrom(Bundle.class))
                          {
                             injectBundle(context, testCase, field);
                          }
                       }
                    }
                 }
              
              

               

              I could not figure out, how to add the system BundleContext and the test Bundle to the Arquillian test context. Currently, the OSGiTestEnricher obtains these from a BundleContextHolder MBean

               

              private BundleContext getSystemBundleContext(Context context)
                 {
                    BundleContext bundleContext = context.get(BundleContext.class);
                    if (bundleContext == null)
                       bundleContext = getBundleContextFromHolder();
                    
                    // Make sure this is really the system context
                    bundleContext = bundleContext.getBundle(0).getBundleContext();
                    return bundleContext;
                 }
              
              

               

              and via the PackageAdmin service

               

                 private Bundle getTestBundle(Context context, Class<?> testClass)
                 {
                    Bundle testbundle = context.get(Bundle.class);
                    if (testbundle == null)
                    {
                       // Get the test bundle from PackageAdmin with the test class as key 
                       BundleContext bundleContext = getSystemBundleContext(context);
                       ServiceReference sref = bundleContext.getServiceReference(PackageAdmin.class.getName());
                       PackageAdmin pa = (PackageAdmin)bundleContext.getService(sref);
                       testbundle = pa.getBundle(testClass);
                    }
                    return testbundle;
                 }

               

              respectively. Here I'd like to get feedback on how this is suposed to work. i.e. How can an in-container test runner add arbitrary information to the context that is later seen by the enrichers?

               

              cheers

              -thomas

              • 4. Re: Arquillian support for OSGi
                Thomas Diesler Master

                There are a number of outstanding  issues,which I created here

                 

                ARQ-190      Remote OSGi bundle testing
                ARQ-191     Support the notion of  shrinkwrap BundleArchive
                ARQ-192     Install OSGi bundles in memory
                ARQ-193      Create auxillary OSGi test bundle
                ARQ-194     Support multiple  bundle deployments

                • 5. Re: Arquillian support for OSGi
                  David Bosschaert Expert

                  Very cool stuff! There are some external testing frameworks in opensource land that do something similar for OSGi bundles but none would provide the integration possibilities with other technologies such as the Appserver or CDI!

                   

                  Few questions / ideas from my side:

                  • As the test has access to the BundleContext, it should be able to call BundleContext.installBundle(). So yes, it would be nice if an actual test method could dynamically put together a number of bundles, install them in the system. Start / stop them etc and use the System BundleContext to interrogate the framework about any side effects. Using Shrinkwrap to create bundles on the fly for this would be ideal.

                   

                  • Another interesting feature would be the installation of bundles that are declared as dependencies in the maven pom.xml. Let's say in the pom.xml you have the following dependency:

                  {code:xml}<dependency>

                    <groupId>org.apache.felix</groupId>

                    <artifactId>org.apache.felix.configadmin</artifactId>

                    <version>${configadmin.version}</version>

                  </dependency>{code}

                  then it would be great if you could install that bundle simply through some installBundle("org.apache.felix", "org.apache.felix.configadmin") API which would infer the version from the maven context. Both Pax Exam and Apache ServiceMix testing have this feature and it makes integration with the maven buildsystem really easy.

                   

                  • Besides the model outlined by Thomas above, it would also be nice if it could support controlling multiple framework instances. As mentioned before, an OSGi Framework can be controlled using a standard interface: org.osgi.framework.launch.Framework extends Bundle. In some test scenarios (especially ones involving Distributed OSGi) you'd want to bring up multiple frameworks and run tests bundles in both of them. The unit test would then essentially orchestrate the whole thing and make assertions on behaviour in these two frameworks which can be obtained through the bundle context of either.

                   

                  • What would be the debugging support provided by Arquillian? Ideally I would be able to fire up my IDE and step through the tests if needed. This is most likely already supported, right?

                   

                  In the past I worked with a number of other OSGi testing frameworks which all have drawbacks. I'll list them here hoping that we learn from these:

                  • Pax-Exam: one major drawback of Pax-Exam is that it starts and stops the framework between every test. Whether it's between every test class or every test method escapes me for the moment, but the bottom line is that it's pretty slow to run. This can have a major impact when you have a large number of tests. The principle is sound: start with a pristine framework for every test, but in practise its sometimes a pain!
                  • Spring-DM testing: the problem this framework has is that its built on top of the Spring-DM framework which basically contaminates the testing environment. This is an issue when your own code also depends on Spring-DM and you want to use different versions of bundles that are also used by the test framework. In my experience I was sometimes forced to replace a bundle that was part of a product with one that Spring-DM-testing used. The end result is that your not really testing the end product...
                  • Apache ServiceMix testing is built on top of Spring-DM testing and adds some maven integration. It has therefore the same issues as Spring-DM-testing.
                  • 6. Re: Arquillian support for OSGi
                    Aslak Knutsen Master

                    David Bosschaert wrote:

                     

                    Few questions / ideas from my side:

                    • As the test has access to the BundleContext, it should be able to call BundleContext.installBundle(). So yes, it would be nice if an actual test method could dynamically put together a number of bundles, install them in the system. Start / stop them etc and use the System BundleContext to interrogate the framework about any side effects. Using Shrinkwrap to create bundles on the fly for this would be ideal.

                    We are looking into exposing a 'Deployer' API for these cases. This will give you the option to deploy and undeploy any Archive in any TestCase lifecycle. With OSGi this API needs to be extended to support start/stop etc..

                    The same requirements has come out of the JBoss Portal project.

                     

                    https://jira.jboss.org/browse/ARQ-195

                     

                    • Another interesting feature would be the installation of bundles that are declared as dependencies in the maven pom.xml. Let's say in the pom.xml you have the following dependency:
                    <dependency>
                      <groupId>org.apache.felix</groupId>
                      <artifactId>org.apache.felix.configadmin</artifactId>
                      <version>${configadmin.version}</version>
                    </dependency>

                     

                    then it would be great if you could install that bundle simply through some installBundle("org.apache.felix", "org.apache.felix.configadmin") API which would infer the version from the maven context. Both Pax Exam and Apache ServiceMix testing have this feature and it makes integration with the maven buildsystem really easy.

                    Not sure if we will support to auto deploy of artifacts defined in the pom, but combined with the Deployer API we should support importing artifacs into ShrinkWrap based on the pom metadta.

                    https://jira.jboss.org/browse/SHRINKWRAP-140

                     

                     

                    • Besides the model outlined by Thomas above, it would also be nice if it could support controlling multiple framework instances. As mentioned before, an OSGi Framework can be controlled using a standard interface: org.osgi.framework.launch.Framework extends Bundle. In some test scenarios (especially ones involving Distributed OSGi) you'd want to bring up multiple frameworks and run tests bundles in both of them. The unit test would then essentially orchestrate the whole thing and make assertions on behaviour in these two frameworks which can be obtained through the bundle context of either.

                    This is definitely something we should look into.  https://jira.jboss.org/browse/ARQ-196

                     

                    • What would be the debugging support provided by Arquillian? Ideally I would be able to fire up my IDE and step through the tests if needed. This is most likely already supported, right?

                    Just attach the standard java debugger to the JVM the Container is running and your good to go.

                     

                    • Pax-Exam: one major drawback of Pax-Exam is that it starts and stops the framework between every test. Whether it's between every test class or every test method escapes me for the moment, but the bottom line is that it's pretty slow to run. This can have a major impact when you have a large number of tests. The principle is sound: start with a pristine framework for every test, but in practise its sometimes a pain.

                    The Default and only option at the moment is:

                    @BeforeSuite == Contianer.start

                    @BeforeClass == Container.deploy

                    @AfterClass == Container.undeploy

                    @AfterSuite == Container.stop

                     

                    That means the container is only started once pr test suite while new deployments are done pr TestClass. This will be configurable in some later version.

                     

                    e.g. Move container restart to be pr TestClass. Multiple testcases ran against the same Deployment, pr Package / Suite.

                    • 7. Re: Arquillian support for OSGi
                      Thomas Diesler Master

                      Thanks David, good feedback.

                       

                      • Another interesting feature would be the installation of bundles  that are declared as dependencies in the maven pom.xml. Let's say in  the pom.xml you have the following dependency:

                       

                      https://jira.jboss.org/browse/ARQ-198

                       

                      Besides the model outlined by Thomas above, it would also be nice if it  could support controlling multiple framework instances. As mentioned  before, an OSGi Framework can be controlled using a standard interface: org.osgi.framework.launch.Framework extends Bundle. In some test scenarios (especially ones involving  Distributed OSGi) you'd want to bring up multiple frameworks

                       

                      https://jira.jboss.org/browse/ARQ-199

                       

                      I guess the other issues were already answered by Aslak