package com.samknows.tests; import android.os.ConditionVariable; import org.robolectric.RobolectricTestRunner; import org.junit.Test; import org.junit.runner.RunWith; import com.samknows.XCT; import com.samknows.measurement.TestRunner.SKTestRunner; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import static com.samknows.tests.LatencyTest.*; @RunWith(RobolectricTestRunner.class) public class LatencyTestTests { final long cMicrosecondInNanoSeconds = 1000; final long cMillisecondInNanoSeconds = cMicrosecondInNanoSeconds * 1000; final long cSecondInNanoSeconds = cMillisecondInNanoSeconds * 1000; final long cTenthOfASecondInNanoSeconds = cSecondInNanoSeconds / 10; @org.junit.Before public void setUp() throws Exception { } @org.junit.After public void tearDown() throws Exception { // https://github.com/robolectric/robolectric/issues/1890 // "Robolectric testing has been designed on the assumption that no state is preserved // between tests & you're always starting from scratch. // Singletons & other static state maintained by your // application and/or test can break that assumption" SKTestRunner.sSetRunningTestRunner(null); } final String cFakeIpAddress = "127.0.0.1"; public class MockSKUDPSocket implements ISKUDPSocket { public int mSendCalledCount = 0; public int mReceiveCalledCount = 0; public int mSetSoTimeoutCalledCount = 0; public int mCloseCalledCount = 0; @Override public void open() throws SocketException { } byte[] lastSent = null; @Override public void send(DatagramPacket pack) throws IOException { mSendCalledCount++; // Copy the "sent" data, so we can "return" it via the next receive method call. lastSent = pack.getData().clone(); } @Override public void receive(DatagramPacket pack) throws IOException { mReceiveCalledCount++; // Return what we sent... BUT WITH THE SERVER MAGIC applied! // The SamKnows server *always* sets the magic value this way - the LatencyTest implementation relies on this. byte[] returningArrayRepresentation = pack.getData(); System.arraycopy(lastSent, 0, returningArrayRepresentation, 0, lastSent.length); // BUT use a MAGIC value in the response (as used by the server!) final int serverMagic = LatencyTest.UdpDatagram.SERVERTOCLIENTMAGIC; returningArrayRepresentation[12] = (byte) (serverMagic >>> 24); returningArrayRepresentation[13] = (byte) (serverMagic >>> 16); returningArrayRepresentation[14] = (byte) (serverMagic >>> 8); returningArrayRepresentation[15] = (byte) (serverMagic); } @Override public void setSoTimeout(int timeout) throws SocketException { mSetSoTimeoutCalledCount++; } @Override public void close() { mCloseCalledCount++; } @Override public long getStartTimeNanoseconds() { return 0; } // For purposes of testing, to give useful standard deviation etc.; ensure that the packets have a spread of timings. long mInterpacketDelay = cTenthOfASecondInNanoSeconds; long timeLast = 0; @Override public long getTimeNowNanoseconds() { long result = timeLast; timeLast += mInterpacketDelay; mInterpacketDelay += cTenthOfASecondInNanoSeconds; return result; } @Override public InetAddress getInetAddressByName(String host) throws UnknownHostException { // This will always work, as a dummy value. InetAddress result = InetAddress.getByName(cFakeIpAddress); return result; } } public LatencyTestTests() { } final static Integer cPort = 80; final static String cTarget = "TestTarget"; final static Double cInterPacketTimeSeconds = 0.1; final static Double cDelayTimeoutSeconds = 2.0; final static Integer cNumberOfPackets = 10; final static Integer cPercentile = 10; private static LatencyTest sCreateLatencyTest(Integer optionalTimeoutInMicroseconds) { List<Param> params = new ArrayList<>(); params.add(new Param(SKAbstractBaseTest.PORT, String.valueOf(cPort))); params.add(new Param(SKAbstractBaseTest.TARGET, cTarget)); params.add(new Param(TestFactory.INTERPACKETTIME, String.valueOf((int) (cInterPacketTimeSeconds * 1000000.0)))); // 0.1 seconds - in Microseconds! params.add(new Param(TestFactory.DELAYTIMEOUT, String.valueOf((int) (cDelayTimeoutSeconds * 1000000.0)))); // 0.1 seconds - in Microseconds! params.add(new Param(TestFactory.NUMBEROFPACKETS, String.valueOf(cNumberOfPackets))); params.add(new Param(TestFactory.PERCENTILE, String.valueOf(cPercentile))); if (optionalTimeoutInMicroseconds != null) { params.add(new Param(TestFactory.MAXTIME, String.valueOf(optionalTimeoutInMicroseconds))); // Microseconds! } LatencyTest theTest = LatencyTest.sCreateLatencyTest(params); return theTest; } static final String cLiveServer = "all-the1.samknows.com"; static final String cLivePort = "6000"; private static LatencyTest sCreateLatencyTest_Live() { List<Param> params = new ArrayList<>(); params.add(new Param(SKAbstractBaseTest.PORT, cLivePort)); params.add(new Param(SKAbstractBaseTest.TARGET, cLiveServer)); params.add(new Param(TestFactory.INTERPACKETTIME, String.valueOf((int) (cInterPacketTimeSeconds * 1000000.0)))); // 0.1 seconds - in Microseconds! params.add(new Param(TestFactory.DELAYTIMEOUT, String.valueOf((int) (cDelayTimeoutSeconds * 1000000.0)))); // 0.1 seconds - in Microseconds! params.add(new Param(TestFactory.NUMBEROFPACKETS, String.valueOf(cNumberOfPackets))); params.add(new Param(TestFactory.PERCENTILE, "100")); // Use "all" results! params.add(new Param(TestFactory.MAXTIME, String.valueOf((int) (10 * 1000000.0)))); // 10 seconds - in Microseconds! LatencyTest theTest = LatencyTest.sCreateLatencyTest(params); return theTest; } // The following test expects 0 out of 10 packets to be lost (as there is no timeout) @Test public void testLatencyTestWithNoTimeLimit() throws Exception{ MockSKUDPSocket mockSKUDPSocket = new MockSKUDPSocket(); // Create latency test with no time limit! LatencyTest latencyTest = sCreateLatencyTest(null); // Set dummy SKTestRunner instance, or the tests will give lots of assertions... SKTestRunner.sSetRunningTestRunner(new SKTestRunner()); // We should now be able to test the test has worked as expected. latencyTest.executeWithSKUDPSocket(mockSKUDPSocket); XCT.Assert(mockSKUDPSocket.mSendCalledCount > 0); XCT.AssertEquals(mockSKUDPSocket.mReceiveCalledCount, 7); // cNumberOfPackets); XCT.AssertEquals(mockSKUDPSocket.mSetSoTimeoutCalledCount, 7+1); XCT.AssertEquals(mockSKUDPSocket.mCloseCalledCount, 1); // And verify results! XCT.AssertEquals(latencyTest.getAverageMicroseconds(), 2100000); XCT.AssertEquals(latencyTest.getMinimumMicroseconds(), 300000); XCT.AssertEquals(latencyTest.getMaximumMicroseconds(), 3900000); XCT.AssertEquals(latencyTest.getStdDeviationMicroseconds(), 1296148); XCT.AssertEquals(latencyTest.getIpAddress(), cFakeIpAddress); XCT.AssertEquals(latencyTest.getJitter(), 1800000); XCT.AssertEquals(latencyTest.getSentPackets(), cNumberOfPackets); XCT.AssertEquals(latencyTest.getReceivedPackets(), 7); XCT.AssertEquals(latencyTest.getLostPackets(), 3); } // The following test expects 3 out of 10 packets to be lost (due to timeout) @Test public void testLatencyTestWithTimeLimit() throws Exception{ MockSKUDPSocket mockSKUDPSocket = new MockSKUDPSocket(); // Create latency test with time limit! final int cTimeoutAfterSecondsInMicroseconds = 3 * 1000000; LatencyTest latencyTest = sCreateLatencyTest(Integer.valueOf(cTimeoutAfterSecondsInMicroseconds)); // Set dummy SKTestRunner instance, or the tests will give lots of assertions... SKTestRunner.sSetRunningTestRunner(new SKTestRunner()); // We should now be able to test the test has worked as expected. latencyTest.executeWithSKUDPSocket(mockSKUDPSocket); XCT.Assert(mockSKUDPSocket.mSendCalledCount > 0); XCT.AssertEquals(mockSKUDPSocket.mReceiveCalledCount, 2); XCT.AssertEquals(mockSKUDPSocket.mSetSoTimeoutCalledCount, 2+1); XCT.AssertEquals(mockSKUDPSocket.mCloseCalledCount, 1); // And verify results! XCT.AssertEquals(latencyTest.getAverageMicroseconds(), 900000); XCT.AssertEquals(latencyTest.getMinimumMicroseconds(), 500000); XCT.AssertEquals(latencyTest.getMaximumMicroseconds(), 1300000); XCT.AssertEquals(latencyTest.getStdDeviationMicroseconds(), 565685); XCT.AssertEquals(latencyTest.getIpAddress(), cFakeIpAddress); XCT.AssertEquals(latencyTest.getJitter(), 400000); XCT.AssertEquals(latencyTest.getSentPackets(), 2); XCT.AssertEquals(latencyTest.getReceivedPackets(), 2); XCT.AssertEquals(latencyTest.getLostPackets(), 0); } // Include a LIVE test, to verify that basic behaviour of the non-mocked system is still fundamentally OK. // This is less able to make specific assertions. @Test public void testLatencyTest_Live() throws Exception{ final boolean[] responseCalled = {false}; final ConditionVariable cv = new ConditionVariable(); final LatencyTest latencyTest = sCreateLatencyTest_Live(); // Set dummy SKTestRunner instance, or the tests will give lots of assertions... SKTestRunner.sSetRunningTestRunner(new SKTestRunner()); Thread thread = new Thread() { @Override public void run() { // We should now be able to test the test has worked as expected. latencyTest.runBlockingTestToFinishInThisThread(); responseCalled[0] = true; cv.open(); } }; thread.start(); cv.block(20000); // And verify results! XCT.Assert(responseCalled[0] == true); XCT.Assert(latencyTest.getAverageMicroseconds() > 0); XCT.Assert(latencyTest.getMinimumMicroseconds() > 0); XCT.Assert(latencyTest.getMinimumMicroseconds() <= latencyTest.getAverageMicroseconds()); XCT.Assert(latencyTest.getMaximumMicroseconds() > 0); XCT.Assert(latencyTest.getMaximumMicroseconds() >= latencyTest.getAverageMicroseconds()); XCT.Assert(latencyTest.getStdDeviationMicroseconds() > 0); XCT.Assert(latencyTest.getIpAddress().equals(cFakeIpAddress) == false); XCT.Assert(latencyTest.getJitter() > 0); // Note: in GENERAL operation, this MIGHT be less - but generally shouldn't be! XCT.Assert(latencyTest.getSentPackets() > 0); XCT.Assert(latencyTest.getSentPackets() <= cNumberOfPackets); // Note: in GENERAL operation, this MIGHT be less - but generally shouldn't be! XCT.Assert(latencyTest.getReceivedPackets() > 0); // Should never get back more packets than we sent! XCT.Assert(latencyTest.getReceivedPackets() <= latencyTest.getSentPackets()); // Note: in GENERAL operation, lost packets are quite common. Should never be negative, however! XCT.Assert(latencyTest.getLostPackets() >= 0); } }