FELIX-4608 : Merge Performance IT into Event Admin. Apply patch from Bob Paulin
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1619324 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/eventadmin/impl/pom.xml b/eventadmin/impl/pom.xml
index c972e67..be27d41 100644
--- a/eventadmin/impl/pom.xml
+++ b/eventadmin/impl/pom.xml
@@ -49,6 +49,68 @@
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
+ <!-- Unit Testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.10</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jmock</groupId>
+ <artifactId>jmock-junit4</artifactId>
+ <version>2.5.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.5.2</version>
+ <scope>test</scope>
+ </dependency>
+ <!-- Integration Testing with Pax Exam -->
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-forked</artifactId>
+ <version>4.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <version>4.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <version>4.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.url</groupId>
+ <artifactId>pax-url-aether</artifactId>
+ <version>2.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.url</groupId>
+ <artifactId>pax-url-wrap</artifactId>
+ <version>2.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-atinject_1.0_spec</artifactId>
+ <version>1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>4.4.1</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<resources>
@@ -122,4 +184,40 @@
</plugin>
</plugins>
</build>
+ <profiles>
+ <profile>
+ <id>java6</id>
+ <activation>
+ <jdk>1.6</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <!-- integration tests run with pax-exam -->
+ <plugin>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>2.12</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <systemProperties>
+ <property>
+ <name>eventadmin.version</name>
+ <value>${project.version}</value>
+ </property>
+ </systemProperties>
+ <includes>
+ <include>**/*IT.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
</project>
diff --git a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java
new file mode 100644
index 0000000..9ea3848
--- /dev/null
+++ b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.eventadmin.ittests;
+
+import static org.junit.Assert.assertNotNull;
+import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.options.AbstractDelegateProvisionOption;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+@RunWith(PaxExam.class)
+public abstract class AbstractTest implements Runnable {
+
+ /** the name of the system property providing the event admin version. */
+ private static final String EVENT_ADMIN_VERSION_PROP = "eventadmin.version";
+
+ /** The logger. */
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Inject
+ protected BundleContext bundleContext;
+
+ /** Event admin. */
+ private EventAdmin eventAdmin;
+
+ /** Event admin reference. */
+ private ServiceReference eventAdminReference;
+
+ private volatile boolean running = false;
+
+ private volatile int nrEvents;
+
+ private volatile int eventCount = 0;
+
+ private volatile int finishEventCount;
+
+ private volatile String prefix;
+
+ private volatile long startTime;
+
+ /** Wait lock for syncing. */
+ private final Object waitLock = new Object();
+
+ /**
+ * Handle an event from the listener.
+ * @param event
+ * @param payload
+ */
+ public void handleEvent(final Event event, final Object payload) {
+ if ( this.running ) {
+ if ( prefix == null || event.getTopic().startsWith(prefix) ) {
+ incCount();
+ }
+ }
+ }
+
+ /**
+ * Send an event
+ * @param topic
+ * @param properties
+ * @param sync
+ */
+ protected void send(String topic, Dictionary<String, Object> properties, boolean sync) {
+ final Event event = new Event(topic, properties);
+ if ( sync ) {
+ getEventAdmin().sendEvent(event);
+ } else {
+ getEventAdmin().postEvent(event);
+ }
+ }
+
+ private synchronized void incCount() {
+ eventCount++;
+ if ( eventCount >= finishEventCount) {
+ final long duration = this.startTime == -1 ? -1 : System.currentTimeMillis() - this.startTime;
+ logger.info("Finished tests, received {} events in {}ms", eventCount, duration);
+ }
+ }
+
+ private synchronized int getCount() {
+ return eventCount;
+ }
+
+ protected void start(final String prefix, final int nrThreads, final int nrEvents, final int finishCount) {
+ logger.info("Starting eventing test {}", this.getClass().getSimpleName());
+
+ this.startTime = -1;
+ this.prefix = prefix;
+ this.nrEvents = nrEvents;
+ finishEventCount = finishCount;
+ eventCount = 0;
+ logger.info("Preparing test with {} threads and {} events per thread.", nrThreads, nrEvents);
+ logger.info("Expecting {} events.", finishCount);
+ this.running = true;
+ final Thread[] threads = new Thread[nrThreads];
+ for(int i = 0; i < threads.length; i++) {
+ threads[i] = new Thread(this);
+ }
+ for(int i = 0; i < threads.length; i++) {
+ threads[i].start();
+ }
+ final Thread infoT = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while ( running ) {
+ logger.info("Received {} events so far.", getCount());
+ try {
+ Thread.sleep(1000 * 5);
+ } catch (final InterruptedException ignore) {
+ // ignore
+ }
+ if ( getCount() >= finishCount) {
+ running = false;
+ synchronized ( waitLock ) {
+ waitLock.notify();
+ }
+ }
+ }
+ }
+ });
+ infoT.start();
+ logger.info("Started test with {} threads and {} events.", nrThreads, nrEvents);
+ this.execute();
+ }
+
+ private void waitForFinish() {
+ while ( this.running ) {
+ synchronized ( this.waitLock ) {
+ try {
+ this.waitLock.wait();
+ } catch (final InterruptedException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ protected abstract void sendEvent(final int index);
+
+ /**
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(1000 * 10);
+ } catch (final InterruptedException ignore) {
+ // ignore
+ }
+ synchronized ( this ) {
+ if ( this.startTime == -1 ) {
+ this.startTime = System.currentTimeMillis();
+ }
+ }
+ logger.info("Started thread");
+ int index = 0;
+ while ( this.running && index < nrEvents ) {
+ this.sendEvent(index);
+
+ index++;
+ }
+ logger.info("Send {} events.", index);
+ }
+
+ private final List<Listener> listeners = new ArrayList<Listener>();
+
+ public void addListener(final String topic, final Object payload) {
+ this.listeners.add(new Listener(this.bundleContext, this, topic, payload));
+ }
+
+ private void stop() {
+ for(final Listener l : listeners) {
+ l.dispose();
+ }
+ listeners.clear();
+ }
+
+ /**
+ * Helper method to get a service of the given type
+ */
+ @SuppressWarnings("unchecked")
+ protected <T> T getService(Class<T> clazz) {
+ final ServiceReference ref = bundleContext.getServiceReference(clazz.getName());
+ assertNotNull("getService(" + clazz.getName() + ") must find ServiceReference", ref);
+ final T result = (T)(bundleContext.getService(ref));
+ assertNotNull("getService(" + clazz.getName() + ") must find service", result);
+ return result;
+ }
+
+ protected EventAdmin getEventAdmin() {
+ if ( eventAdminReference == null || eventAdminReference.getBundle() == null ) {
+ eventAdmin = null;
+ eventAdminReference = bundleContext.getServiceReference(EventAdmin.class.getName());
+ }
+ if ( eventAdmin == null && eventAdminReference != null ) {
+ eventAdmin = (EventAdmin) bundleContext.getService(eventAdminReference);
+ }
+ return eventAdmin;
+ }
+
+ @Configuration
+ public static Option[] configuration() {
+ final String eventAdminVersion = System.getProperty( EVENT_ADMIN_VERSION_PROP, "1.3.3-SNAPSHOT" );
+ return options(
+ provision(
+ mavenBundle( "org.ops4j.pax.tinybundles", "tinybundles", "1.0.0" ),
+ mavenBundle("org.apache.sling", "org.apache.sling.commons.log", "2.1.2"),
+ mavenBundle("org.apache.felix", "org.apache.felix.configadmin", "1.2.8"),
+ mavenBundle("org.apache.felix", "org.apache.felix.metatype", "1.0.4"),
+ mavenBundle("org.apache.felix", "org.apache.felix.eventadmin", eventAdminVersion),
+ mavenBundle("org.ops4j.pax.url", "pax-url-mvn", "1.3.5")
+ ),
+ // below is instead of normal Pax Exam junitBundles() to deal
+ // with build server issue
+ new DirectURLJUnitBundlesOption(),
+ systemProperty("pax.exam.invoker").value("junit"),
+ bundle("link:classpath:META-INF/links/org.ops4j.pax.exam.invoker.junit.link")
+ );
+ }
+
+ /**
+ * Execute a single test.
+ * @param test
+ */
+ private void execute() {
+ this.waitForFinish();
+ this.stop();
+ logger.info("Finished eventing test {}", this.getClass().getSimpleName());
+ try {
+ Thread.sleep(15 * 1000);
+ } catch (final InterruptedException ie) {
+ // ignore
+ }
+ }
+
+
+ /**
+ * Clone of Pax Exam's JunitBundlesOption which uses a direct
+ * URL to the SpringSource JUnit bundle to avoid some weird
+ * repository issues on the Apache build server.
+ */
+ private static class DirectURLJUnitBundlesOption
+ extends AbstractDelegateProvisionOption<DirectURLJUnitBundlesOption> {
+
+ /**
+ * Constructor.
+ */
+ public DirectURLJUnitBundlesOption(){
+ super(
+ bundle("http://repository.springsource.com/ivy/bundles/external/org.junit/com.springsource.org.junit/4.9.0/com.springsource.org.junit-4.9.0.jar")
+ );
+ noUpdate();
+ startLevel(START_LEVEL_SYSTEM_BUNDLES);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return String.format("DirectURLJUnitBundlesOption{url=%s}", getURL());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected DirectURLJUnitBundlesOption itself() {
+ return this;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java
new file mode 100644
index 0000000..b06e6e3
--- /dev/null
+++ b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java
@@ -0,0 +1,62 @@
+/*
+ * 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.eventadmin.ittests;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+public class Listener implements EventHandler {
+
+ private final ServiceRegistration reg;
+
+ private final Object payload;
+
+ private final AbstractTest test;
+
+ public Listener(final BundleContext ctx, final AbstractTest test, final String topic, final Object payload) {
+ this(ctx, test, topic != null ? new String[] {topic} : null, payload);
+ }
+
+ public Listener(final BundleContext ctx, final AbstractTest test, final String[] topics, final Object payload) {
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ if ( topics != null ) {
+ props.put("event.topics", topics);
+ } else {
+ props.put("event.topics", "*");
+ }
+ this.test = test;
+ this.reg = ctx.registerService(EventHandler.class.getName(), this, props);
+ this.payload = payload;
+ }
+
+ public void dispose() {
+ this.reg.unregister();
+ }
+
+ /**
+ * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
+ */
+ @Override
+ public void handleEvent(final Event event) {
+ this.test.handleEvent(event, this.payload);
+ }
+}
diff --git a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/StressTestIT.java b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/StressTestIT.java
new file mode 100644
index 0000000..dcc57c3
--- /dev/null
+++ b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/StressTestIT.java
@@ -0,0 +1,55 @@
+/*
+ * 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.eventadmin.ittests;
+
+import org.junit.Test;
+
+public class StressTestIT extends AbstractTest {
+
+ private static final String PREFIX = "org/apache/felix/eventing/test";
+ private static final int THREADS = 15;
+ private static final int EVENTS_PER_THREAD = 10000;
+
+ @Override
+ protected void sendEvent(int index) {
+ final String postFix = String.valueOf(index % 10);
+ final String topic = PREFIX + '/' + postFix;
+ this.send(topic, null, true);
+ }
+
+ @Test
+ public void testEventing() throws Exception {
+ this.addListener(PREFIX + "/0", null);
+ this.addListener(PREFIX + "/1", null);
+ this.addListener(PREFIX + "/2", null);
+ this.addListener(PREFIX + "/3", null);
+ this.addListener(PREFIX + "/4", null);
+ this.addListener(PREFIX + "/5", null);
+ this.addListener(PREFIX + "/6", null);
+ this.addListener(PREFIX + "/7", null);
+ this.addListener(PREFIX + "/8", null);
+ this.addListener(PREFIX + "/9", null);
+ this.addListener(PREFIX + "/*", null);
+ this.addListener("org/apache/felix/eventing/*", null);
+ this.addListener("org/apache/felix/*", null);
+ this.addListener("org/apache/*", null);
+ this.addListener("org/*", null);
+ this.addListener("*", null);
+
+ this.start(PREFIX, THREADS, EVENTS_PER_THREAD, THREADS * EVENTS_PER_THREAD * (1 + 6));
+ }
+}