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