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