/*
* Copyright (C) 2012 - present by Yann Le Tallec.
* Please see distribution for license.
*/
package com.assylias.jbloomberg;
import com.bloomberglp.blpapi.CorrelationID;
import com.bloomberglp.blpapi.Session;
import com.bloomberglp.blpapi.Subscription;
import com.bloomberglp.blpapi.SubscriptionList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import mockit.Mock;
import mockit.MockUp;
import static org.testng.Assert.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@Test(groups = "unit")
public class SubscriptionManagerTest {
private SubscriptionManager sm;
private Subscriptions subscriptions;
private BlockingQueue<Data> queue;
private CountDownLatch latch;
private AtomicInteger countEvent;
private EventsManager eventsManager;
private DefaultBloombergSession session;
@BeforeClass
public void beforeClass() {
}
@BeforeMethod
public void beforeMethod() {
queue = new LinkedBlockingQueue<>();
eventsManager = new ConcurrentConflatedEventsManager();
sm = new SubscriptionManager(queue, eventsManager);
countEvent = new AtomicInteger();
subscriptions = new Subscriptions();
Sessions.mockStartedSession();
Sessions.resetCounter();
new MockUp<Session>() {
@Mock
public void subscribe(SubscriptionList list) {
for (Subscription s : list) {
subscriptions.add(s);
}
}
@Mock
public void resubscribe(SubscriptionList list) {
for (Subscription s : list) {
subscriptions.replace(s);
}
}
};
session = new DefaultBloombergSession();
sm.start(session);
}
@AfterMethod
public void afterMethod() {
sm.stop(session);
}
@Test(expectedExceptions=NullPointerException.class)
public void startNull() {
SubscriptionManager sm = new SubscriptionManager(queue, eventsManager);
sm.start(null);
}
@Test(expectedExceptions=IllegalStateException.class)
public void stopNull() {
sm.stop(new DefaultBloombergSession());
}
@Test
public void testSubscribe_OneSecurityOneFieldThrottle() throws IOException {
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.BID).throttle(5));
assertTrue(subscriptions.getTickers().contains("ABC"));
assertTrue(subscriptions.getFields("ABC").contains(RealtimeField.BID));
assertEquals(subscriptions.getThrottle("ABC"), 5d);
assertEquals(subscriptions.getSubscriptionsReceived(), 1);
assertEquals(subscriptions.getReSubscriptionsReceived(), 0);
}
@Test
public void testSubscribe_TwoSecuritiesTwoFieldsNoThrottle() throws IOException {
List<String> tickers = Arrays.asList("ABC", "DEF");
List<RealtimeField> fields = Arrays.asList(RealtimeField.ASK, RealtimeField.BID);
sm.subscribe(new SubscriptionBuilder().addSecurities(tickers).addFields(fields));
assertSameContent(subscriptions.getTickers(), tickers);
assertSameContent(subscriptions.getFields("ABC"), fields);
assertSameContent(subscriptions.getFields("DEF"), fields);
assertEquals(subscriptions.getThrottle("ABC"), 0d);
assertEquals(subscriptions.getThrottle("DEF"), 0d);
assertEquals(subscriptions.getSubscriptionsReceived(), 2);
assertEquals(subscriptions.getReSubscriptionsReceived(), 0);
}
@Test
public void testSubscribe_DuplicateSecuritySameFields() throws IOException {
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.ASK));
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.ASK));
assertSameContent(subscriptions.getTickers(), Arrays.asList("ABC"));
assertSameContent(subscriptions.getFields("ABC"), Arrays.asList(RealtimeField.ASK));
assertEquals(subscriptions.getSubscriptionsReceived(), 1);
assertEquals(subscriptions.getReSubscriptionsReceived(), 1);
}
@Test
public void testReSubscribe_AddFields() throws IOException {
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.ASK));
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.BID));
assertSameContent(subscriptions.getTickers(), Arrays.asList("ABC"));
assertSameContent(subscriptions.getFields("ABC"), Arrays.asList(RealtimeField.ASK, RealtimeField.BID));
assertEquals(subscriptions.getSubscriptionsReceived(), 1);
assertEquals(subscriptions.getReSubscriptionsReceived(), 1);
}
@Test
public void testReSubscribe_ModifyThrottle() throws IOException {
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.ASK));
assertEquals(subscriptions.getThrottle("ABC"), 0d);
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").throttle(1));
assertEquals(subscriptions.getThrottle("ABC"), 1d);
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC"));
assertEquals(subscriptions.getThrottle("ABC"), 0d);
assertEquals(subscriptions.getSubscriptionsReceived(), 1);
assertEquals(subscriptions.getReSubscriptionsReceived(), 2);
}
@Test
public void testDispatch_1() throws Exception {
Sessions.mockStartedSession();
DataChangeListener lst = getListener(1);
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.ASK).addListener(lst));
queue.add(new Data(new CorrelationID(0), "ASK", 123));
assertTrue(latch.await(100, TimeUnit.MILLISECONDS));
assertEquals(countEvent.get(), 0);
}
@Test
public void testDispatch_2() throws Exception {
Sessions.mockStartedSession();
DataChangeListener lst = getListener(2);
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.ASK).addListener(lst));
queue.add(new Data(new CorrelationID(0), "ASK", 123));
queue.add(new Data(new CorrelationID(0), "ASK", 453));
assertTrue(latch.await(100, TimeUnit.MILLISECONDS));
assertEquals(countEvent.get(), 0);
}
@Test
public void testDispatch_FieldNotSubscribed() throws Exception {
Sessions.mockStartedSession();
DataChangeListener lst = getListener(1);
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.ASK).addListener(lst));
queue.add(new Data(new CorrelationID(0), "OTHER", 123));
assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
assertNotEquals(countEvent.get(), 0);
}
@Test
public void testDispatch_SecurityNotSubscribed() throws Exception {
Sessions.mockStartedSession();
DataChangeListener lst = getListener(1);
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.ASK).addListener(lst));
queue.add(new Data(new CorrelationID(1), "ASK", 123));
assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
assertNotEquals(countEvent.get(), 0);
}
@Test
public void testDispatch_EventNoChange() throws Exception {
Sessions.mockStartedSession();
DataChangeListener lst = getListener(1);
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addField(RealtimeField.ASK).addListener(lst));
queue.add(new Data(new CorrelationID(0), "ASK", 123));
queue.add(new Data(new CorrelationID(0), "ASK", 123));
assertTrue(latch.await(100, TimeUnit.MILLISECONDS));
assertEquals(countEvent.get(), 0);
}
@Test
public void test1Listener2Securities_AddListeners() throws Exception {
assertEquals(new CorrelationID(0), new CorrelationID(0)); //necessary for the test to pass
Sessions.mockStartedSession();
final AtomicInteger expectedInvocations = new AtomicInteger();
new MockUp<ConcurrentConflatedEventsManager>() {
@Mock(invocations=2)
public void addEventListener(String ticker, CorrelationID id, RealtimeField field, DataChangeListener lst) {
if ((ticker.equals("ABC") && id.value() == 0) || (ticker.equals("DEF") && id.value() == 1)) {
expectedInvocations.incrementAndGet();
} else {
fail("Unexpected invocation of addEventListener: " + ticker + ", " + id);
}
}
};
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addSecurity("DEF").addField(RealtimeField.ASK).addListener(getListener(1)));
assertEquals(expectedInvocations.get(), 2);
}
@Test
public void test1Listener2Securities_FireEvents() throws Exception {
Sessions.mockStartedSession();
final CountDownLatch latch1 = new CountDownLatch(1);
final CountDownLatch latch2 = new CountDownLatch(1);
DataChangeListener lst = new DataChangeListener() {
@Override
public void dataChanged(DataChangeEvent e) {
if (e.getSource().equals("ABC") && e.getNewValue().asInt() == 123) {
latch1.countDown();
} else if (e.getSource().equals("DEF") && e.getNewValue().asInt() == 456) {
latch2.countDown();
} else {
fail("Unexpected event: " + e);
}
}
};
sm.subscribe(new SubscriptionBuilder().addSecurity("ABC").addSecurity("DEF").addField(RealtimeField.ASK).addListener(lst));
queue.add(new Data(new CorrelationID(0), "ASK", 123));
queue.add(new Data(new CorrelationID(1), "ASK", 456));
assertTrue(latch1.await(10000, TimeUnit.MILLISECONDS));
assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS));
}
private static <T> void assertSameContent(Collection<T> expected, Collection<T> actual) {
if (!expected.containsAll(actual) || !actual.containsAll(expected)) {
assertEquals(actual, expected); //just for the error message
}
}
@Test
public void testListener() {
DataChangeListener lst = getListener(1);
assertFalse(latch.getCount() == 0);
lst.dataChanged(null);
assertTrue(latch.getCount() == 0);
lst = getListener(1);
assertFalse(latch.getCount() == 0);
lst.dataChanged(null);
assertTrue(latch.getCount() == 0);
try {
lst.dataChanged(null);
fail("Should have failed");
} catch (IllegalStateException e) {
}
}
private DataChangeListener getListener(final int i) {
latch = new CountDownLatch(i);
countEvent.set(i);
return new DataChangeListener() {
@Override
public void dataChanged(DataChangeEvent e) {
if (countEvent.decrementAndGet() < 0) {
throw new IllegalStateException("latch already at 0");
}
latch.countDown();
}
};
}
}
class Sessions {
static AtomicInteger counter = new AtomicInteger(0);
static void mockStartedSession() {
mockSession(true);
}
static void mockNotStartedSession() {
mockSession(false);
}
static private void mockSession(final boolean isStarted) {
new MockUp<DefaultBloombergSession>() {
@Mock
CorrelationID getNextCorrelationId() {
return new CorrelationID(counter.getAndIncrement());
}
};
}
static void resetCounter() {
counter.set(0);
}
}
/**
*
* a sort of stub for the SubscriptionList class to keep track of what has been sent.
*/
class Subscriptions {
Map<String, Subscription> subscriptions = new HashMap<>();
int subs = 0;
int resubs = 0;
int getSubscriptionsReceived() {
return subs;
}
int getReSubscriptionsReceived() {
return resubs;
}
void add(Subscription s) {
subscriptions.put(getTicker(s), s);
subs++;
}
void replace(Subscription s) {
subscriptions.remove(getTicker(s));
subscriptions.put(getTicker(s), s);
resubs++;
}
void remove(String ticker) {
subscriptions.remove(ticker);
}
List<String> getTickers() {
return new ArrayList<>(subscriptions.keySet());
}
List<RealtimeField> getFields(String ticker) {
List<RealtimeField> fields = new ArrayList<>();
if (subscriptions.containsKey(ticker)) {
Subscription s = subscriptions.get(ticker);
String str = s.subscriptionString();
String[] s0 = str.split("fields=");
String[] s1 = s0[1].split("&");
String[] s2 = s1[0].split(",");
for (String field : s2) {
fields.add(RealtimeField.valueOf(field));
}
}
return fields;
}
double getThrottle(String ticker) {
if (subscriptions.containsKey(ticker)) {
String[] s = subscriptions.get(ticker).subscriptionString().split("interval=");
return s.length >= 2 ? Double.parseDouble(s[1].split(" ")[0]) : 0;
} else {
return 0;
}
}
Object getCorrelationId(String ticker) {
CorrelationID id = subscriptions.get(ticker).correlationID();
return id.isObject() ? id.object() : id.value();
}
static String getTicker(Subscription s) {
return s.subscriptionString().split("\\?")[0];
}
}