@Test
  public void testJoinMessageReceivedForDisconnectedHost() throws Exception {
    final AtomicInteger counter1 = new AtomicInteger(0);
    final AtomicInteger counter2 = new AtomicInteger(0);

    connector1.subscribe(String.class.getName(), new CountingCommandHandler(counter1));
    connector1.connect(20);
    assertTrue(
        "Expected connector 1 to connect within 10 seconds",
        connector1.awaitJoined(10, TimeUnit.SECONDS));

    connector2.subscribe(String.class.getName(), new CountingCommandHandler(counter2));
    connector2.connect(80);
    assertTrue("Connector 2 failed to connect", connector2.awaitJoined());

    // wait for both connectors to have the same view
    waitForConnectorSync();

    ConsistentHash hashBefore = connector1.getConsistentHash();
    // secretly insert an illegal message
    channel1
        .getReceiver()
        .receive(
            new Message(
                channel1.getAddress(),
                new IpAddress(12345),
                new JoinMessage(10, Collections.<String>emptySet())));
    ConsistentHash hash2After = connector1.getConsistentHash();
    assertEquals("That message should not have changed the ring", hashBefore, hash2After);
  }
  @SuppressWarnings("unchecked")
  @Test(timeout = 30000)
  public void testConnectAndDispatchMessages_Balanced() throws Exception {
    assertNull(connector1.getNodeName());
    assertNull(connector2.getNodeName());

    final AtomicInteger counter1 = new AtomicInteger(0);
    final AtomicInteger counter2 = new AtomicInteger(0);

    connector1.subscribe(String.class.getName(), new CountingCommandHandler(counter1));
    connector1.connect(20);
    assertTrue(
        "Expected connector 1 to connect within 10 seconds",
        connector1.awaitJoined(10, TimeUnit.SECONDS));

    connector2.subscribe(String.class.getName(), new CountingCommandHandler(counter2));
    connector2.connect(80);
    assertTrue("Connector 2 failed to connect", connector2.awaitJoined());

    // wait for both connectors to have the same view
    waitForConnectorSync();

    List<FutureCallback> callbacks = new ArrayList<>();

    for (int t = 0; t < 100; t++) {
      FutureCallback<Object, Object> callback = new FutureCallback<>();
      String message = "message" + t;
      if ((t & 1) == 0) {
        connector1.send(message, new GenericCommandMessage<>(message), callback);
      } else {
        connector2.send(message, new GenericCommandMessage<>(message), callback);
      }
      callbacks.add(callback);
    }
    for (FutureCallback callback : callbacks) {
      assertEquals("The Reply!", callback.get());
    }
    assertEquals(100, counter1.get() + counter2.get());
    System.out.println("Node 1 got " + counter1.get());
    System.out.println("Node 2 got " + counter2.get());
    verify(mockCommandBus1, atMost(40))
        .dispatch(any(CommandMessage.class), isA(CommandCallback.class));
    verify(mockCommandBus2, atLeast(60))
        .dispatch(any(CommandMessage.class), isA(CommandCallback.class));
    assertEquals(connector1.getMembers(), connector2.getMembers());
    assertNotNull(connector1.getNodeName());
    assertNotNull(connector2.getNodeName());
    assertNotEquals(connector1.getNodeName(), connector2.getNodeName());
  }
  @Test
  public void testConnectorRecoversWhenGossipRouterReconnects() throws Exception {
    final AtomicInteger counter1 = new AtomicInteger(0);
    final AtomicInteger counter2 = new AtomicInteger(0);

    connector1.subscribe(String.class.getName(), new CountingCommandHandler<String>(counter1));
    connector1.connect(20);
    assertTrue(
        "Expected connector 1 to connect within 10 seconds",
        connector1.awaitJoined(10, TimeUnit.SECONDS));

    connector2.subscribe(Long.class.getName(), new CountingCommandHandler<Long>(counter2));
    connector2.connect(80);

    assertTrue("Connector 2 failed to connect", connector2.awaitJoined());

    // the nodes joined, but didn't detect eachother
    gossipRouter.start();

    // now, they should detect eachother and start syncing their state
    waitForConnectorSync(60);
  }
  @Test(timeout = 300000)
  public void testHashChangeNotification() throws Exception {
    connector1.connect(10);
    connector2.connect(10);

    // wait for both connectors to have the same view
    waitForConnectorSync();

    // connector 1 joined
    ConsistentHash notify1 = hashChangeListener.notifications.poll(5, TimeUnit.SECONDS);

    // connector 2 joined
    ConsistentHash notify2 = hashChangeListener.notifications.poll(5, TimeUnit.SECONDS);
    // Self and other node have joined
    assertEquals(connector1.getConsistentHash(), notify2);

    channel2.close();

    // Other node has left
    ConsistentHash notify3 = hashChangeListener.notifications.poll(5, TimeUnit.SECONDS);
    assertEquals(connector1.getConsistentHash(), notify3);
  }
  @Test(timeout = 30000)
  public void testRingsProperlySynchronized_ChannelAlreadyConnected() throws Exception {
    final AtomicInteger counter1 = new AtomicInteger(0);
    final AtomicInteger counter2 = new AtomicInteger(0);

    connector1.subscribe(String.class.getName(), new CountingCommandHandler(counter1));
    channel1.connect(clusterName);
    connector1.connect(20);
    assertTrue(
        "Expected connector 1 to connect within 10 seconds",
        connector1.awaitJoined(10, TimeUnit.SECONDS));

    connector2.subscribe(Long.class.getName(), new CountingCommandHandler(counter2));
    channel2.connect(clusterName);
    connector2.connect(80);

    assertTrue("Connector 2 failed to connect", connector2.awaitJoined());

    waitForConnectorSync();

    FutureCallback<Object, Object> callback1 = new FutureCallback<>();
    connector1.send("1", new GenericCommandMessage<>("Hello"), callback1);
    FutureCallback<Object, Object> callback2 = new FutureCallback<>();
    connector1.send("1", new GenericCommandMessage<>(1L), callback2);

    FutureCallback<Object, Object> callback3 = new FutureCallback<>();
    connector2.send("1", new GenericCommandMessage<>("Hello"), callback3);
    FutureCallback<Object, Object> callback4 = new FutureCallback<>();
    connector2.send("1", new GenericCommandMessage<>(1L), callback4);

    assertEquals("The Reply!", callback1.get());
    assertEquals("The Reply!", callback2.get());
    assertEquals("The Reply!", callback3.get());
    assertEquals("The Reply!", callback4.get());

    assertTrue(connector1.getConsistentHash().equals(connector2.getConsistentHash()));
  }
  @Test
  public void testSetupOfReplyingCallback() throws InterruptedException {
    final String mockPayload = "DummyString";
    final CommandMessage<String> commandMessage = new GenericCommandMessage<>(mockPayload);

    final DispatchMessage dispatchMessage = new DispatchMessage(commandMessage, serializer, true);
    final Message message = new Message(channel1.getAddress(), dispatchMessage);

    connector1.connect(20);
    assertTrue(
        "Expected connector 1 to connect within 10 seconds",
        connector1.awaitJoined(10, TimeUnit.SECONDS));

    channel1.getReceiver().receive(message);

    // Verify that the newly introduced ReplyingCallBack class is being wired in. Actual behaviour
    // of ReplyingCallback is tested in its unit tests
    verify(mockCommandBus1).dispatch(any(), any(ReplyingCallback.class));
  }
 @Test(expected = ConnectionFailedException.class, timeout = 30000)
 public void testRingsProperlySynchronized_ChannelAlreadyConnectedToOtherCluster()
     throws Exception {
   channel1.connect("other");
   connector1.connect(20);
 }