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