| /* |
| * Copyright (C) 2007 The Guava Authors |
| * |
| * Licensed 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 com.google.common.eventbus; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| |
| import junit.framework.TestCase; |
| |
| import java.util.List; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * Test case for {@link EventBus}. |
| * |
| * @author Cliff Biffle |
| */ |
| public class EventBusTest extends TestCase { |
| private static final String EVENT = "Hello"; |
| private static final String BUS_IDENTIFIER = "test-bus"; |
| |
| private EventBus bus; |
| |
| @Override protected void setUp() throws Exception { |
| super.setUp(); |
| bus = new EventBus(BUS_IDENTIFIER); |
| } |
| |
| public void testBasicCatcherDistribution() { |
| StringCatcher catcher = new StringCatcher(); |
| bus.register(catcher); |
| bus.post(EVENT); |
| |
| List<String> events = catcher.getEvents(); |
| assertEquals("Only one event should be delivered.", 1, events.size()); |
| assertEquals("Correct string should be delivered.", EVENT, events.get(0)); |
| } |
| |
| /** |
| * Tests that events are distributed to any subscribers to their type or any |
| * supertype, including interfaces and superclasses. |
| * |
| * Also checks delivery ordering in such cases. |
| */ |
| public void testPolymorphicDistribution() { |
| // Three catchers for related types String, Object, and Comparable<?>. |
| // String isa Object |
| // String isa Comparable<?> |
| // Comparable<?> isa Object |
| StringCatcher stringCatcher = new StringCatcher(); |
| |
| final List<Object> objectEvents = Lists.newArrayList(); |
| Object objCatcher = new Object() { |
| @SuppressWarnings("unused") |
| @Subscribe public void eat(Object food) { |
| objectEvents.add(food); |
| } |
| }; |
| |
| final List<Comparable<?>> compEvents = Lists.newArrayList(); |
| Object compCatcher = new Object() { |
| @SuppressWarnings("unused") |
| @Subscribe public void eat(Comparable<?> food) { |
| compEvents.add(food); |
| } |
| }; |
| bus.register(stringCatcher); |
| bus.register(objCatcher); |
| bus.register(compCatcher); |
| |
| // Two additional event types: Object and Comparable<?> (played by Integer) |
| Object objEvent = new Object(); |
| Object compEvent = new Integer(6); |
| |
| bus.post(EVENT); |
| bus.post(objEvent); |
| bus.post(compEvent); |
| |
| // Check the StringCatcher... |
| List<String> stringEvents = stringCatcher.getEvents(); |
| assertEquals("Only one String should be delivered.", |
| 1, stringEvents.size()); |
| assertEquals("Correct string should be delivered.", |
| EVENT, stringEvents.get(0)); |
| |
| // Check the Catcher<Object>... |
| assertEquals("Three Objects should be delivered.", |
| 3, objectEvents.size()); |
| assertEquals("String fixture must be first object delivered.", |
| EVENT, objectEvents.get(0)); |
| assertEquals("Object fixture must be second object delivered.", |
| objEvent, objectEvents.get(1)); |
| assertEquals("Comparable fixture must be thirdobject delivered.", |
| compEvent, objectEvents.get(2)); |
| |
| // Check the Catcher<Comparable<?>>... |
| assertEquals("Two Comparable<?>s should be delivered.", |
| 2, compEvents.size()); |
| assertEquals("String fixture must be first comparable delivered.", |
| EVENT, compEvents.get(0)); |
| assertEquals("Comparable fixture must be second comparable delivered.", |
| compEvent, compEvents.get(1)); |
| } |
| |
| public void testSubscriberThrowsException() throws Exception{ |
| final RecordingSubscriberExceptionHandler handler = |
| new RecordingSubscriberExceptionHandler(); |
| final EventBus eventBus = new EventBus(handler); |
| final RuntimeException exception = |
| new RuntimeException("but culottes have a tendancy to ride up!"); |
| final Object subscriber = new Object() { |
| @Subscribe |
| public void throwExceptionOn(String message) { |
| throw exception; |
| } |
| }; |
| eventBus.register(subscriber); |
| eventBus.post(EVENT); |
| |
| assertEquals("Cause should be available.", |
| exception, handler.exception); |
| assertEquals("EventBus should be available.", |
| eventBus, handler.context.getEventBus()); |
| assertEquals("Event should be available.", |
| EVENT, |
| handler.context.getEvent()); |
| assertEquals("Subscriber should be available.", |
| subscriber, handler.context.getSubscriber()); |
| assertEquals("Method should be available.", |
| subscriber.getClass().getMethod("throwExceptionOn", String.class), |
| handler.context.getSubscriberMethod()); |
| } |
| |
| public void testSubscriberThrowsExceptionHandlerThrowsException() throws Exception{ |
| final EventBus eventBus = new EventBus(new SubscriberExceptionHandler() { |
| @Override |
| public void handleException(Throwable exception, |
| SubscriberExceptionContext context) { |
| throw new RuntimeException(); |
| } |
| }); |
| final Object subscriber = new Object() { |
| @Subscribe |
| public void throwExceptionOn(String message) { |
| throw new RuntimeException(); |
| } |
| }; |
| eventBus.register(subscriber); |
| try { |
| eventBus.post(EVENT); |
| } catch (RuntimeException e) { |
| fail("Exception should not be thrown."); |
| } |
| } |
| |
| public void testDeadEventForwarding() { |
| GhostCatcher catcher = new GhostCatcher(); |
| bus.register(catcher); |
| |
| // A String -- an event for which noone has registered. |
| bus.post(EVENT); |
| |
| List<DeadEvent> events = catcher.getEvents(); |
| assertEquals("One dead event should be delivered.", 1, events.size()); |
| assertEquals("The dead event should wrap the original event.", |
| EVENT, events.get(0).getEvent()); |
| } |
| |
| public void testDeadEventPosting() { |
| GhostCatcher catcher = new GhostCatcher(); |
| bus.register(catcher); |
| |
| bus.post(new DeadEvent(this, EVENT)); |
| |
| List<DeadEvent> events = catcher.getEvents(); |
| assertEquals("The explicit DeadEvent should be delivered.", |
| 1, events.size()); |
| assertEquals("The dead event must not be re-wrapped.", |
| EVENT, events.get(0).getEvent()); |
| } |
| |
| public void testMissingSubscribe() { |
| bus.register(new Object()); |
| } |
| |
| public void testUnregister() { |
| StringCatcher catcher1 = new StringCatcher(); |
| StringCatcher catcher2 = new StringCatcher(); |
| try { |
| bus.unregister(catcher1); |
| fail("Attempting to unregister an unregistered object succeeded"); |
| } catch (IllegalArgumentException expected) { |
| // OK. |
| } |
| |
| bus.register(catcher1); |
| bus.post(EVENT); |
| bus.register(catcher2); |
| bus.post(EVENT); |
| |
| List<String> expectedEvents = Lists.newArrayList(); |
| expectedEvents.add(EVENT); |
| expectedEvents.add(EVENT); |
| |
| assertEquals("Two correct events should be delivered.", |
| expectedEvents, catcher1.getEvents()); |
| |
| assertEquals("One correct event should be delivered.", |
| Lists.newArrayList(EVENT), catcher2.getEvents()); |
| |
| bus.unregister(catcher1); |
| bus.post(EVENT); |
| |
| assertEquals("Shouldn't catch any more events when unregistered.", |
| expectedEvents, catcher1.getEvents()); |
| assertEquals("Two correct events should be delivered.", |
| expectedEvents, catcher2.getEvents()); |
| |
| try { |
| bus.unregister(catcher1); |
| fail("Attempting to unregister an unregistered object succeeded"); |
| } catch (IllegalArgumentException expected) { |
| // OK. |
| } |
| |
| bus.unregister(catcher2); |
| bus.post(EVENT); |
| assertEquals("Shouldn't catch any more events when unregistered.", |
| expectedEvents, catcher1.getEvents()); |
| assertEquals("Shouldn't catch any more events when unregistered.", |
| expectedEvents, catcher2.getEvents()); |
| } |
| |
| // NOTE: This test will always pass if register() is thread-safe but may also |
| // pass if it isn't, though this is unlikely. |
| |
| public void testRegisterThreadSafety() throws Exception { |
| List<StringCatcher> catchers = Lists.newCopyOnWriteArrayList(); |
| List<Future<?>> futures = Lists.newArrayList(); |
| ExecutorService executor = Executors.newFixedThreadPool(10); |
| int numberOfCatchers = 10000; |
| for (int i = 0; i < numberOfCatchers; i++) { |
| futures.add(executor.submit(new Registrator(bus, catchers))); |
| } |
| for (int i = 0; i < numberOfCatchers; i++) { |
| futures.get(i).get(); |
| } |
| assertEquals("Unexpected number of catchers in the list", |
| numberOfCatchers, catchers.size()); |
| bus.post(EVENT); |
| List<String> expectedEvents = ImmutableList.of(EVENT); |
| for (StringCatcher catcher : catchers) { |
| assertEquals("One of the registered catchers did not receive an event.", |
| expectedEvents, catcher.getEvents()); |
| } |
| } |
| |
| /** |
| * Tests that bridge methods are not subscribed to events. In Java 8, |
| * annotations are included on the bridge method in addition to the original |
| * method, which causes both the original and bridge methods to be subscribed |
| * (since both are annotated @Subscribe) without specifically checking for |
| * bridge methods. |
| */ |
| public void testRegistrationWithBridgeMethod() { |
| final AtomicInteger calls = new AtomicInteger(); |
| bus.register(new Callback<String>() { |
| @Subscribe |
| @Override |
| public void call(String s) { |
| calls.incrementAndGet(); |
| } |
| }); |
| |
| bus.post("hello"); |
| |
| assertEquals(1, calls.get()); |
| } |
| |
| /** |
| * Records thrown exception information. |
| */ |
| private static final class RecordingSubscriberExceptionHandler |
| implements SubscriberExceptionHandler { |
| |
| public SubscriberExceptionContext context; |
| public Throwable exception; |
| |
| @Override |
| public void handleException(Throwable exception, |
| SubscriberExceptionContext context) { |
| this.exception = exception; |
| this.context = context; |
| } |
| } |
| |
| /** |
| * Runnable which registers a StringCatcher on an event bus and adds it to a |
| * list. |
| */ |
| private static class Registrator implements Runnable { |
| private final EventBus bus; |
| private final List<StringCatcher> catchers; |
| |
| Registrator(EventBus bus, List<StringCatcher> catchers) { |
| this.bus = bus; |
| this.catchers = catchers; |
| } |
| |
| @Override |
| public void run() { |
| StringCatcher catcher = new StringCatcher(); |
| bus.register(catcher); |
| catchers.add(catcher); |
| } |
| } |
| |
| /** |
| * A collector for DeadEvents. |
| * |
| * @author cbiffle |
| * |
| */ |
| public static class GhostCatcher { |
| private List<DeadEvent> events = Lists.newArrayList(); |
| |
| @Subscribe |
| public void ohNoesIHaveDied(DeadEvent event) { |
| events.add(event); |
| } |
| |
| public List<DeadEvent> getEvents() { |
| return events; |
| } |
| } |
| |
| private interface Callback<T> { |
| void call(T t); |
| } |
| } |