1

I've written a @component in DS that is supposed to be instantiated and activated in multiple instances. In order to test that I've written a pax exam test where I boot karaf and added scr. Everything works fine, but... it will not instantiate the services until after the test method has run thus gives me no space to do assertions etc.

@Test
public final void testing() throws Exception { 
props = createProperties(user, pass, host);
cfg = configurationAdmin.
     createFactoryConfiguration(CouchbaseConnectionProvider.SVC_NAME);
cfg.update(props);

final ServiceTracker tracker = new ServiceTracker(bundleContext, CouchbaseConnectionProvider.class, null);
tracker.open();

CouchbaseConnectionProvider svc = (CouchbaseConnectionProvider) tracker.waitForService(5000);
// It will wait 5s and after testing exits it will create the service
}

What am I doing wrong here? Since when method exits it will properly create and activate the service with all properties.

I may add that the test method using a thread "ion(3)-127.0.0.1" and when DS instantiates uses the thread "84-b6b23468b652)".

Cheers, Mario

Update 3 There where actually two bugs, one on my side and one somewhere else (in felix CM?) since the config where accessable by my interface impl bundle after a while (while container was shutting down) but it should really been bound to the pax test bundle (and of course CM itself) and never been "free:d" when container was shutting down. Where it that bug is I do not know - I'll wrap up a minimalistic mvn project and try the felix cm guys and I'll post the update here.

Update 2 I've filed a bug (https://ops4j1.jira.com/browse/PAXEXAM-725) if someone is interested to follow the progress (if there's a bug ;))

Update 1 This is my configuration in the testclass

package se.crossbreed.foundation.persistence.provider.couchbase;

@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class CouchbaseConnectionProviderTests extends CbTestBase {
  ...
}

Here is the configuration in the testclass that will use base class for base options.

@org.ops4j.pax.exam.Configuration
public Option[] config() {
    List<Option> options = super.baseConfig();
    options.addAll(Arrays
            .asList(features(karafStandardRepo, "scr"),
                    mavenBundle()
                            .groupId("se.crossbreed.foundation.persistence")
                            .artifactId(
                                    "se.crossbreed.foundation.persistence.core")
                            .versionAsInProject(),
                    mavenBundle().groupId("io.reactivex")
                            .artifactId("rxjava").versionAsInProject(),
                    mavenBundle()
                            .groupId("se.crossbreed.ports.bundles")
                            .artifactId(
                                    "se.crossbreed.ports.bundles.couchbase.java-client")
                            .versionAsInProject(),
                    mavenBundle()
                            .groupId("se.crossbreed.foundation.persistence")
                            .artifactId(
                                    "se.crossbreed.foundation.persistence.provider.couchbase")
                            .versionAsInProject()));

    // above bundle is the one I'm trying to test and where
    // this test resides in (project wise)
    return options.toArray(new Option[] {});
}

The base configuration is gotten from a base class

protected List<Option> baseConfig() {
    return new ArrayList<Option>(
            Arrays.asList(new Option[] {
                    logLevel(LogLevel.INFO),
                    karafDistributionConfiguration().frameworkUrl(karafUrl)
                            .unpackDirectory(new File("target", "exam"))
                            .useDeployFolder(false),
                    configureConsole().ignoreLocalConsole(),
                    mavenBundle().groupId("biz.aQute.bnd")
                            .artifactId("bndlib").version("${version.bndlib}"),
                    mavenBundle()
                            .groupId("se.crossbreed.foundation")
                            .artifactId(
                                    "se.crossbreed.foundation.core.annotations")
                            .versionAsInProject(),
                    mavenBundle()
                            .groupId("se.crossbreed.foundation")
                            .artifactId(
                                    "se.crossbreed.foundation.core.interfaces")
                            .versionAsInProject() }));
}

The package for the test is

package se.crossbreed.foundation.persistence.provider.couchbase;

And the CouchbaseConnectionProvider is on the same package

package se.crossbreed.foundation.persistence.provider.couchbase;

import se.crossbreed.foundation.persistence.core.CbDbConnectionProvider;

public interface CouchbaseConnectionProvider extends CbDbConnectionProvider {
    public final static String SVC_NAME = "couchbase.connection.provider";
}

The implementation:

package se.crossbreed.foundation.persistence.provider.couchbase.impl;

@Component(immediate = true, name = 
    CouchbaseConnectionProvider.SVC_NAME, provide = {
    CouchbaseConnectionProvider.class, CbDbConnectionProvider.class,
    CbService.class }, properties = { "providerType=DOCUMENT" }, 
    configurationPolicy = ConfigurationPolicy.require)
    public class CouchbaseConnectionProviderImpl implements
    CouchbaseConnectionProvider { ... }

Here's the project structure of the Couchbase Provider and the test that I'm failing to get to work (until after the test has run ;).

Project Structure of the couchbase provider and the test

Mario Toffia
  • 510
  • 5
  • 16

3 Answers3

1

