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