/* * Copyright (C) 2012 - present by Yann Le Tallec. * Please see distribution for license. */ package com.assylias.jbloomberg; import com.assylias.bigblue.utils.TypedObject; import com.bloomberglp.blpapi.CorrelationID; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class EventsManagerTest { private EventsManager em; private CountDownLatch latch; private final AtomicInteger countEvent = new AtomicInteger(); private volatile DataChangeEvent evt; private CorrelationID id; private RealtimeField field; private String ticker; @BeforeMethod(groups = "unit") public void beforeMethod() { em = new ConcurrentConflatedEventsManager(); id = new CorrelationID(0); field = RealtimeField.ASK; ticker = "TICKER"; } @Test(groups = "unit") public void testFire_FieldNotRegistered() throws Exception { DataChangeListener lst = getDataChangeListener(1); em.addEventListener(ticker, id, field, lst); em.fireEvent(id, RealtimeField.ASK_ALL_SESSION, 1234); assertFalse(latch.await(10, TimeUnit.MILLISECONDS)); } @Test(groups = "unit") public void testFire_Ok() throws Exception { DataChangeListener lst = getDataChangeListener(1); em.addEventListener(ticker, id, field, lst); em.fireEvent(id, field, 1234); assertTrue(latch.await(10, TimeUnit.MILLISECONDS)); assertEquals(evt.getDataName(), "ASK"); assertNull(evt.getOldValue()); assertEquals(evt.getNewValue().asInt(), 1234); } @Test(groups = "unit") public void testFire_SameValueTwiceSentOnce() throws Exception { DataChangeListener lst = getDataChangeListener(2); em.addEventListener(ticker, id, field, lst); em.fireEvent(id, field, 1234); em.fireEvent(id, field, 1234); assertFalse(latch.await(10, TimeUnit.MILLISECONDS)); //second event not sent to listener assertEquals(evt.getDataName(), "ASK"); assertNull(evt.getOldValue()); assertEquals(evt.getNewValue().asInt(), 1234); } //TODO: it seems that the order of events is not preseved which could be an issue in case of two successive //data points on the same security //The problem is that the current setup does not allow to strongly guarantee the order and the solution is probably //to have a single queue but that may prove to be an issue performance wise if some listeners do a lot of work with new data... @Test(groups = "unit", enabled = false, invocationCount = 20, threadPoolSize = 2) public void testFire_2Listeners() throws Exception { CountDownLatch latch = new CountDownLatch(4); String ticker = "" + new Random().nextDouble(); List<DataChangeEvent> events = new CopyOnWriteArrayList<>(); DataChangeListener lst1 = getDataChangeListener(latch, events); DataChangeListener lst2 = getDataChangeListener(latch, events); em.addEventListener(ticker, id, field, lst1); em.addEventListener(ticker, id, field, lst2); em.fireEvent(id, field, 1); em.fireEvent(id, field, 2); assertTrue(latch.await(500, TimeUnit.MILLISECONDS), msg(latch, events)); assertEquals(events.size(), 4); assertEquals(events.get(0).getDataName(), "ASK"); assertNull(events.get(0).getOldValue(), msg(latch, events)); assertNotNull(events.get(3).getOldValue(), msg(latch, events)); assertEquals(events.get(3).getOldValue().asInt(), 1, msg(latch, events)); assertEquals(events.get(3).getNewValue().asInt(), 2, msg(latch, events)); } private String msg(CountDownLatch latch, List<DataChangeEvent> e) { return "latch.count = " + latch.getCount() + ", evt = " + String.valueOf(e); } @Test(groups = "unit") public void test2Listeners2Securities() throws Exception { latch = new CountDownLatch(2); CorrelationID id1 = new CorrelationID(0); CorrelationID id2 = new CorrelationID(1); em.addEventListener("SEC 1", id1, field, new DataChangeListener() { @Override public void dataChanged(DataChangeEvent e) { assertEquals(e.getSource(), "SEC 1"); assertEquals(e.getDataName(), field.toString()); assertEquals(e.getOldValue(), null); assertEquals(e.getNewValue().asInt(), 123); latch.countDown(); } }); em.addEventListener("SEC 2", id2, field, new DataChangeListener() { @Override public void dataChanged(DataChangeEvent e) { assertEquals(e.getSource(), "SEC 2"); assertEquals(e.getDataName(), field.toString()); assertEquals(e.getOldValue(), null); assertEquals(e.getNewValue().asInt(), 456); latch.countDown(); } }); em.fireEvent(id1, field, 123); em.fireEvent(id2, field, 456); assertTrue(latch.await(100, TimeUnit.MILLISECONDS)); } @Test(groups = "unit") public void test1Listener2Securities() throws Exception { final CountDownLatch latch1 = new CountDownLatch(1); final CountDownLatch latch2 = new CountDownLatch(1); CorrelationID id1 = new CorrelationID(0); CorrelationID id2 = new CorrelationID(1); DataChangeListener lst = new DataChangeListener() { @Override public void dataChanged(DataChangeEvent e) { if (e.getSource().equals("SEC 1") && e.getDataName().equals(field.toString()) && e.getOldValue() == null && e.getNewValue().asInt() == 123) { latch1.countDown(); } else if (e.getSource().equals("SEC 2") && e.getDataName().equals(field.toString()) && e.getOldValue() == null && e.getNewValue().asInt() == 456) { latch2.countDown(); } else { fail("Unexpected event received: " + e); } } }; em.addEventListener("SEC 1", id1, field, lst); em.addEventListener("SEC 2", id2, field, lst); em.fireEvent(id1, field, 123); em.fireEvent(id2, field, 456); assertTrue(latch1.await(100, TimeUnit.MILLISECONDS)); assertTrue(latch2.await(100, TimeUnit.MILLISECONDS)); } @Test(groups = "unit") public void testFire_Concurrent() throws Exception { final int NUM_EVENTS = 10_000; final int NUM_THREADS = 100; final int NUM_PER_THREAD = NUM_EVENTS / NUM_THREADS; final DataChangeEvent[] events = new DataChangeEvent[NUM_EVENTS]; latch = new CountDownLatch(NUM_EVENTS); countEvent.set(0); DataChangeListener lst = new DataChangeListener() { @Override public void dataChanged(DataChangeEvent e) { events[countEvent.getAndIncrement()] = e; latch.countDown(); } }; em.addEventListener(ticker, id, field, lst); ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); for (int i = 0; i < NUM_THREADS; i++) { final int start = i * NUM_PER_THREAD; Runnable r = new Runnable() { @Override public void run() { for (int i = start; i < start + NUM_PER_THREAD; i++) { em.fireEvent(id, field, i); } } }; executor.submit(r); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.SECONDS); assertTrue(latch.await(1, TimeUnit.SECONDS), "latch at " + latch.getCount()); Set<TypedObject> newValues = new HashSet<>(); Set<TypedObject> oldValues = new HashSet<>(); for (DataChangeEvent e : events) { assertEquals(e.getSource(), ticker); assertEquals(e.getDataName(), "ASK"); newValues.add(e.getNewValue()); oldValues.add(e.getOldValue()); } assertEquals(newValues.size(), NUM_EVENTS); assertEquals(oldValues.size(), NUM_EVENTS); //including null } @Test(groups = "unit") public void testFire_Performance() throws Exception { final int NUM_EVENTS = 1_000_000; final int NUM_THREADS = 1; final int NUM_PER_THREAD = NUM_EVENTS / NUM_THREADS; countEvent.set(0); DataChangeListener lst = new DataChangeListener() { @Override public void dataChanged(DataChangeEvent e) { countEvent.getAndIncrement(); } }; em.addEventListener(ticker, id, field, lst); ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); for (int i = 0; i < NUM_THREADS; i++) { final int start = i * NUM_PER_THREAD; Runnable r = new Runnable() { @Override public void run() { for (int i = start; i < start + NUM_PER_THREAD; i++) { em.fireEvent(id, field, i); } } }; executor.submit(r); } long start = System.nanoTime(); executor.shutdown(); executor.awaitTermination(60, TimeUnit.SECONDS); long time = (System.nanoTime() - start) / 1_000_000; } private DataChangeListener getDataChangeListener(final int i) { latch = new CountDownLatch(i); countEvent.set(i); return new DataChangeListener() { @Override public void dataChanged(DataChangeEvent e) { if (countEvent.decrementAndGet() < 0) fail("latch already at 0"); evt = e; latch.countDown(); } }; } private DataChangeListener getDataChangeListener(CountDownLatch latch, List<DataChangeEvent> evt) { return e -> { if (latch.getCount() == 0) fail("latch already at 0"); evt.add(e); latch.countDown(); }; } }