package eu.hgross.blaubot.test; import net.jodah.concurrentunit.Waiter; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import eu.hgross.blaubot.core.BlaubotConstants; import eu.hgross.blaubot.core.IActionListener; import eu.hgross.blaubot.core.IBlaubotConnection; import eu.hgross.blaubot.core.IBlaubotDevice; import eu.hgross.blaubot.admin.AbstractAdminMessage; import eu.hgross.blaubot.admin.AdminMessageFactory; import eu.hgross.blaubot.admin.RelayAdminMessage; import eu.hgross.blaubot.messaging.BlaubotMessage; import eu.hgross.blaubot.messaging.BlaubotMessageReceiver; import eu.hgross.blaubot.messaging.BlaubotMessageSender; import eu.hgross.blaubot.messaging.IBlaubotMessageListener; import eu.hgross.blaubot.mock.BlaubotConnectionQueueMock; import eu.hgross.blaubot.mock.BlaubotDeviceMock; /** * Created by henna on 30.01.15. * * Tests the BlaubotMessageReceiver and Sender objects if they work together. */ public class MessageSenderAndReceiverTest { private Random random = new Random(); private IBlaubotConnection connection1; private IBlaubotConnection connection2; private BlaubotMessageSender conn1_sender; private BlaubotMessageReceiver conn1_receiver; private BlaubotMessageSender conn2_sender; private BlaubotMessageReceiver conn2_receiver; @Before public void setUp() { IBlaubotDevice device = new BlaubotDeviceMock("Device1"); IBlaubotDevice device2 = new BlaubotDeviceMock("Device2"); connection1 = new BlaubotConnectionQueueMock(device); connection2 = ((BlaubotConnectionQueueMock) connection1).getOtherEndpointConnection(device2); conn1_sender = new BlaubotMessageSender(connection1); conn1_receiver = new BlaubotMessageReceiver(connection1); conn2_sender = new BlaubotMessageSender(connection2); conn2_receiver = new BlaubotMessageReceiver(connection2); conn1_sender.activate(); conn2_sender.activate(); conn1_receiver.activate(); conn2_receiver.activate(); } @After public void cleanUp() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(4); IActionListener deactivationListener = new IActionListener() { @Override public void onFinished() { latch.countDown(); } }; conn1_sender.deactivate(deactivationListener); conn2_sender.deactivate(deactivationListener); conn1_receiver.deactivate(deactivationListener); conn2_receiver.deactivate(deactivationListener); boolean timedOut = !latch.await(5000, TimeUnit.MILLISECONDS); Assert.assertTrue("MessageSender or Receiver deactivation timed out", !timedOut); } private byte[] createRandomPayload() { return createRandomPayload(20); } private byte[] createRandomPayload(int numBytes) { byte[] b = new byte[numBytes]; random.nextBytes(b); return b; } /** * Tests deactivation of each receiver/listeners once and asserts the callback invocation * @param waiter the waiter to be resolved when finished */ private void deactivateSendersAndReceivers(Waiter waiter) { final int LATCH_TIMEOUT = 5000; // ms final CountDownLatch receiverLatch = new CountDownLatch(2); IActionListener receiverActionListener = new IActionListener() { @Override public void onFinished() { receiverLatch.countDown(); } }; final CountDownLatch senderLatch = new CountDownLatch(2); IActionListener senderActionListener = new IActionListener() { @Override public void onFinished() { senderLatch.countDown(); } }; conn1_receiver.deactivate(receiverActionListener); conn2_receiver.deactivate(receiverActionListener); conn1_sender.deactivate(senderActionListener); conn2_sender.deactivate(senderActionListener); boolean senderLatchTimedOut = false; boolean receiverLatchTimedOut = false; try { senderLatchTimedOut = !senderLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS); receiverLatchTimedOut = !receiverLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { receiverLatchTimedOut = true; } // assert that the receiver and sender latches were called twice for the initial deactivate call if (senderLatchTimedOut) { waiter.fail("One of the sender deactivate() listeners were not called in time"); } if (receiverLatchTimedOut) { waiter.fail("One of the receiver deactivate() listeners were not called in time"); } waiter.resume(); } /** * Helper method to create some traffic originating from both sides. * It will send one message per side */ private void sendOneMessagePerSide() { BlaubotMessage msg1 = new BlaubotMessage(); BlaubotMessage msg2 = new BlaubotMessage(); msg1.setPayload("TestMessageToConn1".getBytes(BlaubotConstants.STRING_CHARSET)); msg2.setPayload("TestMessageToConn2".getBytes(BlaubotConstants.STRING_CHARSET)); conn1_sender.sendMessage(msg1); conn2_sender.sendMessage(msg2); } /** * * Helper method to send multiple messages per side to create some traffic. * @param messagesPerSide number of messages to be sent from each side */ private void sendMultipleMessagesPerSide(int messagesPerSide) { for(int i=0; i<messagesPerSide; i++) { sendOneMessagePerSide(); } } @Test(timeout=45000) /** * Tests if that for each deactivate-call to a message receiver/sender the finished callback is called * after some time. */ public void testDeactivationCallbackIsAlwaysCalled() throws InterruptedException, TimeoutException { Waiter waiter = new Waiter(); // test the deactivation's happy path deactivateSendersAndReceivers(waiter); // now we test the same for consecutive deactivate calls (idempotence) for (int i=0; i < 30; i++) { Waiter w = new Waiter(); deactivateSendersAndReceivers(w); w.await(); } // now the same with activation beforehand for (int i=0; i < 30; i++) { conn1_receiver.activate(); conn2_receiver.activate(); conn1_sender.activate(); conn2_sender.activate(); Waiter w = new Waiter(); deactivateSendersAndReceivers(w); w.await(); } int cnt = 1; // now the same with multiple (idempotent) activations beforehand for (int i=0; i < cnt; i++) { conn1_receiver.activate(); conn2_receiver.activate(); conn1_sender.activate(); conn2_sender.activate(); conn1_receiver.activate(); conn2_receiver.activate(); conn1_sender.activate(); conn2_sender.activate(); Waiter w = new Waiter(); deactivateSendersAndReceivers(w); w.await(); } // now multiple deactivations for (int i=0; i < 30; i++) { conn1_receiver.activate(); conn2_receiver.activate(); conn1_sender.activate(); conn2_sender.activate(); Waiter w = new Waiter(), w2 = new Waiter(), w3 = new Waiter(); deactivateSendersAndReceivers(w); deactivateSendersAndReceivers(w2); deactivateSendersAndReceivers(w3); w.await(); w2.await(); w3.await(); } // now the same with multiple activations beforehand and everything in parallel final Waiter concurrentWaiter = new Waiter(); for (int i=0; i < cnt; i++) { new Thread(new Runnable() { @Override public void run() { conn1_receiver.activate(); conn2_receiver.activate(); conn1_sender.activate(); conn2_sender.activate(); conn1_receiver.activate(); conn2_receiver.activate(); conn1_sender.activate(); conn2_sender.activate(); Waiter w = new Waiter(); deactivateSendersAndReceivers(w); try { w.await(); } catch (TimeoutException e) { concurrentWaiter.fail("Deactivation timed out"); } concurrentWaiter.resume(); } }).start(); } concurrentWaiter.await(10000, TimeUnit.MILLISECONDS, cnt); } @Test(timeout=5000) public void testSendAndReceiveSequentially() throws InterruptedException { for(int i=0; i<100; i++) { // we create a random payload, send it over conn1, receive it on the other end, deserialize it, send it back over conn2 and then receive it on conn1 final CountDownLatch latch = new CountDownLatch(2); final byte[] testPayload = createRandomPayload(); BlaubotMessage testMsg = new BlaubotMessage(); testMsg.setPayload(testPayload); IBlaubotMessageListener conn2_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); conn2_sender.sendMessage(message); latch.countDown(); } }; IBlaubotMessageListener conn1_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); latch.countDown(); } }; conn1_receiver.addMessageListener(conn1_listener); conn2_receiver.addMessageListener(conn2_listener); // start the ping-pong conn1_sender.sendMessage(testMsg); // await the receives and asserts on each side latch.await(); //remove the attached listeners conn1_receiver.removeMessageListener(conn1_listener); conn2_receiver.removeMessageListener(conn2_listener); } } @Test(timeout=45000) /** * Tests if the chunked message logic works for the bordercase, whereas the chunked size is a * multiple of the MAX payload size */ public void testSendAndReceiveChunkedMessagesSequentiallyBorderCase() throws InterruptedException { int times = 3; // test it with a multiple for(int i=0; i<times; i++) { // we create a random payload that exceeds the MAX_PAYLOAD size, send it over conn1, receive it on the other end, deserialize it, send it back over conn2 and then receive it on conn1 final CountDownLatch latch = new CountDownLatch(2); final byte[] testPayload = createRandomPayload(BlaubotConstants.MAX_PAYLOAD_SIZE * 5); BlaubotMessage testMsg = new BlaubotMessage(); testMsg.setPayload(testPayload); IBlaubotMessageListener conn2_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); conn2_sender.sendMessage(message); latch.countDown(); } }; IBlaubotMessageListener conn1_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); latch.countDown(); } }; conn1_receiver.addMessageListener(conn1_listener); conn2_receiver.addMessageListener(conn2_listener); // start the ping-pong conn1_sender.sendMessage(testMsg); // await the receives and asserts on each side latch.await(); //remove the attached listeners conn1_receiver.removeMessageListener(conn1_listener); conn2_receiver.removeMessageListener(conn2_listener); } // test it with no multiple MAX_PAYLOAD_SIZE (should not be chunked) for(int i=0; i<times; i++) { // we create a random payload that exceeds the MAX_PAYLOAD size, send it over conn1, receive it on the other end, deserialize it, send it back over conn2 and then receive it on conn1 final CountDownLatch latch = new CountDownLatch(2); final byte[] testPayload = createRandomPayload(BlaubotConstants.MAX_PAYLOAD_SIZE); BlaubotMessage testMsg = new BlaubotMessage(); testMsg.setPayload(testPayload); IBlaubotMessageListener conn2_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); conn2_sender.sendMessage(message); latch.countDown(); } }; IBlaubotMessageListener conn1_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); latch.countDown(); } }; conn1_receiver.addMessageListener(conn1_listener); conn2_receiver.addMessageListener(conn2_listener); // start the ping-pong conn1_sender.sendMessage(testMsg); // await the receives and asserts on each side latch.await(); //remove the attached listeners conn1_receiver.removeMessageListener(conn1_listener); conn2_receiver.removeMessageListener(conn2_listener); } // test it with 5.5 multiple for(int i=0; i<times; i++) { // we create a random payload that exceeds the MAX_PAYLOAD size, send it over conn1, receive it on the other end, deserialize it, send it back over conn2 and then receive it on conn1 final CountDownLatch latch = new CountDownLatch(2); final byte[] testPayload = createRandomPayload((int)((float)BlaubotConstants.MAX_PAYLOAD_SIZE * (float)5.5)); BlaubotMessage testMsg = new BlaubotMessage(); testMsg.setPayload(testPayload); IBlaubotMessageListener conn2_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); conn2_sender.sendMessage(message); latch.countDown(); } }; IBlaubotMessageListener conn1_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); latch.countDown(); } }; conn1_receiver.addMessageListener(conn1_listener); conn2_receiver.addMessageListener(conn2_listener); // start the ping-pong conn1_sender.sendMessage(testMsg); // await the receives and asserts on each side latch.await(); //remove the attached listeners conn1_receiver.removeMessageListener(conn1_listener); conn2_receiver.removeMessageListener(conn2_listener); } } /** * Tests the border cases of chunked relay admin messages */ @Test(timeout=25000) public void testSendAndReceiveRelayAdminMessageBorderCases() throws InterruptedException { // Build payloads BlaubotMessage emptyMsg = new BlaubotMessage(); emptyMsg.setPayload(new byte[0]); BlaubotMessage maxMessage = new BlaubotMessage(); maxMessage.setPayload(createRandomPayload(BlaubotConstants.MAX_PAYLOAD_SIZE)); BlaubotMessage mediumMessage = new BlaubotMessage(); mediumMessage.setPayload(createRandomPayload(BlaubotConstants.MAX_PAYLOAD_SIZE/2)); BlaubotMessage smallMessage1 = new BlaubotMessage(); smallMessage1.setPayload(createRandomPayload(1)); BlaubotMessage smallMessage2 = new BlaubotMessage(); smallMessage2.setPayload(createRandomPayload(2)); BlaubotMessage chunked1 = new BlaubotMessage(); chunked1.setPayload(createRandomPayload(BlaubotConstants.MAX_PAYLOAD_SIZE+1)); BlaubotMessage chunked2 = new BlaubotMessage(); chunked2.setPayload(createRandomPayload(BlaubotConstants.MAX_PAYLOAD_SIZE * 2)); BlaubotMessage chunked3 = new BlaubotMessage(); chunked3.setPayload(createRandomPayload((int)(BlaubotConstants.MAX_PAYLOAD_SIZE * 1.5f))); BlaubotMessage chunked4 = new BlaubotMessage(); chunked4.setPayload(createRandomPayload((int)(BlaubotConstants.MAX_PAYLOAD_SIZE * 2.5f))); BlaubotMessage chunked5 = new BlaubotMessage(); chunked5.setPayload(createRandomPayload(BlaubotConstants.MAX_PAYLOAD_SIZE * 2 + 1)); BlaubotMessage chunked6 = new BlaubotMessage(); chunked6.setPayload(createRandomPayload(BlaubotConstants.MAX_PAYLOAD_SIZE * 2 - 1)); BlaubotMessage chunked7 = new BlaubotMessage(); chunked7.setPayload(createRandomPayload(BlaubotConstants.MAX_PAYLOAD_SIZE * 6 - 1)); final List<BlaubotMessage> allMessages = Arrays.asList(emptyMsg, maxMessage, mediumMessage, smallMessage1, smallMessage2, chunked1, chunked2, chunked3, chunked4, chunked5, chunked6, chunked7); final List<BlaubotMessage> nonChunkedMessages = Arrays.asList(emptyMsg, maxMessage, mediumMessage, smallMessage1, smallMessage2); final List<RelayAdminMessage> relayedMessages = new ArrayList<>(); for (BlaubotMessage m : nonChunkedMessages) { // put each message in a relay message RelayAdminMessage relayAdminMessage = new RelayAdminMessage(m.toBytes()); relayedMessages.add(relayAdminMessage); } /* The actual tests */ for(BlaubotMessage message : allMessages) { // we send it over conn1, receive it on the other end, deserialize it, send it back over conn2 and then receive it on conn1 final CountDownLatch latch = new CountDownLatch(2); final byte[] testPayload = message.getPayload(); IBlaubotMessageListener conn2_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); conn2_sender.sendMessage(message); latch.countDown(); } }; IBlaubotMessageListener conn1_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); latch.countDown(); } }; conn1_receiver.addMessageListener(conn1_listener); conn2_receiver.addMessageListener(conn2_listener); // start the ping-pong conn1_sender.sendMessage(message); // await the receives and asserts on each side latch.await(); //remove the attached listeners conn1_receiver.removeMessageListener(conn1_listener); conn2_receiver.removeMessageListener(conn2_listener); } // now we repeat that with the relay admin messages for(final RelayAdminMessage relayAdminMessage : relayedMessages) { // we send it over conn1, receive it on the other end, deserialize it, send it back over conn2 and then receive it on conn1 final CountDownLatch latch = new CountDownLatch(2); BlaubotMessage message = relayAdminMessage.toBlaubotMessage(); // this is the message that will be sent. it contains an AdminMEssage which contains another blaubotmessage final byte[] testPayload = message.getPayload(); final byte[] wrappedPayload = relayAdminMessage.getAsBlaubotMessage().getPayload(); IBlaubotMessageListener conn2_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { // check the raw payload Assert.assertArrayEquals(testPayload, message.getPayload()); // get the inherited blaubot message and check the payload final AbstractAdminMessage adminMessage = AdminMessageFactory.createAdminMessageFromRawMessage(message); Assert.assertTrue(adminMessage instanceof RelayAdminMessage); final RelayAdminMessage receivedRelayAdminMessage = (RelayAdminMessage) adminMessage; final BlaubotMessage wrappedBlaubotMessage = receivedRelayAdminMessage.getAsBlaubotMessage(); Assert.assertArrayEquals("The payload wrapped inside the RelayAdminMessage's BlabotMessage was not received correctly.", wrappedPayload, wrappedBlaubotMessage.getPayload()); conn2_sender.sendMessage(message); latch.countDown(); } }; IBlaubotMessageListener conn1_listener = new IBlaubotMessageListener() { @Override public void onMessage(BlaubotMessage message) { Assert.assertArrayEquals(testPayload, message.getPayload()); // get the inherited blaubot message and check the payload final AbstractAdminMessage adminMessage = AdminMessageFactory.createAdminMessageFromRawMessage(message); Assert.assertTrue(adminMessage instanceof RelayAdminMessage); final RelayAdminMessage receivedRelayAdminMessage = (RelayAdminMessage) adminMessage; final BlaubotMessage wrappedBlaubotMessage = receivedRelayAdminMessage.getAsBlaubotMessage(); Assert.assertArrayEquals("The payload wrapped inside the RelayAdminMessage's BlabotMessage was not received correctly.", wrappedPayload, wrappedBlaubotMessage.getPayload()); latch.countDown(); } }; conn1_receiver.addMessageListener(conn1_listener); conn2_receiver.addMessageListener(conn2_listener); // start the ping-pong conn1_sender.sendMessage(message); // await the receives and asserts on each side latch.await(); //remove the attached listeners conn1_receiver.removeMessageListener(conn1_listener); conn2_receiver.removeMessageListener(conn2_listener); } } }