Platinum Partner
java,osgi,testing,fragments,activator,commandprovider

OSGi Fragment Bundles Don't Have Activators. Or Do They?

Using "pseudo"-activators, fragment bundles can be more active than you think! Writing test code in an OSGi-setting requires some more work and decisions than in a plain Java-main()-applications-setting.

For example, we typically don't want to put test code in the "functional" bundles themselves, as this makes the build process more complex, as we need to be able to extract the test code from the "production" release package. On the other hand, we don't want to export all bundle packages. So if we put our test code in separate bundles, we're not able to access all classes/packages that we might like to test...

An often-cited approach can then be to include the test code in fragment bundles. These can be easily included/excluded in test launches or release packaging, and they use the same classloader as the host bundle, so they have access to all of the host's classes.

For more info/discussion on this, check:

Ok, and now what?

Indeed. Once the decision to put test code in fragments is made, the next step is : how can we get these tests executed???

The problems are :

  • fragments do not get activated, so they can not register services, start service trackers etc. It's not possible AFAIK to have the fragment initiate any kind of action on "start-up".
  • fragment classes/resources are only visible in the host bundle, unless the host bundle exports them. But if the fragment is meant to be optional, we can not export its packages from the host.
  • host bundles can not easily discover their registered fragment bundles. The only approach I found was to inspect the MANIFEST of all running bundles, and found out if there are some with a Fragment-Host entry that refers to the host bundle. And even there, we may need features that are only practically available in the new OSGi 4.2. See  http://java.dzone.com/articles/osgi-junit-test-extender-using

So how can we "discover" the presence of test cases? In the links above, all kinds of tricks with reflection are described, or using eclipse-specific things. Others ensure that their test fragments provide a "magic file" on the root etc., for which the host can search. All these approaches are based on the host bundle being aware of the optional presence of a test fragment, and checking for the presence of a class or a file on some predefined path. In this way, test cases can be found and added to test suites etc. But this implies that the host bundle is aware of the purpose of the fragment(s).

Now, to make things even more complicated, we often like to run tests using Equinox Console commands. This implies that we must be able to register CommandProvider implementations as OSGi services. But fragments do not have activators, so how can they register service implementations!?

Enter the  TestFragmentActivator

To increase testing flexibility, we don't want the host bundle to be aware whether the fragment is providing JUnit test cases, CommandProviders, or other test approaches.

At the same time, we want to allow the fragment to be able to register OSGi services or perform other OSGi-magic typically done in Activators.

So we propose the following approach :

  • each test fragment must provide a BundleActivator implementation with a predefined qualified name. E.g. for a project FOO, to test backend services, the name could be com.foo.backend.service.test.TestFragmentActivator
  • in the host bundle's Activator, we add code like :
public class Activator  implements BundleActivator {private static Activator defaultInstance;// a reference to the fragment's pseudo-activatorprivate BundleActivator testFragmentActivator;public void start(BundleContext context) throws Exception {defaultInstance = this;try {Class<? extends BundleActivator> frgActClass =                               (Class<? extends BundleActivator>) Class.forName("com.foo.backend.service.test.TestFragmentActivator");testFragmentActivator = frgActClass.newInstance();testFragmentActivator.start(context);} catch (ClassNotFoundException e) {// ignore, means the test fragment is not present...// it's a dirty way to find out, but don't know how to                         // discover fragment contribution in a better way...}}public void stop(BundleContext context) throws Exception {defaultInstance = null;if(testFragmentActivator!=null) testFragmentActivator.stop(context);}public static Activator getDefault() {return defaultInstance;}}
  • and in the fragment we add the implementation, that can e.g. register a new CommandProvider :
package com.foo.backend.service.test;import org.eclipse.osgi.framework.console.CommandProvider;import org.osgi.framework.BundleActivator;import org.osgi.framework.BundleContext;import org.osgi.framework.ServiceRegistration;import com.foo.backend.service.test.impl.ServiceTestCommandProvider;/** * This is a fake activator, i.e. the OSGI platform will never see or invoke it, * as we're inside a fragment. The goal is that the host bundle tries to  * figure out if this kind of activator is present and if so, invoke its start()  * & stop() methods from inside its own ones. *  * Remark that this activator will not really start the fragment, it will remain  * in the RESOLVED state as far as the OSGi platform is concerned! */public class TestFragmentActivator implements BundleActivator {private ServiceRegistration svcReg;public void start(BundleContext context) throws Exception {ServiceTestCommandProvider svcTester = new ServiceTestCommandProvider();svcReg = context.registerService(CommandProvider.class.getName(),                                                      svcTester, null);}public void stop(BundleContext context) throws Exception {svcReg.unregister();}}

In a similar way, we could use the fragment's pseudo activator to register JUnit testcases to some centralized test suite executor etc.

Couldn't it even be a useful pattern in general for fragments, i.e. not only for test-oriented fragments??

What do you think?

Erwin De Ley
iSencia Belgium
Voorhavenlaan 31 - 011
B-9000 Gent
Belgium
http://www.isencia.be/

 

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}