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