Home | History | Annotate | Download | only in eventbus
      1 /*
      2  * Copyright (C) 2007 The Guava Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.common.eventbus;
     18 
     19 import com.google.common.collect.ImmutableList;
     20 import com.google.common.collect.Lists;
     21 
     22 import junit.framework.TestCase;
     23 
     24 import java.util.Collection;
     25 import java.util.List;
     26 import java.util.Set;
     27 import java.util.concurrent.ExecutorService;
     28 import java.util.concurrent.Executors;
     29 import java.util.concurrent.Future;
     30 import java.util.concurrent.atomic.AtomicInteger;
     31 
     32 /**
     33  * Test case for {@link EventBus}.
     34  *
     35  * @author Cliff Biffle
     36  */
     37 public class EventBusTest extends TestCase {
     38   private static final String EVENT = "Hello";
     39   private static final String BUS_IDENTIFIER = "test-bus";
     40 
     41   private EventBus bus;
     42 
     43   @Override protected void setUp() throws Exception {
     44     super.setUp();
     45     bus = new EventBus(BUS_IDENTIFIER);
     46   }
     47 
     48   public void testBasicCatcherDistribution() {
     49     StringCatcher catcher = new StringCatcher();
     50     bus.register(catcher);
     51     bus.post(EVENT);
     52 
     53     List<String> events = catcher.getEvents();
     54     assertEquals("Only one event should be delivered.", 1, events.size());
     55     assertEquals("Correct string should be delivered.", EVENT, events.get(0));
     56   }
     57 
     58   /**
     59    * Tests that events are distributed to any subscribers to their type or any
     60    * supertype, including interfaces and superclasses.
     61    *
     62    * Also checks delivery ordering in such cases.
     63    */
     64   public void testPolymorphicDistribution() {
     65     // Three catchers for related types String, Object, and Comparable<?>.
     66     // String isa Object
     67     // String isa Comparable<?>
     68     // Comparable<?> isa Object
     69     StringCatcher stringCatcher = new StringCatcher();
     70 
     71     final List<Object> objectEvents = Lists.newArrayList();
     72     Object objCatcher = new Object() {
     73       @SuppressWarnings("unused")
     74       @Subscribe public void eat(Object food) {
     75         objectEvents.add(food);
     76       }
     77     };
     78 
     79     final List<Comparable<?>> compEvents = Lists.newArrayList();
     80     Object compCatcher = new Object() {
     81       @SuppressWarnings("unused")
     82       @Subscribe public void eat(Comparable<?> food) {
     83         compEvents.add(food);
     84       }
     85     };
     86     bus.register(stringCatcher);
     87     bus.register(objCatcher);
     88     bus.register(compCatcher);
     89 
     90     // Two additional event types: Object and Comparable<?> (played by Integer)
     91     final Object OBJ_EVENT = new Object();
     92     final Object COMP_EVENT = new Integer(6);
     93 
     94     bus.post(EVENT);
     95     bus.post(OBJ_EVENT);
     96     bus.post(COMP_EVENT);
     97 
     98     // Check the StringCatcher...
     99     List<String> stringEvents = stringCatcher.getEvents();
    100     assertEquals("Only one String should be delivered.",
    101         1, stringEvents.size());
    102     assertEquals("Correct string should be delivered.",
    103         EVENT, stringEvents.get(0));
    104 
    105     // Check the Catcher<Object>...
    106     assertEquals("Three Objects should be delivered.",
    107         3, objectEvents.size());
    108     assertEquals("String fixture must be first object delivered.",
    109         EVENT, objectEvents.get(0));
    110     assertEquals("Object fixture must be second object delivered.",
    111         OBJ_EVENT, objectEvents.get(1));
    112     assertEquals("Comparable fixture must be thirdobject delivered.",
    113         COMP_EVENT, objectEvents.get(2));
    114 
    115     // Check the Catcher<Comparable<?>>...
    116     assertEquals("Two Comparable<?>s should be delivered.",
    117         2, compEvents.size());
    118     assertEquals("String fixture must be first comparable delivered.",
    119         EVENT, compEvents.get(0));
    120     assertEquals("Comparable fixture must be second comparable delivered.",
    121         COMP_EVENT, compEvents.get(1));
    122   }
    123 
    124   public void testSubscriberThrowsException() throws Exception{
    125     final RecordingSubscriberExceptionHandler handler =
    126         new RecordingSubscriberExceptionHandler();
    127     final EventBus eventBus = new EventBus(handler);
    128     final RuntimeException exception =
    129         new RuntimeException("but culottes have a tendancy to ride up!");
    130     final Object subscriber = new Object() {
    131       @Subscribe
    132       public void throwExceptionOn(String message) {
    133         throw exception;
    134       }
    135     };
    136     eventBus.register(subscriber);
    137     eventBus.post(EVENT);
    138 
    139     assertEquals("Cause should be available.",
    140         exception, handler.exception);
    141     assertEquals("EventBus should be available.",
    142         eventBus, handler.context.getEventBus());
    143     assertEquals("Event should be available.",
    144         EVENT,
    145         handler.context.getEvent());
    146     assertEquals("Subscriber should be available.",
    147         subscriber, handler.context.getSubscriber());
    148     assertEquals("Method should be available.",
    149         subscriber.getClass().getMethod("throwExceptionOn", String.class),
    150         handler.context.getSubscriberMethod());
    151   }
    152 
    153   public void testSubscriberThrowsExceptionHandlerThrowsException() throws Exception{
    154     final EventBus eventBus = new EventBus(new SubscriberExceptionHandler() {
    155       @Override
    156       public void handleException(Throwable exception,
    157           SubscriberExceptionContext context) {
    158         throw new RuntimeException();
    159       }
    160     });
    161     final Object subscriber = new Object() {
    162       @Subscribe
    163       public void throwExceptionOn(String message) {
    164         throw new RuntimeException();
    165       }
    166     };
    167     eventBus.register(subscriber);
    168     try {
    169       eventBus.post(EVENT);
    170     } catch (RuntimeException e) {
    171       fail("Exception should not be thrown.");
    172     }
    173   }
    174 
    175   public void testDeadEventForwarding() {
    176     GhostCatcher catcher = new GhostCatcher();
    177     bus.register(catcher);
    178 
    179     // A String -- an event for which noone has registered.
    180     bus.post(EVENT);
    181 
    182     List<DeadEvent> events = catcher.getEvents();
    183     assertEquals("One dead event should be delivered.", 1, events.size());
    184     assertEquals("The dead event should wrap the original event.",
    185         EVENT, events.get(0).getEvent());
    186   }
    187 
    188   public void testDeadEventPosting() {
    189     GhostCatcher catcher = new GhostCatcher();
    190     bus.register(catcher);
    191 
    192     bus.post(new DeadEvent(this, EVENT));
    193 
    194     List<DeadEvent> events = catcher.getEvents();
    195     assertEquals("The explicit DeadEvent should be delivered.",
    196         1, events.size());
    197     assertEquals("The dead event must not be re-wrapped.",
    198         EVENT, events.get(0).getEvent());
    199   }
    200 
    201   public void testFlattenHierarchy() {
    202     HierarchyFixture fixture = new HierarchyFixture();
    203     Set<Class<?>> hierarchy = bus.flattenHierarchy(fixture.getClass());
    204 
    205     assertEquals(5, hierarchy.size());
    206     assertContains(Object.class, hierarchy);
    207     assertContains(HierarchyFixtureInterface.class, hierarchy);
    208     assertContains(HierarchyFixtureSubinterface.class, hierarchy);
    209     assertContains(HierarchyFixtureParent.class, hierarchy);
    210     assertContains(HierarchyFixture.class, hierarchy);
    211   }
    212 
    213   public void testMissingSubscribe() {
    214     bus.register(new Object());
    215   }
    216 
    217   public void testUnregister() {
    218     StringCatcher catcher1 = new StringCatcher();
    219     StringCatcher catcher2 = new StringCatcher();
    220     try {
    221       bus.unregister(catcher1);
    222       fail("Attempting to unregister an unregistered object succeeded");
    223     } catch (IllegalArgumentException expected) {
    224       // OK.
    225     }
    226 
    227     bus.register(catcher1);
    228     bus.post(EVENT);
    229     bus.register(catcher2);
    230     bus.post(EVENT);
    231 
    232     List<String> expectedEvents = Lists.newArrayList();
    233     expectedEvents.add(EVENT);
    234     expectedEvents.add(EVENT);
    235 
    236     assertEquals("Two correct events should be delivered.",
    237                  expectedEvents, catcher1.getEvents());
    238 
    239     assertEquals("One correct event should be delivered.",
    240                  Lists.newArrayList(EVENT), catcher2.getEvents());
    241 
    242     bus.unregister(catcher1);
    243     bus.post(EVENT);
    244 
    245     assertEquals("Shouldn't catch any more events when unregistered.",
    246                  expectedEvents, catcher1.getEvents());
    247     assertEquals("Two correct events should be delivered.",
    248                  expectedEvents, catcher2.getEvents());
    249 
    250     try {
    251       bus.unregister(catcher1);
    252       fail("Attempting to unregister an unregistered object succeeded");
    253     } catch (IllegalArgumentException expected) {
    254       // OK.
    255     }
    256 
    257     bus.unregister(catcher2);
    258     bus.post(EVENT);
    259     assertEquals("Shouldn't catch any more events when unregistered.",
    260                  expectedEvents, catcher1.getEvents());
    261     assertEquals("Shouldn't catch any more events when unregistered.",
    262                  expectedEvents, catcher2.getEvents());
    263   }
    264 
    265   // NOTE: This test will always pass if register() is thread-safe but may also
    266   // pass if it isn't, though this is unlikely.
    267 
    268   public void testRegisterThreadSafety() throws Exception {
    269     List<StringCatcher> catchers = Lists.newCopyOnWriteArrayList();
    270     List<Future<?>> futures = Lists.newArrayList();
    271     ExecutorService executor = Executors.newFixedThreadPool(10);
    272     int numberOfCatchers = 10000;
    273     for (int i = 0; i < numberOfCatchers; i++) {
    274       futures.add(executor.submit(new Registrator(bus, catchers)));
    275     }
    276     for (int i = 0; i < numberOfCatchers; i++) {
    277       futures.get(i).get();
    278     }
    279     assertEquals("Unexpected number of catchers in the list",
    280         numberOfCatchers, catchers.size());
    281     bus.post(EVENT);
    282     List<String> expectedEvents = ImmutableList.of(EVENT);
    283     for (StringCatcher catcher : catchers) {
    284       assertEquals("One of the registered catchers did not receive an event.",
    285           expectedEvents, catcher.getEvents());
    286     }
    287   }
    288 
    289   /**
    290    * Tests that bridge methods are not subscribed to events. In Java 8,
    291    * annotations are included on the bridge method in addition to the original
    292    * method, which causes both the original and bridge methods to be subscribed
    293    * (since both are annotated @Subscribe) without specifically checking for
    294    * bridge methods.
    295    */
    296   public void testRegistrationWithBridgeMethod() {
    297     final AtomicInteger calls = new AtomicInteger();
    298     bus.register(new Callback<String>() {
    299       @Subscribe
    300       @Override
    301       public void call(String s) {
    302         calls.incrementAndGet();
    303       }
    304     });
    305 
    306     bus.post("hello");
    307 
    308     assertEquals(1, calls.get());
    309   }
    310 
    311   private <T> void assertContains(T element, Collection<T> collection) {
    312     assertTrue("Collection must contain " + element,
    313         collection.contains(element));
    314   }
    315 
    316   /**
    317    * Records thrown exception information.
    318    */
    319   private static final class RecordingSubscriberExceptionHandler
    320       implements SubscriberExceptionHandler {
    321 
    322     public SubscriberExceptionContext context;
    323     public Throwable exception;
    324 
    325     @Override
    326     public void handleException(Throwable exception,
    327         SubscriberExceptionContext context) {
    328       this.exception = exception;
    329       this.context = context;
    330     }
    331   }
    332 
    333   /**
    334    * Runnable which registers a StringCatcher on an event bus and adds it to a
    335    * list.
    336    */
    337   private static class Registrator implements Runnable {
    338     private final EventBus bus;
    339     private final List<StringCatcher> catchers;
    340 
    341     Registrator(EventBus bus, List<StringCatcher> catchers) {
    342       this.bus = bus;
    343       this.catchers = catchers;
    344     }
    345 
    346     @Override
    347     public void run() {
    348       StringCatcher catcher = new StringCatcher();
    349       bus.register(catcher);
    350       catchers.add(catcher);
    351     }
    352   }
    353 
    354   /**
    355    * A collector for DeadEvents.
    356    *
    357    * @author cbiffle
    358    *
    359    */
    360   public static class GhostCatcher {
    361     private List<DeadEvent> events = Lists.newArrayList();
    362 
    363     @Subscribe
    364     public void ohNoesIHaveDied(DeadEvent event) {
    365       events.add(event);
    366     }
    367 
    368     public List<DeadEvent> getEvents() {
    369       return events;
    370     }
    371   }
    372 
    373   private interface HierarchyFixtureInterface {
    374     // Exists only for hierarchy mapping; no members.
    375   }
    376 
    377   private interface HierarchyFixtureSubinterface
    378       extends HierarchyFixtureInterface {
    379     // Exists only for hierarchy mapping; no members.
    380   }
    381 
    382   private static class HierarchyFixtureParent
    383       implements HierarchyFixtureSubinterface {
    384     // Exists only for hierarchy mapping; no members.
    385   }
    386 
    387   private static class HierarchyFixture extends HierarchyFixtureParent {
    388     // Exists only for hierarchy mapping; no members.
    389   }
    390 
    391   private interface Callback<T> {
    392     void call(T t);
    393   }
    394 }
    395