package io.nextop.client.node.nextop;
import com.google.common.base.Objects;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import io.nextop.Id;
import io.nextop.Message;
import io.nextop.client.MessageContext;
import io.nextop.client.MessageContexts;
import io.nextop.client.MessageControlState;
import io.nextop.client.test.WorkloadRunner;
import io.nextop.wire.Pipe;
import io.nextop.client.node.Head;
import io.nextop.rx.MoreSchedulers;
import junit.framework.TestCase;
import rx.Notification;
import rx.Observer;
import rx.Scheduler;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
import javax.annotation.Nullable;
import javax.tools.Diagnostic;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
// connects two nextop nodes in memory
public class NextopNodeTest extends TestCase {
public void testRandomStreaming() throws Throwable {
Scheduler testScheduler = MoreSchedulers.serial();
// run the test on the correct scheduler
RandomStreamingTest test = new RandomStreamingTest(testScheduler);
test.start();
test.join();
}
static final class RandomStreamingTest extends WorkloadRunner {
Random r = new Random();
// FIXME more thorough stream
// FIXME list of send messages for each. then just verify send==received
// FIXME random messages: from a fixed number of groups, set different priorities
int n = 1000;
RandomMessageGenerator rmg = new RandomMessageGenerator(r,
new RandomMessageGenerator.GroupDist(Id.create(), 0, 4),
new RandomMessageGenerator.GroupDist(Id.create(), 0, 10),
new RandomMessageGenerator.GroupDist(Id.create(), 10, 20),
new RandomMessageGenerator.GroupDist(Id.create(), 4, 10));
final List<Message> aSend = new LinkedList<Message>();
final List<Message> bSend = new LinkedList<Message>();
final List<Message> aReceive = new LinkedList<Message>();
final List<Message> bReceive = new LinkedList<Message>();
RandomStreamingTest(Scheduler scheduler) {
super(scheduler);
}
@Override
protected void run() throws Exception {
NextopNode a = new NextopNode();
NextopNode b = new NextopNode();
Pipe wfp = new Pipe();
a.setWireFactory(wfp.getA());
b.setWireFactory(wfp.getB());
MessageContext aContext = MessageContexts.create(MoreSchedulers.serial());
MessageControlState aMcs = new MessageControlState(aContext);
MessageContext bContext = MessageContexts.create(MoreSchedulers.serial());
MessageControlState bMcs = new MessageControlState(bContext);
Head aHead = Head.create(aContext, aMcs, a, scheduler);
Head bHead = Head.create(bContext, bMcs, b, scheduler);
aHead.init(null);
bHead.init(null);
aHead.start();
bHead.start();
Subscription da = aHead.defaultReceive().subscribe(new Action1<Message>() {
@Override
public void call(Message message) {
aReceive.add(message);
}
});
Subscription db = bHead.defaultReceive().subscribe(new Action1<Message>() {
@Override
public void call(Message message) {
bReceive.add(message);
}
});
for (int i = 0; i < n; ++i) {
Message message = rmg.next();
aSend.add(message);
aHead.send(message);
}
for (int i = 0; i < n; ++i) {
Message message = rmg.next();
bSend.add(message);
bHead.send(message);
}
}
@Override
protected void check() throws Exception {
assertEquals(aSend.size(), bReceive.size());
assertEquals(bSend.size(), aReceive.size());
assertEquals(new HashSet<Message>(aSend), new HashSet<Message>(bReceive));
assertEquals(new HashSet<Message>(bSend), new HashSet<Message>(aReceive));
}
}
// test that the full set of message control gets transferred
public void testRandomStreamingMessageControl() throws Throwable {
Scheduler testScheduler = MoreSchedulers.serial();
// run the test on the correct scheduler
RandomStreamingMessageControlTest test = new RandomStreamingMessageControlTest(testScheduler);
test.start();
test.join();
}
static final class RandomStreamingMessageControlTest extends WorkloadRunner {
Random r = new Random();
int rm = 10;
// ep% chance of an error on each send
int ep = 10;
// ep0% change of an error on the first send
int ep0 = 30;
int n = 1000;
RandomMessageGenerator rmg = new RandomMessageGenerator(r);
List<Message> aSend = new LinkedList<Message>();
List<Message> bSend = new LinkedList<Message>();
Multimap<Message, Response> aResponses = ArrayListMultimap.create();
Multimap<Message, Response> aExpectedResponses = ArrayListMultimap.create();
Multimap<Message, Response> bResponses = ArrayListMultimap.create();
Multimap<Message, Response> bExpectedResponses = ArrayListMultimap.create();
RandomStreamingMessageControlTest(Scheduler scheduler) {
super(scheduler);
}
@Override
protected void run() throws Exception {
NextopNode a = new NextopNode();
NextopNode b = new NextopNode();
Pipe wfp = new Pipe();
a.setWireFactory(wfp.getA());
b.setWireFactory(wfp.getB());
MessageContext aContext = MessageContexts.create(MoreSchedulers.serial());
MessageControlState aMcs = new MessageControlState(aContext);
MessageContext bContext = MessageContexts.create(MoreSchedulers.serial());
MessageControlState bMcs = new MessageControlState(bContext);
final Head aHead = Head.create(aContext, aMcs, a, scheduler);
final Head bHead = Head.create(bContext, bMcs, b, scheduler);
aHead.init(null);
bHead.init(null);
aHead.start();
bHead.start();
aHead.defaultReceive().subscribe(new DefaultReceiver(aHead, bExpectedResponses));
bHead.defaultReceive().subscribe(new DefaultReceiver(bHead, aExpectedResponses));
for (int i = 0; i < n; ++i) {
Message message = rmg.next();
aSend.add(message);
aHead.receive(message.inboxRoute()).subscribe(new ResponseObserver(message, aResponses));
aHead.send(message);
}
for (int i = 0; i < n; ++i) {
Message message = rmg.next();
bSend.add(message);
bHead.receive(message.inboxRoute()).subscribe(new ResponseObserver(message, bResponses));
bHead.send(message);
}
}
@Override
protected void check() throws Exception {
assertEquals(n, aSend.size());
assertEquals(n, bSend.size());
for (Message request : aSend) {
assertTrue(aResponses.containsKey(request));
}
for (Message request : bSend) {
assertTrue(bResponses.containsKey(request));
}
// DEBUGGING (pinpoint the mismatches)
// for (Message request : aSend) {
// List<Response> a = (List<Response>) aExpectedResponses.get(request);
// List<Response> b = (List<Response>) aResponses.get(request);
// int n = a.size();
// assertEquals(String.format("%s <> %s", a, b), n, b.size());
// for (int i = 0; i < n; ++i) {
// Response ra = a.get(i);
// Response rb = b.get(i);
// if (!ra.equals(rb)) {
// fail(String.format("[%d] %s <> %s", i, ra, rb));
// }
// }
// }
// for (Message request : aSend) {
// List<Response> a = (List<Response>) aExpectedResponses.get(request);
// List<Response> b = (List<Response>) aResponses.get(request);
// int n = a.size();
// assertEquals(String.format("%s <> %s", a, b), n, b.size());
// for (int i = 0; i < n; ++i) {
// Response ra = a.get(i);
// Response rb = b.get(i);
// if (!ra.equals(rb)) {
// fail(String.format("[%d] %s <> %s", i, ra, rb));
// }
// }
// }
for (Message request : aSend) {
assertEquals(aExpectedResponses.get(request), aResponses.get(request));
}
for (Message request : bSend) {
assertEquals(bExpectedResponses.get(request), bResponses.get(request));
}
assertEquals(aExpectedResponses, aResponses);
assertEquals(bExpectedResponses, bResponses);
}
class DefaultReceiver implements Action1<Message> {
final Head head;
final Multimap<Message, Response> expectedResponses;
DefaultReceiver(Head head, Multimap<Message, Response> expectedResponses) {
this.head = head;
this.expectedResponses = expectedResponses;
}
@Override
public void call(Message message) {
Message complete = Message.newBuilder().setRoute(message.inboxRoute()).build();
if (r.nextInt(100) < ep0) {
expectedResponses.put(message, new Response(Notification.Kind.OnError, null));
head.error(complete);
} else {
boolean error = false;
for (int i = 0, m = r.nextInt(2 * rm); i < m; ++i) {
if (r.nextInt(100) < ep) {
expectedResponses.put(message, new Response(Notification.Kind.OnError, null));
head.error(complete);
error = true;
break;
} else {
Message response = rmg.next().toBuilder().setRoute(message.inboxRoute()).build();
expectedResponses.put(message, new Response(Notification.Kind.OnNext, response));
head.send(response);
}
}
if (!error) {
expectedResponses.put(message, new Response(Notification.Kind.OnCompleted, null));
head.complete(complete);
}
}
}
}
class ResponseObserver implements Observer<Message> {
final Message request;
final Multimap<Message, Response> responses;
ResponseObserver(Message request, Multimap<Message, Response> responses) {
this.request = request;
this.responses = responses;
}
@Override
public void onNext(Message message) {
responses.put(request, new Response(Notification.Kind.OnNext, message));
}
@Override
public void onCompleted() {
responses.put(request, new Response(Notification.Kind.OnCompleted, null));
}
@Override
public void onError(Throwable e) {
responses.put(request, new Response(Notification.Kind.OnError, null));
}
}
final class Response {
final Notification.Kind kind;
final @Nullable Message message;
Response(Notification.Kind kind, @Nullable Message message) {
this.kind = kind;
this.message = message;
switch (kind) {
case OnNext:
if (null == message) {
throw new IllegalArgumentException();
}
break;
case OnCompleted:
case OnError:
if (null != message) {
throw new IllegalArgumentException();
}
break;
default:
throw new IllegalArgumentException();
}
}
@Override
public String toString() {
switch (kind) {
case OnNext:
return String.format("N %s", message);
case OnCompleted:
return "C";
case OnError:
return "E";
default:
throw new IllegalStateException();
}
}
@Override
public int hashCode() {
int c = kind.hashCode();
if (null != message) {
c = 31 * c + message.hashCode();
}
return c;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Response)) {
return false;
}
Response b = (Response) o;
return kind.equals(b.kind) && Objects.equal(message, b.message);
}
}
}
static final class RandomMessageGenerator {
final Random r;
final GroupDist[] groupDists;
RandomMessageGenerator(Random r, GroupDist ... groupDists) {
this.r = r;
this.groupDists = groupDists;
}
Message next() {
Message.Builder builder = Message.newBuilder();
if (0 < groupDists.length) {
GroupDist groupDist = groupDists[r.nextInt(groupDists.length)];
int groupPriority = groupDist.minPriority + r.nextInt(groupDist.maxPriority - groupDist.minPriority);
builder
.setGroupId(groupDist.groupId)
.setGroupPriority(groupPriority);
}
// FIXME set more properties here
builder.setRoute("GET http://nextop.io");
return builder.build();
}
static final class GroupDist {
final Id groupId;
final int minPriority;
final int maxPriority;
GroupDist(Id groupId, int minPriority, int maxPriority) {
this.groupId = groupId;
this.minPriority = minPriority;
this.maxPriority = maxPriority;
}
}
}
// FIXME tests around sync, reconnect, disconnect behavior
// FIXME test around never losing a message
}