(I don't actually see anything wrong with your code, the ConfigurationAdmin should work asynchronously. The new service comming up after the test still looks like a synchronization issue though. In that case, this setup might fix it.)

Instead of creating the configuration inside the test method you could use pax-exam-cm to create the factory configuration with the other options:

@org.ops4j.pax.exam.Configuration
public Option[] config() {
    List<Option> options = super.baseConfig();
    options.addAll(Arrays
        .asList(features(karafStandardRepo, "scr"),
        //missing conversion: putAll() needs a Map        
        ConfigurationAdminOptions.factoryConfiguration(CouchbaseConnectionProvider.SVC_NAME)
                        .putAll(createProperties(user, pass, host)).create(true).asOption(),
                mavenBundle()
                        .groupId("se.crossbreed.foundation.persistence")
                        .artifactId(
                                "se.crossbreed.foundation.persistence.core")
                        .versionAsInProject(),
                mavenBundle().groupId("io.reactivex")
                        .artifactId("rxjava").versionAsInProject(),
                mavenBundle()
                        .groupId("se.crossbreed.ports.bundles")
                        .artifactId(
                                   "se.crossbreed.ports.bundles.couchbase.java-client")
                        .versionAsInProject(),
                mavenBundle()
                        .groupId("se.crossbreed.foundation.persistence")
                        .artifactId(
                                "se.crossbreed.foundation.persistence.provider.couchbase")
                        .versionAsInProject()));

    // above bundle is the one I'm trying to test and where
    // this test resides in (project wise)
    return options.toArray(new Option[] {});
}

Maven settings:

<dependency>
    <groupId>org.ops4j.pax.exam</groupId>
    <artifactId>pax-exam-cm</artifactId>
    <version>${exam.version}</version>                
</dependency>

You can then also simply use the @Inject annotation to get the CouchbaseConnectionProvider inside the test.

@Inject
CouchbaseConnectionProvider svc;
  • Thanks for your help around AdminOptions! Yes I can se that it does post it async (I'd guess DS is one of the listeners). However it seems that it's thread is waiting during the test. Is this a bug to be reported to PAX? This paricular case - however the reason I have the instantiation in my test method is that I can test it with a another service dependency (within the couchbaseconnectionprovider) and how it's activated and deactivatet (hopefully properly). In this setup this will be impossible to do so. – Mario Toffia Jul 08 '15 at 19:41
  • Thanks for your help! It was actually my fault, I did not provide with the correct owner for the configuration (omitted null or the correct bundle location for my crossbreed provider bundle). The only mystery for me left is why in the world the service got created at a later stage since it did not own the config... strange - but thanks again! :) cheers/mario – Mario Toffia Jul 09 '15 at 12:14
0

I suspect that the test deploys the CouchbaseConnectionProvider interface with itself. So you try to retrieve the service using a different interface than the one the real service provides.

You should try to add imports and exports to your test bundle for the package CouchbaseConnectionProvider resides in.

To do this use a ProbeBuilder

@ProbeBuilder
public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) {
    probe.setHeader(Constants.IMPORT_PACKAGE, "..");
    probe.setHeader(Constants.EXPORT_PACKAGE, "..");
    return probe;
}
Christian Schneider
  • 19,420
  • 2
  • 39
  • 64
  • Thanks! :) - Now I understand what your trying to tell me - yes the test is residing in the tests folder in same project as my src/main/java for the interface (& implementation) - If service tracker fails - that I would be fine with but my problem is that DS will *not* even instantiate / activate my service (see updated question :)). It will do that after test method has finished and the container is shutting down (then it will be instantiated). – Mario Toffia Jul 07 '15 at 18:53
  • It was a bug of mine, did bind the configuration to current executing bundle by omitting bundle location in createFactoryConfiguration - thanks for your help! – Mario Toffia Jul 09 '15 at 12:16
0

thanks both of you for your input - I chose to answer this question myself since I had a bug in my code and got help from Christoph.

I quote the answer from him here if there someone else did what I did.

The problem was that I did not set the configuration ownership as anonymous via (pid, null) in createFactoryConfiguration. Instead I used createFactoryConfiguration(pid) then it got bound to the current executing bundle and not the bundle I was testing. As Christoph explained it was possible for me to get the bundle location of the service bundle and set that explicitly.

Cheers, Mario

Here's Christoph Läubrich answer

"Christoph Läubrich added a comment - 13 minutes ago

Okay I think I know what might be the problem now: You are using the createFactoryConfiguration(java.lang.String factoryPid), this means you will create a configuration that is exclusivly bound to your bundle! Thus no other bundle is allowed to access the configuration! Use the createFactoryConfiguration(java.lang.String factoryPid, java.lang.String location) instead with a null argument for the location! This way you create an anonymous configuration that will be bound to the first bundle that fetches this config. Alternativly you can get the location of the target bundle and explicitly pass this as an parameter, but this is often not needed. If this still do not work, we must take a closer look at your configuration, connect to the karaf shell (while stopped at a breakpoint) and get a list of all bundles (bundle:list) and a list of all components (scr:list). Also you should collect detailed information about the probe bundle and the bundle that should provide the service (packages:imports)."

Mario Toffia
  • 510
  • 5
  • 16
  • Or rather what I've done is that I use the correct bundle location since that is what needed instead of first one grabs... cheers/mario – Mario Toffia Jul 09 '15 at 12:15