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