FELIX-3910: pax-exam 3.0.0 migration tests (work in progress). Added new AspectRaceTest for
the FELIX-3910 issue.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1530204 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/test2/pom.xml b/dependencymanager/test2/pom.xml
new file mode 100644
index 0000000..367812d
--- /dev/null
+++ b/dependencymanager/test2/pom.xml
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to you under the Apache License, Version
+ 2.0 (the "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+ applicable law or agreed to in writing, software distributed under
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix-parent</artifactId>
+ <version>2.1</version>
+ <relativePath>../../pom/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.felix.dependencymanager.test2</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Felix Dependency Manager Integration Tests 2</name>
+
+ <properties>
+ <bundle.build.name>
+ ${basedir}/target
+ </bundle.build.name>
+ <bundle.file.name>
+ ${bundle.build.name}/${project.build.finalName}.jar
+ </bundle.file.name>
+
+ <felix.build.source>5</felix.build.source>
+ <felix.build.target>5</felix.build.target>
+ <felix.java.signature.artifactId>java15</felix.java.signature.artifactId>
+ <exam.version>3.0.0</exam.version>
+ <url.version>1.5.2</url.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>5.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>5.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <version>3.1.1-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.6</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Integration Testing with Pax Exam -->
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-native</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ops4j.pax.url</groupId>
+ <artifactId>pax-url-aether</artifactId>
+ <version>${url.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>4.2.1</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>0.9.20</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>0.9.20</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Dependency Manager Annotations -->
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager.annotation</artifactId>
+ <version>3.1.1-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Build the bundle which contains various components used by integration tests -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.3.7</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Name>Apache Felix Dependency Manager Annotations Tests</Bundle-Name>
+ <Bundle-SymbolicName>org.apache.felix.dependencymanager.test2.components</Bundle-SymbolicName>
+ <Export-Package>org.apache.felix.dependencymanager.test2.components</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+
+ <!-- java 5 required for annotations support -->
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+
+ <!-- DM annotation plugin used to parse annotations from org.apache.felix.dependencymanager.test2.components artifact -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager.annotation</artifactId>
+ <version>3.1.1-SNAPSHOT</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>scan</goal>
+ </goals>
+ <configuration>
+ <log>debug</log>
+ <!-- generated-output-dir>.</generated-output-dir> -->
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- We have no unit tests, we only have integration tests -->
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>surefire-it</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <systemProperties>
+ <property>
+ <name>project.bundle.file</name>
+ <value>${bundle.file.name}</value>
+ </property>
+ </systemProperties>
+ <excludes>
+ <exclude>**/components/*</exclude>
+ </excludes>
+ <includes>
+ <include>**/integration/**/*</include>
+ </includes>
+ </configuration>
+ </execution>
+ </executions>
+ <configuration>
+ <excludes>
+ <exclude>**/integration/**</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/dependencymanager/test2/src/main/java/org/apache/felix/dependencymanager/test2/components/Ensure.java b/dependencymanager/test2/src/main/java/org/apache/felix/dependencymanager/test2/components/Ensure.java
new file mode 100644
index 0000000..0cbdb73
--- /dev/null
+++ b/dependencymanager/test2/src/main/java/org/apache/felix/dependencymanager/test2/components/Ensure.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dependencymanager.test2.components;
+
+import java.io.PrintStream;
+
+import junit.framework.Assert;
+
+/**
+ * Helper class to make sure that steps in a test happen in the correct order. Instantiate
+ * this class and subsequently invoke <code>step(nr)</code> with steps starting at 1. You
+ * can also have threads wait until you arrive at a certain step.
+ */
+public class Ensure {
+ private final boolean DEBUG;
+ private static long INSTANCE = 0;
+ private static final int RESOLUTION = 100;
+ private static PrintStream STREAM = System.out;
+ int step = 0;
+ private Throwable m_throwable;
+
+ public Ensure() {
+ this(true);
+ }
+
+ public Ensure(boolean debug) {
+ DEBUG = debug;
+ if (DEBUG) {
+ INSTANCE++;
+ }
+ }
+
+ public void setStream(PrintStream output) {
+ STREAM = output;
+ }
+
+ /**
+ * Mark this point as step <code>nr</code>.
+ *
+ * @param nr the step we are in
+ */
+ public synchronized void step(int nr) {
+ step ++;
+ Assert.assertEquals(nr, step);
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] step " + step + " [" + currentThread() + "] " + info);
+ }
+ notifyAll();
+ }
+
+ private String getLineInfo(int depth) {
+ StackTraceElement[] trace = Thread.currentThread().getStackTrace();
+ String info = trace[depth].getClassName() + "." + trace[depth].getMethodName() + ":" + trace[depth].getLineNumber();
+ return info;
+ }
+
+ /**
+ * Mark this point as the next step.
+ */
+ public synchronized void step() {
+ step++;
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] next step " + step + " [" + currentThread() + "] " + info);
+ }
+ notifyAll();
+ }
+
+ /**
+ * Wait until we arrive at least at step <code>nr</code> in the process, or fail if that
+ * takes more than <code>timeout</code> milliseconds. If you invoke wait on a thread,
+ * you are effectively assuming some other thread will invoke the <code>step(nr)</code>
+ * method.
+ *
+ * @param nr the step to wait for
+ * @param timeout the number of milliseconds to wait
+ */
+ public synchronized void waitForStep(int nr, int timeout) {
+ final int initialTimeout = timeout;
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] waiting for step " + nr + " [" + currentThread() + "] " + info);
+ }
+ while (step < nr && timeout > 0) {
+ try {
+ wait(RESOLUTION);
+ timeout -= RESOLUTION;
+ }
+ catch (InterruptedException e) {}
+ }
+ if (step < nr) {
+ throw new IllegalStateException("Timed out waiting for " + initialTimeout + " ms for step " + nr + ", we are still at step " + step);
+ }
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] arrived at step " + nr + " [" + currentThread() + "] " + info);
+ }
+ }
+
+ private String currentThread() {
+ Thread thread = Thread.currentThread();
+ return thread.getId() + " " + thread.getName();
+ }
+
+ public static Runnable createRunnableStep(final Ensure ensure, final int nr) {
+ return new Runnable() { public void run() { ensure.step(nr); }};
+ }
+
+ public synchronized void steps(Steps steps) {
+ steps.next(this);
+ }
+
+ /**
+ * Helper class for naming a list of step numbers. If used with the steps(Steps) method
+ * you can define at which steps in time this point should be passed. That means you can
+ * check methods that will get invoked multiple times during a test.
+ */
+ public static class Steps {
+ private final int[] m_steps;
+ private int m_stepIndex;
+
+ /**
+ * Create a list of steps and initialize the step counter to zero.
+ */
+ public Steps(int... steps) {
+ m_steps = steps;
+ m_stepIndex = 0;
+ }
+
+ /**
+ * Ensure we're at the right step. Will throw an index out of bounds exception if we enter this step more often than defined.
+ */
+ public void next(Ensure ensure) {
+ ensure.step(m_steps[m_stepIndex++]);
+ }
+ }
+
+ /**
+ * Saves a thrown exception that occurred in a different thread. You can only save one exception
+ * at a time this way.
+ */
+ public synchronized void throwable(Throwable throwable) {
+ m_throwable = throwable;
+ }
+
+ /**
+ * Throws a <code>Throwable</code> if one occurred in a different thread and that thread saved it
+ * using the <code>throwable()</code> method.
+ */
+ public synchronized void ensure() throws Throwable {
+ if (m_throwable != null) {
+ throw m_throwable;
+ }
+ }
+}
diff --git a/dependencymanager/test2/src/main/java/org/apache/felix/dependencymanager/test2/components/SimpleAnnotations.java b/dependencymanager/test2/src/main/java/org/apache/felix/dependencymanager/test2/components/SimpleAnnotations.java
new file mode 100644
index 0000000..e1e2db9
--- /dev/null
+++ b/dependencymanager/test2/src/main/java/org/apache/felix/dependencymanager/test2/components/SimpleAnnotations.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dependencymanager.test2.components;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.annotation.api.Component;
+import org.apache.felix.dm.annotation.api.Destroy;
+import org.apache.felix.dm.annotation.api.Init;
+import org.apache.felix.dm.annotation.api.Inject;
+import org.apache.felix.dm.annotation.api.Property;
+import org.apache.felix.dm.annotation.api.Registered;
+import org.apache.felix.dm.annotation.api.ServiceDependency;
+import org.apache.felix.dm.annotation.api.Start;
+import org.apache.felix.dm.annotation.api.Stop;
+import org.apache.felix.dm.annotation.api.Unregistered;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogService;
+
+public class SimpleAnnotations {
+ /**
+ * Provides a <code>Runnable</code> service, which is required by the
+ * {@link Consumer} class.
+ */
+ @Component(properties = {@Property(name = "foo", value = "bar")})
+ public static class Producer implements Runnable {
+ @ServiceDependency(filter = "(name=simple.annotations.producer)")
+ volatile Ensure _ensure;
+
+ @ServiceDependency
+ volatile LogService _logService;
+
+ @Inject
+ volatile BundleContext _ctx;
+
+ @Init
+ protected void init() {
+ _logService.log(LogService.LOG_INFO, "producer.init");
+ // Our component is initializing (at this point: all required
+ // dependencies are injected).
+ _ensure.step(1);
+ }
+
+ @Start
+ protected void start() {
+ // We are about to be registered in the OSGi registry.
+ _ensure.step(2);
+ }
+
+ @Registered
+ protected void registered(ServiceRegistration sr) {
+ _logService.log(LogService.LOG_INFO, "Registered");
+ if (sr == null) {
+ _ensure.throwable(new Exception("ServiceRegistration is null"));
+ }
+ if (!"bar".equals(sr.getReference().getProperty("foo"))) {
+ _ensure.throwable(new Exception("Invalid Service Properties"));
+ }
+ _ensure.step(3);
+ }
+
+ public void run() {
+ _ensure.step(5);
+ }
+
+ @Stop
+ protected void stop() {
+ // We are about to be unregistered from the OSGi registry, and we
+ // must stop.
+ _ensure.step(8);
+ }
+
+ @Unregistered
+ protected void stopped() {
+ // We are unregistered from the OSGi registry.
+ _ensure.step(9);
+ }
+
+ @Destroy
+ public void destroy() {
+ // Our component is shutting down.
+ _ensure.step(10);
+ }
+ }
+
+ /**
+ * Consumes a service which is provided by the {@link Producer} class.
+ */
+ @Component
+ public static class Consumer {
+ @ServiceDependency
+ volatile LogService _logService;
+
+ @ServiceDependency
+ volatile Runnable _runnable;
+
+ @ServiceDependency(filter = "(name=simple.annotations.consumer)")
+ volatile Ensure _ensure;
+
+ @Inject
+ volatile BundleContext _bc;
+ BundleContext _bcNotInjected;
+
+ @Inject
+ volatile DependencyManager _dm;
+ DependencyManager _dmNotInjected;
+
+ @Inject
+ volatile org.apache.felix.dm.Component _component;
+ org.apache.felix.dm.Component _componentNotInjected;
+
+ @Start
+ protected void start() {
+ _logService.log(LogService.LOG_INFO, "Consumer.START: ");
+ checkInjectedFields();
+ _ensure.step(4);
+ _runnable.run();
+ }
+
+ private void checkInjectedFields() {
+ if (_bc == null) {
+ _ensure.throwable(new Exception("Bundle Context not injected"));
+ return;
+ }
+ if (_bcNotInjected != null) {
+ _ensure.throwable(new Exception("Bundle Context must not be injected"));
+ return;
+ }
+
+ if (_dm == null) {
+ _ensure.throwable(new Exception("DependencyManager not injected"));
+ return;
+ }
+ if (_dmNotInjected != null) {
+ _ensure.throwable(new Exception("DependencyManager must not be injected"));
+ return;
+ }
+
+ if (_component == null) {
+ _ensure.throwable(new Exception("Component not injected"));
+ return;
+ }
+ if (_componentNotInjected != null) {
+ _ensure.throwable(new Exception("Component must not be injected"));
+ return;
+ }
+ }
+
+ @Stop
+ protected void stop() {
+ _ensure.step(6);
+ }
+
+ @Destroy
+ void destroy() {
+ _ensure.step(7);
+ }
+ }
+}
diff --git a/dependencymanager/test2/src/test/java/org/apache/felix/dependencymanager/test2/integration/annotations/SimpleAnnotationsTest.java b/dependencymanager/test2/src/test/java/org/apache/felix/dependencymanager/test2/integration/annotations/SimpleAnnotationsTest.java
new file mode 100644
index 0000000..2e37f5a
--- /dev/null
+++ b/dependencymanager/test2/src/test/java/org/apache/felix/dependencymanager/test2/integration/annotations/SimpleAnnotationsTest.java
@@ -0,0 +1,51 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.felix.dependencymanager.test2.integration.annotations;
+
+import org.apache.felix.dependencymanager.test2.components.Ensure;
+import org.apache.felix.dependencymanager.test2.components.SimpleAnnotations;
+import org.apache.felix.dependencymanager.test2.integration.common.TestBase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Use case: Ensure that a Provider can be injected into a Consumer, using simple DM annotations.
+ */
+@RunWith(PaxExam.class)
+public class SimpleAnnotationsTest extends TestBase
+{
+ @Test
+ public void testSimpleAnnotations() throws Throwable
+ {
+ Ensure e = new Ensure();
+ ServiceRegistration er = register(e, "simple.annotations.producer");
+ e.waitForStep(3, 10000); // Producer registered
+ ServiceRegistration er2 = register(e, "simple.annotations.consumer");
+
+ er2.unregister(); // stop consumer
+ er.unregister(); // stop provider
+
+ // And check if components have been deactivated orderly.
+ e.waitForStep(10, 10000);
+ e.ensure();
+ stopTestComponentsBundle();
+ }
+}
diff --git a/dependencymanager/test2/src/test/java/org/apache/felix/dependencymanager/test2/integration/api/AspectRaceTest.java b/dependencymanager/test2/src/test/java/org/apache/felix/dependencymanager/test2/integration/api/AspectRaceTest.java
new file mode 100644
index 0000000..72961c8
--- /dev/null
+++ b/dependencymanager/test2/src/test/java/org/apache/felix/dependencymanager/test2/integration/api/AspectRaceTest.java
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dependencymanager.test2.integration.api;
+
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import junit.framework.Assert;
+
+import org.apache.felix.dependencymanager.test2.components.Ensure;
+import org.apache.felix.dependencymanager.test2.integration.common.TestBase;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+@RunWith(PaxExam.class)
+public class AspectRaceTest extends TestBase {
+ volatile ExecutorService _serviceExec;
+ volatile ExecutorService _aspectExec;
+ volatile DependencyManager _dm;
+ final static int SERVICES = 3;
+ final static int ASPECTS_PER_SERVICE = 10;
+
+ @Inject
+ private static volatile BundleContext _bctx;
+
+ @Test
+ public void testConcurrentAspects() {
+ try {
+ warn("starting aspect race test");
+ int cores = Runtime.getRuntime().availableProcessors();
+ // Used to inject S services
+ _serviceExec = Executors.newFixedThreadPool(cores);
+ // Used to inject S Aspects
+ _aspectExec = Executors.newFixedThreadPool(cores);
+
+ // Setup test components using dependency manager.
+ // We create a Controller which is injected with some S services,
+ // and each S services has some aspects (SAspect).
+
+ _dm = new DependencyManager(_bctx);
+ Controller controller = new Controller();
+ Component c = _dm
+ .createComponent()
+ .setImplementation(controller)
+ .setInterface(Controller.class.getName(), null)
+ .setComposition("getComposition")
+ .add(_dm.createServiceDependency().setService(S.class)
+ .setCallbacks("bind", null, "unbind", "swap")
+ .setRequired(true));
+
+ _dm.add(c);
+
+ for (int loop = 1; loop <= 10000; loop++) {
+ // Perform concurrent injections of "S" service and S aspects
+ // into the Controller component;
+ Factory f = new Factory();
+ f.register();
+
+ controller.check();
+
+ // unregister all services and aspects concurrently
+ f.unregister();
+
+ if ((loop) % 100 == 0) {
+ warn("Performed " + loop + " tests.");
+ }
+ }
+
+ if (super.errorsLogged()) {
+ Assert.assertFalse("Test failed.", false);
+ }
+ }
+
+ catch (Throwable t) {
+ error("Test failed", t);
+ Assert.fail("Test failed: " + t.getMessage());
+ } finally {
+ shutdown(_serviceExec);
+ shutdown(_aspectExec);
+ }
+ }
+
+ void shutdown(ExecutorService exec) {
+ exec.shutdown();
+ try {
+ exec.awaitTermination(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ public interface S {
+ void invoke(Ensure e);
+ }
+
+ public static class SImpl implements S {
+ final int _step;
+
+ SImpl(int step) {
+ _step = step;
+ }
+
+ public void invoke(Ensure e) {
+ e.step(_step);
+ }
+
+ public String toString() {
+ return "SImpl[" + _step + "]";
+ }
+ }
+
+ public static class SAspect implements S {
+ volatile S _next;
+ final int _rank;
+ final int _step;
+
+ SAspect(int rank) {
+ _rank = rank;
+ _step = ASPECTS_PER_SERVICE - rank + 1;
+ }
+
+ public void added(S s) {
+ _next = s;
+ }
+
+ public void swap(S oldS, S newS) {
+ _next = newS;
+ }
+
+ public void invoke(Ensure e) {
+ e.step(_step);
+ _next.invoke(e);
+ }
+
+ public String toString() {
+ return "SAspect[" + _rank + ", " + _step + "]";
+ }
+ }
+
+ class Factory {
+ final ConcurrentLinkedQueue<Component> m_services = new ConcurrentLinkedQueue<Component>();
+ final ConcurrentLinkedQueue<Component> m_aspects = new ConcurrentLinkedQueue<Component>();
+
+ public void register() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(SERVICES
+ + (ASPECTS_PER_SERVICE * SERVICES));
+
+ for (int i = 1; i <= SERVICES; i++) {
+ final int serviceId = i;
+ _serviceExec.execute(new Runnable() {
+ public void run() {
+ try {
+ Component c = _dm.createComponent();
+ Hashtable<String, String> props = new Hashtable<String, String>();
+ props.put("id", String.valueOf(serviceId));
+ c.setInterface(S.class.getName(), props)
+ .setImplementation(
+ new SImpl(ASPECTS_PER_SERVICE + 1));
+ m_services.add(c);
+ _dm.add(c);
+ latch.countDown();
+ } catch (Throwable e) {
+ error(e);
+ }
+ }
+ });
+
+ for (int j = 1; j <= ASPECTS_PER_SERVICE; j++) {
+ final int rank = j;
+ _aspectExec.execute(new Runnable() {
+ public void run() {
+ try {
+ SAspect sa = new SAspect(rank);
+ Component aspect = _dm.createAspectService(
+ S.class, "(id=" + serviceId + ")",
+ rank, "added", null, null, "swap")
+ .setImplementation(sa);
+ debug("adding aspect " + sa);
+ m_aspects.add(aspect);
+ _dm.add(aspect);
+ latch.countDown();
+ } catch (Throwable e) {
+ error(e);
+ }
+ }
+ });
+ }
+ }
+
+ if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException(
+ "could not register services and aspects timely");
+ }
+
+ debug("all registered: aspects=" + m_aspects);
+ // Thread.sleep(5000);
+ }
+
+ public void unregister() throws InterruptedException,
+ InvalidSyntaxException {
+ final CountDownLatch latch = new CountDownLatch(SERVICES
+ + (ASPECTS_PER_SERVICE * SERVICES));
+
+ unregisterAspects(latch);
+ unregisterServices(latch);
+
+ if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException(
+ "could not unregister services and aspects timely");
+ }
+
+ if (_bctx.getServiceReference(S.class.getName()) != null) {
+ error("could not unregister some services or aspects !");
+ }
+ debug("unregistered all aspects and services concurrently");
+ }
+
+ public void unregisterAspects(final CountDownLatch latch)
+ throws InterruptedException, InvalidSyntaxException {
+ Component c;
+ debug("unregister: aspects=" + m_aspects);
+
+ while ((c = m_aspects.poll()) != null) {
+ final Component c$ = c;
+ _serviceExec.execute(new Runnable() {
+ public void run() {
+ try {
+ debug("removing service " + c$);
+ _dm.remove(c$);
+ latch.countDown();
+ } catch (Throwable e) {
+ error(e);
+ }
+ }
+ });
+ }
+ }
+
+ public void unregisterServices(final CountDownLatch latch)
+ throws InterruptedException {
+ Component c;
+ debug("unregister: services=" + m_services);
+
+ while ((c = m_services.poll()) != null) {
+ final Component c$ = c;
+ _serviceExec.execute(new Runnable() {
+ public void run() {
+ try {
+ debug("removing service " + c$);
+ _dm.remove(c$);
+ latch.countDown();
+ } catch (Throwable e) {
+ error(e);
+ }
+ }
+ });
+ }
+
+ debug("unregistered all services");
+ }
+ }
+
+ public class Controller {
+ final Composition m_compo = new Composition();
+ final HashSet<S> _services = new HashSet<S>();
+
+ Object[] getComposition() {
+ return new Object[] { this, m_compo };
+ }
+
+ void bind(ServiceReference sr) {
+ S s = (S) sr.getBundle().getBundleContext().getService(sr);
+ if (s == null) {
+ throw new IllegalStateException(
+ "bindA: bundleContext.getService returned null");
+ }
+ debug("bind " + s);
+ synchronized (this) {
+ _services.add(s);
+ }
+ }
+
+ void swap(S previous, S current) {
+ debug("swap: " + previous + "," + current);
+ synchronized (this) {
+ if (!_services.remove(previous)) {
+ error("swap: unknow previous counter: " + previous);
+ }
+ _services.add(current);
+ }
+ }
+
+ void unbind(S a) {
+ debug("unbind " + a);
+ synchronized (this) {
+ _services.remove(a);
+ }
+ }
+
+ void check() {
+ synchronized (this) {
+ for (S s : _services) {
+ debug("checking service: " + s + " ...");
+ Ensure ensure = new Ensure(false);
+ s.invoke(ensure);
+ }
+ }
+ }
+ }
+
+ public static class Composition {
+ }
+}
diff --git a/dependencymanager/test2/src/test/java/org/apache/felix/dependencymanager/test2/integration/common/TestBase.java b/dependencymanager/test2/src/test/java/org/apache/felix/dependencymanager/test2/integration/common/TestBase.java
new file mode 100644
index 0000000..5c01334
--- /dev/null
+++ b/dependencymanager/test2/src/test/java/org/apache/felix/dependencymanager/test2/integration/common/TestBase.java
@@ -0,0 +1,313 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dependencymanager.test2.integration.common;
+
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.cleanCaches;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.systemTimeout;
+import static org.ops4j.pax.exam.CoreOptions.vmOption;
+import static org.ops4j.pax.exam.CoreOptions.workingDirectory;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Hashtable;
+
+import javax.inject.Inject;
+
+import org.apache.felix.dependencymanager.test2.components.Ensure;
+import org.junit.After;
+import org.junit.Before;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.OptionUtils;
+import org.ops4j.pax.exam.ProbeBuilder;
+import org.ops4j.pax.exam.TestProbeBuilder;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogService;
+
+public abstract class TestBase implements LogService, FrameworkListener {
+ public static final String OSGI_SPEC_VERSION = "4.2.0";
+ private final static int LOG_LEVEL = LogService.LOG_WARNING;
+ private volatile boolean m_errorsLogged;
+
+ // the name of the system property providing the bundle file to be installed
+ // and tested
+ protected static final String BUNDLE_JAR_SYS_PROP = "project.bundle.file";
+
+ // the default bundle jar file name
+ protected static final String BUNDLE_JAR_DEFAULT = "target/org.apache.felix.dependencymanager.test2-3.0.2-SNAPSHOT.jar";
+
+ // the JVM option to set to enable remote debugging
+ protected static final String DEBUG_VM_OPTION = "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=30303";
+
+ // the actual JVM option set, extensions may implement a static
+ // initializer overwriting this value to have the configuration()
+ // method include it when starting the OSGi framework JVM
+ protected static String paxRunnerVmOption = null;
+
+ @Inject
+ protected BundleContext context;
+
+ protected ServiceRegistration logService;
+
+ @Configuration
+ public static Option[] configuration() {
+ final String bundleFileName = System.getProperty(BUNDLE_JAR_SYS_PROP, BUNDLE_JAR_DEFAULT);
+ final File bundleFile = new File(bundleFileName);
+ if (!bundleFile.canRead()) {
+ throw new IllegalArgumentException("Cannot read from bundle file " + bundleFileName
+ + " specified in the " + BUNDLE_JAR_SYS_PROP + " system property");
+ }
+
+ final Option[] base = options(
+ workingDirectory("target/paxexam/"),
+ systemProperty("dm.runtime.log").value("false"),
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("WARN"),
+ systemTimeout(5000),
+ cleanCaches(true),
+ junitBundles(),
+ mavenBundle("org.ops4j.pax.tinybundles", "tinybundles", "1.0.0"),
+ mavenBundle("org.apache.felix", "org.apache.felix.dependencymanager",
+ "3.1.1-SNAPSHOT"),
+ mavenBundle("org.apache.felix", "org.apache.felix.dependencymanager.runtime",
+ "3.1.1-SNAPSHOT"),
+ mavenBundle("org.apache.felix", "org.apache.felix.configadmin", "1.6.0"),
+ mavenBundle("org.apache.felix", "org.apache.felix.metatype", "1.0.8"),
+ bundle(bundleFile.toURI().toString()));
+ final Option option = (paxRunnerVmOption != null) ? vmOption(paxRunnerVmOption) : null;
+ return OptionUtils.combine(base, option);
+ }
+
+ @ProbeBuilder
+ public TestProbeBuilder buildProbe(TestProbeBuilder builder) {
+ return builder.setHeader(Constants.IMPORT_PACKAGE,
+ "org.apache.felix.dependencymanager.test2.components");
+ }
+
+ @Before
+ public void setUp() {
+ logService = context.registerService(LogService.class.getName(), this, null);
+ context.addFrameworkListener(this);
+ }
+
+ @After
+ public void tearDown() throws BundleException {
+ logService.unregister();
+ context.removeFrameworkListener(this);
+ }
+
+ /**
+ * Create and provide an Ensure object with a name service property.
+ */
+ protected ServiceRegistration register(Ensure e, String name) {
+ Hashtable<String, String> props = new Hashtable<String, String>();
+ props.put("name", name);
+ return context.registerService(Ensure.class.getName(), e, props);
+ }
+
+ /**
+ * Helper method used to stop a given bundle.
+ *
+ * @param symbolicName
+ * the symbolic name of the bundle to be stopped.
+ * @param context
+ * the context used to lookup all installed bundles.
+ */
+ protected void stopBundle(String symbolicName) {
+ // Stop the test.annotation bundle
+ boolean found = false;
+ for (Bundle b : context.getBundles()) {
+ if (b.getSymbolicName().equals(symbolicName)) {
+ try {
+ found = true;
+ b.stop();
+ } catch (BundleException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ if (!found) {
+ throw new IllegalStateException("bundle " + symbolicName + " not found");
+ }
+ }
+
+ /**
+ * Stops our test components bundle.
+ */
+ protected void stopTestComponentsBundle() {
+ stopBundle("org.apache.felix.dependencymanager.test2.components");
+ }
+
+ /**
+ * Suspend the current thread for a while.
+ *
+ * @param n
+ * the number of milliseconds to wait for.
+ */
+ protected void sleep(int ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ public void log(int level, String message) {
+ checkError(level, null);
+ if (LOG_LEVEL >= level) {
+ System.out.println(getLevel(level) + " - " + Thread.currentThread().getName() + " : " + message);
+ }
+ }
+
+ public void log(int level, String message, Throwable exception) {
+ checkError(level, exception);
+ if (LOG_LEVEL >= level) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getLevel(level) + " - " + Thread.currentThread().getName() + " : ");
+ sb.append(message);
+ parse(sb, exception);
+ System.out.println(sb.toString());
+ }
+ }
+
+ public void log(ServiceReference sr, int level, String message) {
+ checkError(level, null);
+ if (LOG_LEVEL >= level) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getLevel(level) + " - " + Thread.currentThread().getName() + " : ");
+ sb.append(message);
+ System.out.println(sb.toString());
+ }
+ }
+
+ public void log(ServiceReference sr, int level, String message, Throwable exception) {
+ checkError(level, exception);
+ if (LOG_LEVEL >= level) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getLevel(level) + " - " + Thread.currentThread().getName() + " : ");
+ sb.append(message);
+ parse(sb, exception);
+ System.out.println(sb.toString());
+ }
+ }
+
+ protected boolean errorsLogged() {
+ return m_errorsLogged;
+ }
+
+ private void parse(StringBuilder sb, Throwable t) {
+ if (t != null) {
+ sb.append(" - ");
+ StringWriter buffer = new StringWriter();
+ PrintWriter pw = new PrintWriter(buffer);
+ t.printStackTrace(pw);
+ sb.append(buffer.toString());
+ m_errorsLogged = true;
+ }
+ }
+
+ private String getLevel(int level) {
+ switch (level) {
+ case LogService.LOG_DEBUG :
+ return "DEBUG";
+ case LogService.LOG_ERROR :
+ return "ERROR";
+ case LogService.LOG_INFO :
+ return "INFO";
+ case LogService.LOG_WARNING :
+ return "WARN";
+ default :
+ return "";
+ }
+ }
+
+ private void checkError(int level, Throwable exception) {
+ if (level >= LOG_ERROR) {
+ m_errorsLogged = true;
+ }
+ if (exception != null) {
+ m_errorsLogged = true;
+ }
+ }
+
+ public void frameworkEvent(FrameworkEvent event) {
+ int eventType = event.getType();
+ String msg = getFrameworkEventMessage(eventType);
+ int level = (eventType == FrameworkEvent.ERROR) ? LOG_ERROR : LOG_WARNING;
+ if (msg != null) {
+ log(level, msg, event.getThrowable());
+ } else {
+ log(level, "Unknown fwk event: " + event);
+ }
+ }
+
+ private String getFrameworkEventMessage(int event) {
+ switch (event) {
+ case FrameworkEvent.ERROR :
+ return "FrameworkEvent: ERROR";
+ case FrameworkEvent.INFO :
+ return "FrameworkEvent INFO";
+ case FrameworkEvent.PACKAGES_REFRESHED :
+ return "FrameworkEvent: PACKAGE REFRESHED";
+ case FrameworkEvent.STARTED :
+ return "FrameworkEvent: STARTED";
+ case FrameworkEvent.STARTLEVEL_CHANGED :
+ return "FrameworkEvent: STARTLEVEL CHANGED";
+ case FrameworkEvent.WARNING :
+ return "FrameworkEvent: WARNING";
+ default :
+ return null;
+ }
+ }
+
+ protected void warn(String msg) {
+ log(LogService.LOG_WARNING, "[" + Thread.currentThread().getName() + "] " + msg);
+ }
+
+ protected void info(String msg) {
+ log(LogService.LOG_INFO, "[" + Thread.currentThread().getName() + "] " + msg);
+ }
+
+ protected void debug(String msg) {
+ log(LogService.LOG_DEBUG, "[" + Thread.currentThread().getName() + "] " + msg);
+ }
+
+ protected void error(String msg) {
+ log(LogService.LOG_ERROR, "[" + Thread.currentThread().getName() + "] " + msg);
+ }
+
+ protected void error(String msg, Throwable err) {
+ log(LogService.LOG_ERROR, "[" + Thread.currentThread().getName() + "] " + msg, err);
+ }
+
+ protected void error(Throwable err) {
+ log(LogService.LOG_ERROR, "[" + Thread.currentThread().getName() + "] error:", err);
+ }
+}