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 
setUp()43   @Override protected void setUp() throws Exception {
44     super.setUp();
45     bus = new EventBus(BUS_IDENTIFIER);
46   }
47 
testBasicCatcherDistribution()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    */
testPolymorphicDistribution()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 
testSubscriberThrowsException()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 
testSubscriberThrowsExceptionHandlerThrowsException()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 
testDeadEventForwarding()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 
testDeadEventPosting()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 
testFlattenHierarchy()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 
testMissingSubscribe()213   public void testMissingSubscribe() {
214     bus.register(new Object());
215   }
216 
testUnregister()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 
testRegisterThreadSafety()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    */
testRegistrationWithBridgeMethod()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 
assertContains(T element, Collection<T> collection)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
handleException(Throwable exception, SubscriberExceptionContext context)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 
Registrator(EventBus bus, List<StringCatcher> catchers)341     Registrator(EventBus bus, List<StringCatcher> catchers) {
342       this.bus = bus;
343       this.catchers = catchers;
344     }
345 
346     @Override
run()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
ohNoesIHaveDied(DeadEvent event)364     public void ohNoesIHaveDied(DeadEvent event) {
365       events.add(event);
366     }
367 
getEvents()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> {
call(T t)392     void call(T t);
393   }
394 }
395