In this installment of the OSGi series, we add more complete Unit Testing support in the project. We also establish that some behaviour of the Servlet Bridge may not be what we want and then provide a way to customize it.
Please make sure you read the previous installments before you continue with this article…
To ensure greater flexibility, performance and encapsulation, we will be using OSGi fragments.
http://static.springsource.org/osgi/docs/1.1.3/reference/html/appendix-tips.html
OSGi fragments attach to “host” bundles and can be used to extend functionality, provide configuration and even extra manifest entries.
On the first part, we move our unit tests to fragments. This has the benefit of being able to separate tests from actual code as well as allowing us to deploy using a smaller footprint in situations where we don’t want test code such as production deployments.
If we start with the cache code we’ve got so far we need to separate the test code from two bundles: ‘com.calidos.dani.osgi.cache.provider.memory’ and ‘com.calidos.dani.osgi.cache.provider.memcached’. That is easy enough.
We create a new OSGi bundle fragment project like this:
We specify the bundle the new fragment is attaching to:
The next step is to move the JUnit code from the main bundle onto the fragment. If we run the test independently we see that it passes as the classpath of the host and fragment bundles is merged into one.
We then add the fragment to our existing run configuration and it will be loaded onto our environment. So far so good, but there is no way to run the tests outside our controlled Eclipse.
Slim Ouertani at Javalobby shows us how to run fragment tests from within the OSGi environment in a great article. His code uses the new bundle tracker feature to discover any test fragments and exposing functionality to be able to run the tests.
You can get the source at kenai: http://kenai.com/projects/testosgifragment
We read the article thoroughly, fetch the source and build test.extender using maven:
mvn install
The code works great but has a bug which is triggered when the code looks for the host of the fragment by reading the ‘Fragment-Host:’ header. If the value specifies any kind of version qualifier it fails. It also crashes when trying to test a bundle or a fragment that doesn’t have the ‘Unit-Test’ custom header.
The ‘Unit-Test’ custom header lives on the fragment manifest and is used by test.extender to control who is able to be tested and if tests are done manually or automatically.
If no ‘Unit-Test’ header is present or if its value is empty, test.extender will refuse to run any tests present on the bundle. If the value is ‘true’ then test.extender will run the tests automatically whenever the test fragment bundle is loaded. If the value is anything else the tests won’t run automatically but you can still run them using the ‘test’ and ‘testall’ commands.
I have patched the code to accept a version qualifier for the fragment host as well as giving out an informative error message when attempting to test the wrong bundle.
Once this is done, we play with the test.extender by obtaining the fragment bundle id and issuing ‘test <id>’ on the console:
osgi> test 80
Bundle : [80] : com.calidos.dani.osgi.cache.provider.memory.test
_
CLASS : [com.calidos.dani.osgi.cache.provider.memory.test.MemoryCacheTest]
___________________________________________________________________________
Method : [ testClear ] PASS
Method : [ testInit ] PASS
Method : [ testInitInt ] PASS
Method : [ testSize ] PASS
Method : [ testSet ] PASS
Method : [ testGet ] PASS
Method : [ testGetStatus ] PASS
___________________________________________________________________________
_
We can also use the ‘testall’ command which will look for all the testable fragment bundles and run the test there.
Having tested that functionality, we separate the tests from the memcached bundle and move them onto their own bundle.
Here you can download the two test fragments and the patch to modify test.extender, neat.
Ok then, we move onto the next thing that is looking closely at the Servlet Bridge and its behaviour. What we are interested in is what happens when the container deploys and inits the Web app itself, starting up the servlet bridge. The bridge uses the container temporary folder to deploy the OSGi environment in a subfolder, copying any bundles into that subfolder. If the subfolder exists and there are bundles that have the same names, they won’t be copied. I have found this behaviour to be very confusing for newbies to the platform and very annoying for veterans. I’m not entirely sure if this is deliberate and has performance reasons or what. Calling the framework controls to prevent that is not optimal and manually removing the temporary folder isn’t, either. One possible solution would be to use a building system that increments the micro number every time there is a build and remember to clear up the container temporary folder from time to time. That is not always desirable or possible but fortunately, the servlet bridge implementation provides a way to customize it to our heart’s content.
Basically we have the following code in the bridge init() method:
framework.init(getServletConfig()); framework.deploy(); framework.start(); frameworkStarted = true;
In this case, ‘framework’ is a instance of the FrameworkLauncher class which is created just before this code snippet. The FrameworkLauncher is the class responsible for deploying and launching OSGi. We note that the object is created by default as a plain FrameworkLauncher instance. However, the class to be used can be customized by specifying it in the web.xml file using the ‘frameworkLauncherClass’ servlet initialization parameter. As long as that class is a subclass of FrameworkLauncher the appropriate contract is fulfilled.
This means we can override some behaviour and delete the framework deployment folders upon startup:
@Override public void init() { super.init(); File servletTemp = (File) context.getAttribute("javax.servlet.context.tempdir"); File platformDirectory = new File(servletTemp, "eclipse"); if (platformDirectory.exists()) { deleteDirectory(platformDirectory); } }
Cool, so we need to export this as a jar file named dani-frameworklauncher.jar into the package project lib folder:
Next we change the servletbridge web.xml configuration parameter to load the new framework launcher:
<init-param>
<param-name>frameworkLauncherClass</param-name>
<param-value>com.calidos.dani.osgi.servletbridge.FrameworkLauncher2</param-value>
</init-param>
To test, we can add a file onto the $APACHE_TOMCAT/work/Catalina/localhost/
Here you can download the framework launcher project, put it on the WEB-INF/lib folder alongside the servlet bridge and enjoy!
Hi ,
Thanks for such an important post.. It is very helpful. Unforunately i cannot find the source file in the location http://kenai.com/projects/testosgifragment.
Can u please help me
thanks
Checkout http://dani.calidos.com/img/2013/testosgifragment.tar.gz
I’ve uploaded a copy of the project.
Thanks a lot .. for the link….