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:
- http://rcpquickstart.com/2007/06/20/unit-testing-plug-ins-with-fragments/
- http://www.modumind.com/2008/06/12/running-unit-tests-for-rcp-and-osgi-applications/
- http://michaelscharf.blogspot.com/2008/04/is-osgi-enemy-of-junit-tests_17.html
- http://alblue.blogspot.com/2006/03/javaeclipse-running-all-tests-in.html
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-activator
private 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
Kerkstraat 108
B-9050 Gent
Belgium
http://www.isencia.com
http://www.isencia.be/index_en.html
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





Comments
Mladen Girazovski replied on Fri, 2009/12/18 - 3:58am
I think you should distinguishbetween isolated unit tests (white box testin, where you need to know the internals) and integrated unit tests (black box testing, all you know are the interfaces), the first is best kept in the same project, ie. like Maven2 does in a seperate source path, that doesn't get added to the final product, the latter is best kept in his own project/bundle.
Mirko Jahn replied on Fri, 2009/12/18 - 5:53am
Interesting post. Though, I have some comments on how and when ;-)
1.) Activators are bad! Not the activation in general, but the code one provides usually is. For more details, have a look at http://neilbartlett.name/blog/2009/11/30/osgi-its-time-to-ban-bundle-activators/ (Neil's blog).
2.) Use Spring, DS, BluePrint or what ever DI framework you prefer (just make sure it supports the fragment approach). Besides of limiting the actual code needed, one can theoretically attach more than one fragment.
3.) Bundle trackers are simple to implement even with OSGi 4.1! All the core features of Spring DM are based on the extender approach. OSGi 4.2 "only" added convenience methods...
4.) Code duplication is an issue! Ultimately, you only want to expose the Unit tests as services (or a per bundle based reference) and have a consumer bundle with all the domain logic to run tests.
5.) Last but most importantly, you really need to think about what your test should look like! A junit test for instance should run outside an OSGi container! There are fw's out there providing simple mockcapabilities of OSGi API's so this shouldn't be an issue. If you're running blackbox tests, you shouldn't have access to the internals of a bundle at test. The only meaningful reason why this would make sense is to have two components. On test driver running the test and a fragment attached to the bundle at test to allow for remote configuration of the behavior, otherwise not possible. And that's about it.
@migr:
I agree for most part. The only thing... we decided to have the blackbox tests in the same project, but in different class paths (generating two distinct bundles) for one major reason: Versioning! With this we know that a certain test driver is related to a specific bundle without the need for a mapping table or additional rules or regulations.
erwin de ley replied on Fri, 2009/12/18 - 9:34am
Thanks for the feedback.
Indeed, I can imagine that a certain amount of "pure" unit tests might be included in the functional bundle project. And then with some build-script-magic (be it Maven/Ant/whatever/) they get excluded from the release package. And you can launch them with a plain JUnit launcher, outside of a running OSGi. Nice and simple.
But this is not the setting that this posting is about.
If the number of tests increases, or the "level"/complexity of testing increases the test code quickly gathers a life of its own, with different maintenance needs and update schedules. Then I think it's better to split test code and application code into separate bundles (or fragments).
Furthermore when you have tests that depend on the presence of services, or you just want to ensure that the tests are run in an environment that's comparable/equal to the final "production" environment, I really 'like' (strange I know, but well...) to execute them in a real OSGi platform.
@mirkojahn :
I'm a bit old-fashioned and like my logic to be in code. Annotations and XML-files just add noise for me, activators suite me fine in most cases even if it means I need to run some code templates in my IDE...
DI is nice and all, but I've had my share of 'XML-debugging' or annotation-guessing. But I must admit that DS is almost tempting me to convert ;-)
About the BundleTrackers : this does not really solve my problem. I'm not really happy to have to browse MANIFEST entries to discover fragments etc. Anyway, I want the fragment to be able to actively contribute tests/test tools/... I don't want the host bundle to be too much involved in enabling all of that. Just a simple pseudo-activator suites me fine and the fragment can do it's thing from there.
White-box/black-box testing are indeed clean descriptions. But I often need grey-box testing as well.
cheers
erwin
Slim Ouertani replied on Fri, 2009/12/18 - 9:39am
Great post,
erwin de ley replied on Fri, 2009/12/18 - 3:23pm
in response to:
Slim Ouertani
@slim : Well, the host bundle is checking if a fragment activator is provided or not. And then just forwarding the start/stop invocations to it. But what actually happens then is completely up to the fragment.
Activators may not be everyone's cup of tea, but for the moment I still prefer sticking to them.
cheers
erwin