3/9/12

How to deal with common problems in pax-exam-karaf

Prologue
Back in January I made a post about advanced integration testing with pax-exam-karaf. That post was pretty successful, as I got tons of questions from people that started using it.

In this post I am going to write down these questions and provide some answers that I hope that will help you have a more smooth experience with integration testing in Karaf.

Where should I place my integration test module?
Quite often people want to test a single bundle. In this case having the integration tests hosted inside the bundle itself seems a reasonable choice.

Unfortunately it is not. Pax Exam in general will start a new container and will install your tests as a proper OSGi bundle. That means that it expects to find bundle, however the test phase runs before the install phase in maven. That means that when you run the tests the bundle that you are testing will not be installed yet. To avoid this issue, you better host all your integration tests as a separate module.

The bundle context is not getting injected in my test?
For injecting the bundle context in the test Pax Exam Karaf makes use of javax.inject.Inject annotation. In your classpath there will be also a org.ops4j.pax.exam.Inject annotation. Make sure that you use the javax.inject.Inject to inject the bundle context. That's very easy to get you confused so be careful.

My system properties are not visible from within the test?
Quite often people customize the behavior of their tests, using system properties. It's really common to configure maven-surefire-plugin to expose maven properties as system properties (e.g. passing credentials to a test, allocating a free port for test & more).

That's something really useful, but you have to always remember a small detail. "The test is bootstrapped by one jvm, but runs in an other". That means that specifying system properties in the surefire plugin configuration, will not automagically set these properties to the Karaf container that will be used as the host of your tests.

I usually make sure to pass the desired system properties in the target Karaf container, by adding them in the etc/system.properties file of the container:
The above snippet also shows, how you can configure Karaf config.properties. The example shows how you can add additional execution environments in your test configuration.

How can I have my test extend a class from an other module?
You should never forget that under the hood  Pax Exam will create a bundle called probe, that contains all your tests. That means that if your tests extend a class that is not present in the current module, it won't be found by your probe bundle. In older versions of  Pax Exam you could pretty easily modify the contests of the probe and include additional classes from any module visible to your project. In the 2.x series its more complicated.

The easiest way to go is to make sure your install the bundle that contains the super class to the target container. Here is an example:

How can I configure the jre.properties of my test?
That's something really common, when you want to for example to test CXF in plain Karaf and not using ServiceMix/FuseESB etc.Pax Exam Karaf allows you to replace any configuration file with the one of your choice.


That's it! I hope you found this useful! Happy testing!

