OSGI classloaders, integration testing and all that

So I managed to get my services to communicate and all. After this I wanted to set up some basic (regression) tests that make sure they keep working together as before. Using the familiar JUnit4 I set on this adventure. It is a unit testing tool but works just fine for me with integration testing like this. As usual it all turned out to be much more difficult than I was expecting. First I need to start the services in an OSGI container. After some wondering around I finally found the way to do it is:

String bundleDir = “bundles”;

Map<String, String> config = new HashMap<String, String>();
config.put("felix.log.level", "1");
config.put("felix.auto.deploy.action", "install,update,start");
config.put(AutoProcessor.AUTO_DEPLOY_DIR_PROPERY, bundleDir);
ServiceLoader<FrameworkFactory> loader = ServiceLoader.load(FrameworkFactory.class);
FrameworkFactory factory = loader.iterator().next();
Framework fw = factory.newFramework(config);
fw.init();
BundleContext bc = fw.getBundleContext();
AutoProcessor.process(config, bc);
fw.start();
    ServiceLoader<FrameworkFactory> loader = ServiceLoader.load(FrameworkFactory.class);
    FrameworkFactory factory = loader.iterator().next();
    Framework fw = factory.newFramework(config);
    fw.init();
    BundleContext bc = fw.getBundleContext();
    AutoProcessor.process(config, bc);
    fw.start();

What does it all mean? It expects that in the working directory (set in the IDE run-dialog) a directory named “bundles” is located. In this directory my ant script deploys my own bundles and copies the Felix framework bundles I use. Then it enables the autodeploy feature of Felix to always load these bundles at startup (and update them). Finally it starts the container, loading my updated stuff in it.

With the container started and my bundles deployed I then wanted to invoke some services and check the end results. First of all it seems a bit hard to test much unless you have access to some state information from the components (services that is). So I had to add some new methods to the interfaces to support what I wanted to test. Then when I get the services from outside the container (using the framework object I just created) it fails.. With all sorts of strange exceptions. Luckily I was able to make some educated guesses that this must be some problems with the OSGI classloader vs the classloader used by the JRE to run my JUnit tests.

SO I looked at all sorts of stuff to see how to fix this. Pex testing tools are intended for something like this but start a maven instance to download a container, use RMI for calls and all sorts of strange things. All I wanted was to call a service running in the same VM. How hard can it be? So I try with simple reflection to call the method on the OSGI service class. Of course it keeps failing still.. So I figure I have to put the stuff in the container so it is loaded with the same classloader as the service bundle. Made it into a test service bundle just for testing. It now looks like this:

The part in the JUnit test that makes the call to the test bundle:

BundleContext bc = fw.getBundleContext();
ServiceReference reference = bc.getServiceReference(OSGITester.class.getName());
    final Object originalTester = bc.getService(reference);
    InvocationHandler handler = new InvocationHandler() {
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method testMethod = originalTester.getClass().getMethod(method.getName(), method.getParameterTypes());
        testMethod.invoke(originalTester, args);
        return null;
      }
    };
    OSGITester tester = (OSGITester) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] {OSGITester.class}, handler);
    tester.init();
   tester.testMethod();

and the part in the test bundle that is called..

    ServiceReference reference = bc.getServiceReference(ServiceToBeTested.class.getName());

    ServiceToBeTested std = (ServiceToBeTested) std.getService(reference);

   AssertSomethingWithJunit()....

So the same problem seems to be here as in my previous post about Felix failing and Equinox working. I included the same class files in both bundles cause I thought it would be easy and nice and all that. I think this is what I did when Equinox worked and Felix did not. So must be some difference in Equinox reusing a classloader or something and Felix having different ones for different bundles. In any case it was still failing and I was starting to get pissed with it all.

In the end I got it working with this solution by making the BundleToBeTested export all packages that had something I wanted to test and the TesterBundle import them as well. Removing the duplicate class files from the bundle jar files was needed also. After this they seem to be using the same classloader (and whatever else is needed for the Felix magic) and now it all works.  Woohoo, I can make the tests as normal Java classes without any strange maven/RMI stuff and see all the reporting in my IDE as usual.

Now for something completely different..

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s