package com.maxifier.guice.events; import org.testng.annotations.Test; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicInteger; import static org.mockito.Mockito.*; import static org.testng.Assert.*; public class EventDispatcherUTest { interface Listener { @Handler @HandleClass(Object.class) void test(String s); } @Test public void testMethodParam() { EventDispatcher dispatcher = new EventDispatcherImpl(mock(ListenerRegistrationQueue.class)); Listener listener = mock(Listener.class); dispatcher.register(listener); dispatcher.fireEvent("123"); verify(listener).test("123"); verifyNoMoreInteractions(listener); } @Test public void testClassFilters() { AnimalListener listener = mock(AnimalListener.class); EventDispatcher dispatcher = new EventDispatcherImpl(mock(ListenerRegistrationQueue.class)); dispatcher.register(listener); for (Animal animal : Animal.values()) { dispatcher.fireEvent(animal); verify(listener).animal(animal); } verify(listener).dangerousAnimal(Animal.CROCODILE); verify(listener).dangerousAnimal(Animal.TIGER); verify(listener).eatableAnimal(Animal.RABBIT); verifyNoMoreInteractions(listener); } @Test(expectedExceptions = CyclicFilterAnnotationException.class) public void testCyclicAnnotations1() { class Test1 { @Handler @CyclicAnnotation1 void test() { } } EventDispatcher d = new EventDispatcherImpl(mock(ListenerRegistrationQueue.class)); d.register(new Test1()); } @Test(expectedExceptions = CyclicFilterAnnotationException.class) public void testCyclicAnnotations2() { class Test2 { @Handler @CyclicAnnotation2 void test() { } } EventDispatcher d = new EventDispatcherImpl(mock(ListenerRegistrationQueue.class)); d.register(new Test2()); } @Test(expectedExceptions = CyclicFilterAnnotationException.class) public void testAutoAnnotated() { class TestAuto { @Handler @InvalidAutoAnnotation void test() { } } EventDispatcher d = new EventDispatcherImpl(mock(ListenerRegistrationQueue.class)); d.register(new TestAuto()); } @Test(expectedExceptions = RuntimeException.class) public void testEmptyHandler() { class Test1 { @Handler void test() { } } EventDispatcher d = new EventDispatcherImpl(mock(ListenerRegistrationQueue.class)); d.register(new Test1()); } @Test public void testGroupEvents() { } @Test public void testUnhandledEvents() { class TestEventDispatcher extends EventDispatcherImpl { boolean b; TestEventDispatcher() { super(mock(ListenerRegistrationQueue.class)); } @Override protected synchronized void unhandledEvent(Object event) { assertFalse(b, "unhandled event passed twice"); assertEquals(event, "event"); b = true; } } TestEventDispatcher d = new TestEventDispatcher(); d.fireEvent("event"); assertTrue(d.b, "unhandled event wasn't processed"); } @Test public void testWeakReference() throws Exception { class TestListener { boolean x = false; @Handler synchronized void test(Object o) { assertFalse(x); assertEquals(o, "123"); x = true; } } ReferenceQueue<? super TestListener> q = new ReferenceQueue<TestListener>(); EventDispatcher d = new EventDispatcherImpl(mock(ListenerRegistrationQueue.class)); TestListener tl = new TestListener(); WeakReference<TestListener> wr = new WeakReference<TestListener>(tl, q); d.register(tl); d.fireEvent("123"); assertTrue(tl.x); //noinspection UnusedAssignment tl = null; System.gc(); Thread.sleep(100); assertEquals(q.poll(), wr); } @Test public void testRegisterInHandler() { final EventDispatcher d = new EventDispatcherImpl(mock(ListenerRegistrationQueue.class)); final Listener l = mock(Listener.class); class TestListener { int x = 0; @Handler synchronized void test(String s) { if (s.equals("123")) { assertEquals(x, 0); x++; d.register(l); d.fireEvent("321"); } else if (s.equals("321")) { assertEquals(x, 1); x++; } else if (s.equals("222")) { assertEquals(x, 2); x++; } else { fail("Strange event: " + s); } } } final TestListener tl = new TestListener(); d.register(tl); d.fireEvent("123"); d.fireEvent("222"); assertEquals(tl.x, 3); verify(l).test("222"); verifyNoMoreInteractions(l); } // https://jira.maxifier.com/browse/XGUICE-30 // // Example of inverted stack trace: // Thread 1: // -fireEvent - holds readLock // -fireEvent0 // -someHandler - tries to get lock X, but it has to wait for Thread 2. // // Thread 2: // -someMethod - holds lock X // -fireEvent - holds readLock // -fireEvent0 - releases readLock // if event class never met before, it will waits for Thread 1 to acquire writeLock. // I set so huge timeout because in case of deadlock it's hard it will hang forever, but at our build agents // the time may vary greatly, even in simplest tests. // // This test is not really deterministic, it is stochastic, but in case of real deadlock it fails from time to time., @Test(timeOut = 60000) public void testDeadlockRegression() { final EventDispatcherImpl d = new EventDispatcherImpl(new ListenerRegistrationQueue()); final Object lock = new Object(); final AtomicInteger v = new AtomicInteger(); d.register(new Object() { @Handler public void handle(Integer i) { v.set(i); } @Handler public void handle(String s) { Thread t = new Thread() { @Override public void run() { synchronized (lock) { d.fireEvent(3); } } }; t.start(); // we sleep a bit sleepABit(); synchronized (lock) { sleepABit(); } try { t.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); d.fireEvent("test"); assertEquals(v.get(), 3); } private void sleepABit() { try { Thread.sleep(100 + (int) (Math.random() * 10)); } catch (InterruptedException e) { throw new RuntimeException(e); } } }