26 comments:

  1. Are there any versioning issues we should be aware of? I'm asking because I know we had this problem at Whirr.

    ReplyDelete
  2. Well the only versioning issue I have encountered so far is guava. Projects that use 11+ versions of guava will still need to use 10 for the itests.

    ReplyDelete
  3. I need to pass a maven property (say ${project.version}) to pax-exam-karaf, I am not getting how can I achieve this by following the example mentioned here:

    editConfigurationFilePut("etc/system.properties", "mykey", "myvalue"),

    Replacing "myvalue" with "${project.version}" doesn't work!

    Thanks in advance
    Arun

    ReplyDelete
  4. There are a few alternatives to solving this problem.
    What you need to understand is that maven properties are not available to the unit test, unless you specify them to maven-surefire-plugin configuration. Then you can access them with System.getProperty("project.version) for example. Note. that the property will not be available throughout the whole test, but only int the configure method. This is why we do the editConfigurationFilePut.

    An alternative that is specific to maven versions is also the following:

    editConfigurationFilePut("etc/system.properties", "fabric.version", MavenUtils.getArtifactVersion("xxxx","yyyy")

    The last doesn't require surefire configuration.

    ReplyDelete
  5. Thanks a lot for your reply..
    I have been using maven-surefire-plugin for passing the maven property to test but I was missing this bit
    editConfigurationFilePut("etc/system.properties", "mykey", System.getProperty(project.version))

    instead I was using...
    editConfigurationFilePut("etc/system.properties", "mykey", "${project.version}")

    After reading your reply it occurred to me to use first one.

    Arun

    ReplyDelete
  6. I have got one more question, how can I get hold of ESB log (fuseesb.log) from within the test?
    I am in requirement of parsing the output from one of the installed bundle.

    fuse-esb-full.zip is unzipped into following location which changes in each run..
    ${project.basedir}/target/paxexam/unpack/c294b1fa-d162-40b8-ad46-c1a022c1f1ea

    Thanks in advance!
    Arun

    ReplyDelete
  7. Hi Arun,

    I had the same issue myself with logging. I think that a workaround for me was to use a child instance for this kind of tests, which normally produced a log file, while the root container didn't

    ReplyDelete
  8. Hi Iocanel, I found an easier solution for my problem..issuing log:clear & log:display command and parsing the returned String for what I am looking for
    Arun

    ReplyDelete
  9. Hi Ioannis,
    Thanks for your responses so far. These have been very useful to me.

    Currently I am after a solution where:
    1. I can make test wait till FuseESB loads completely.

    2. I can ensure that the features and bundles listed in @configuration method be installed in some specific order.
    My @configuration methods looks like below. Installation of bundle "xyz2" complains of missing requirement of org.apache.camel. It Seems installation of bundles do not wait for installation of feature camel-smpp here
    public Option[] config() {
    return new Option[]{
    karafDistributionConfiguration().frameworkUrl(maven().groupId(FUSE_ESB_GROUP_ID).artifactId(FUSE_ESB_ARTIFACT_ID).type("zip").version(FUSE_ESB_VERSION)).
    karafVersion(KARAF_VERSION).unpackDirectory(new File("target/paxexam/unpack/")),
    keepRuntimeFolder(),
    scanFeatures(maven().groupId("org.apache.camel.karaf").artifactId("apache-camel").type("xml").classifier("features").version("2.9.0.fuse-7-061"),"camel-smpp"),
    provision(
    CoreOptions.mavenBundle().groupId("org.fusesource.tooling.testing").artifactId("pax-exam-karaf").versionAsInProject(),
    CoreOptions.mavenBundle().groupId("abc").artifactId("xyz1").versionAsInProject(),
    CoreOptions.mavenBundle().groupId("abc").artifactId("xyz2").versionAsInProject(),
    )
    };
    }

    Please suggest..

    Thanks
    Arun

    ReplyDelete
  10. Hi Arun,

    I would expect 1 to work as is, without much effort (not 100% sure).
    Regarding 2, mixing bundles and features is pain if u need ordering. So I would suggest to wrap your bundles xyz1 and xyz2 into a feature and just use features. It will make your life so much easier.

    ReplyDelete
  11. Thanks Ioannis, using features instead of a number of bundles seems solve the problem.
    Even I expect 1 to work.. still I think following might be of use to me:-
    return new Option[]{
    ....
    new TimeoutOption(Constants.WAIT_FOREVER),
    };
    Older version of Pax-Exam seems to have a method waitForFrameworkStartup() which is not available anymore.

    I appreciate it a lot you replying promptly, and yes, your solutions work too :)

    Next question from me is: Pax-Exam is not able to get fuse-esb-full..zip from a repository listed in maven settings.xml. I tried below also which again did not work:

    import static org.ops4j.pax.exam.CoreOptions.repositories;
    return new Option[]{
    repositories("http://"),
    //repository("http://"),
    karafDistributionConfiguration().frameworkUrl(maven().groupId(FUSE_ESB_GROUP_ID).artifactId(FUSE_ESB_ARTIFACT_ID).type("zip").version(FUSE_ESB_VERSION)).
    karafVersion(KARAF_VERSION).unpackDirectory(new File("target/paxexam/unpack/")),
    }
    Please suggest. Thanks
    -Arun

    ReplyDelete
  12. If settings.xml doesn't work for you maybe adding the repos in the pom.xml wii do the trick.

    Also the problem you have may be related with the fuse repo itself. I had trouble accessing it myself this morning.

    ReplyDelete
  13. Yes.. adding the dependency in the pom.xml ensures downloading the fuse esb zip and in my test I pick it as a file resource from the project's local repo.

    My next question might be more related with Pax-Exam rather than with pax-exam-karaf still I would like to ask as I am getting all my answers here.

    In my test I am not able to read a resource placed in src/test/resource. I have tried with following 4 ways..all lead to NullPointerException or similar

    (1).class.getResourceAsStream(testDataFileName);
    (2)new ClassPathResource("src/test/resources/");
    (3)new ClassPathResource(testDataFileName);
    (4)Thread.currentThread().getContextClassLoader().getResource(testDataFileName);

    I assume the resources in classpath be added in the Probe bundle! Or may be I am missing how to access it essentially.

    Thanks again for your support so far

    ReplyDelete
  14. In pax-exam 1.x I recall adding resources in the probe programmaticlly in the configure method, but haven't tired that in 2.x.

    ReplyDelete
  15. I did not need to attempt to add resource parametrically as resources in the ClassPath of project seems to be available in the bundle. Below worked for me to get hold of the resources packaged with the bundle.
    InputStream is = MyClass.class.getClassLoader().getResourceAsStream(testDataFileName);

    Another question :)
    I am in requirement of getting hold of child fabric containers and execute commands in it eg refreshing/stopping any bundle.
    1)How do I execute any command in child container just like I can do so in root container using executeCommand(String)
    2)How can I get hold of BundleContext of the child container

    Many thanks
    Arun

    ReplyDelete
  16. 1) Why don't you try something like: executeCommand("fabric:container-connect child-container my-super-cool-command"). I am using it all the time. You can have a look: https://github.com/fusesource/fuse/tree/master/fabric/fabric-itests/fabric-pax-exam

    2) I don't know if this is possible. It's definitely non-trivial. My feeling is that you don't need to do that. You can either connect via shell (see question 1) or even via jmx and get most of the things you need.

    ReplyDelete
  17. Hi iocanel,

    I am fairly new to pax exam but wondering if you can help.
    I have tests set up, such that they use @Before to create some fabric containers, each containing bundles that I use throughout my @Tests, and then destroyed with a @After.

    I issue I have is that this container creation is obviously run before each and every @Test, which is becoming quite inefficient, and may be better if it was done once only.

    Have you any thoughts around this? I think I'm right in thinking that @BeforeClass isn't available to pax exam

    thanks
    Coxy

    ReplyDelete
  18. Coxy,

    I also think that @BeforeClass can't be used in this use case.
    In my tests, I tend to prefer to run each test case in a clean environment, so I avoid using the same containers across tests.

    So I haven't really explored ways around this issue. I'll have a look and shall I find anything of interest I'll reply to you.

    ReplyDelete
  19. Hi iocanel,

    Thanks for your thoughts with the last question.

    I am now trying to put my @Test's into separate class files and extend the base 'setup' class (which contains only the @Configuration, @Before and @After). Seems to work ok, apart from when I run the tests and get the an initializationError (No tests added to setup!)

    It is referring to my base class, which indeed has no @Test. Have you come across a way to overcome this? so that it isn't expecting the base setup class to be an actual test.

    Thanks
    Coxy

    ReplyDelete
  20. Hi Ioannis,
    Thanks for your support so far.

    Pax-exam-karaf have been working good until we switched to fuse esb version 7.0.2.
    Following command works fine on fuse 7.0.2 console but not in Pax Exam test:
    "fabric:mq-create --create-container broker mq-broker"

    Above gives: java.lang.AssertionError: no such object in table

    I tried above with both the below versions of pax-exam-karaf
    7.0.0.fuse-061
    7.0.2.fuse-097

    Are there any known issues and its workaround in pax-exam-karaf with Fuse ESB 7.0.2.fuse-097, especially with regards to fabric containers creation?

    Thanks in advance
    Arun

    ReplyDelete
    Replies
    1. The "no such object in table" indicates that its a jmx thing. I'll have a closer look.

      Delete
    2. Any findings with it?
      You already might know this but in case it is useful, When I debug, the line "tracker.open(true)" from method executeCommand() giving the problem

      Delete
  21. Thanks for your reply. I'll keep watching this space

    ReplyDelete
  22. Hi Ioannis,

    I am wondering if you've had a look at pax exam running against Fuse ESB 7.1.0?

    Thanks
    Coxy

    ReplyDelete
    Replies
    1. Apologies for the late response, but I was a bit snowed putting things together for the forthcoming Jboss Fuse release.

      @Arun: The "no such object in table" is a quite common JMX issue. When you connect to jmx, it passes you an object with the rmi server hostname and then your clients attempts to connect to that hostname. If your client fails to resolve that hostname its pretty possible that you'll end up with errors like this. Creating a child container in fabric, or using the mq-create command will result in a jmx call to the root container, so that the child can be created. Passing the system property to the jvm with a reasonable java.rmi.server.hostname is your best chance to get it working.

      Note: In fabric 7.2 which will be part of the JBoss Fuse 6 release (the successor of Fuse ESB 7.1) fabric will manage this option for you.

      @Coxy: You should be able to have your tests inside a parent class and keep the configuration, setup and teardown classes in an other class. In fact this is soemthing that we do: https://github.com/fusesource/fuse/blob/master/fabric/fabric-itests/fabric-pax-exam/src/test/java/org/fusesource/fabric/itests/paxexam/camel/CamelProfileTest.java

      Delete