/** * Copyright 2016 LinkedIn Corp. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ package com.github.ambry.utils; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.*; /** * Tests Utils methods */ public class UtilsTest { @Test(expected = IllegalArgumentException.class) public void testGetRandomLongException() { Utils.getRandomLong(new Random(), 0); } public void whpGetRandomLongRangeTest(int range, int draws) { // This test is probabilistic in nature if range is greater than one. // Make sure draws >> range for test to pass with high probability. int count[] = new int[range]; Random random = new Random(); for (int i = 0; i < draws; i++) { long r = Utils.getRandomLong(random, range); assertTrue(r >= 0); assertTrue(r < range); count[(int) r] = count[(int) r] + 1; } for (int i = 0; i < range; i++) { assertTrue(count[i] > 0); } } @Test public void testGetRandom() { whpGetRandomLongRangeTest(1, 1); whpGetRandomLongRangeTest(2, 1000); whpGetRandomLongRangeTest(3, 1000); whpGetRandomLongRangeTest(31, 100 * 1000); whpGetRandomLongRangeTest(99, 100 * 1000); whpGetRandomLongRangeTest(100, 100 * 1000); } @Test public void testReadStrings() throws IOException { // good case ByteBuffer buffer = ByteBuffer.allocate(10); buffer.putShort((short) 8); String s = getRandomString(8); buffer.put(s.getBytes()); buffer.flip(); String outputString = Utils.readShortString(new DataInputStream(new ByteBufferInputStream(buffer))); Assert.assertEquals(s, outputString); // 0-length buffer.rewind(); buffer.putShort(0, (short) 0); outputString = Utils.readShortString(new DataInputStream(new ByteBufferInputStream(buffer))); Assert.assertTrue(outputString.isEmpty()); // failed case buffer.rewind(); buffer.putShort((short) 10); buffer.put(s.getBytes()); buffer.flip(); try { outputString = Utils.readShortString(new DataInputStream(new ByteBufferInputStream(buffer))); Assert.assertTrue(false); } catch (IllegalArgumentException e) { Assert.assertTrue(true); } buffer.rewind(); buffer.putShort(0, (short) -1); try { outputString = Utils.readShortString(new DataInputStream(new ByteBufferInputStream(buffer))); Assert.fail("Should have encountered exception with negative length."); } catch (IllegalArgumentException e) { } // good case buffer = ByteBuffer.allocate(40004); buffer.putInt(40000); s = getRandomString(40000); buffer.put(s.getBytes()); buffer.flip(); outputString = Utils.readIntString(new DataInputStream(new ByteBufferInputStream(buffer)), StandardCharsets.UTF_8); Assert.assertEquals(s, outputString); // 0-length buffer.rewind(); buffer.putInt(0, 0); outputString = Utils.readShortString(new DataInputStream(new ByteBufferInputStream(buffer))); Assert.assertTrue(outputString.isEmpty()); // failed case buffer.rewind(); buffer.putInt(50000); buffer.put(s.getBytes()); buffer.flip(); try { outputString = Utils.readIntString(new DataInputStream(new ByteBufferInputStream(buffer)), StandardCharsets.UTF_8); fail("Should have failed"); } catch (IllegalArgumentException e) { // expected. } buffer.rewind(); buffer.putInt(0, -1); try { Utils.readIntString(new DataInputStream(new ByteBufferInputStream(buffer)), StandardCharsets.UTF_8); Assert.fail("Should have encountered exception with negative length."); } catch (IllegalArgumentException e) { // expected. } } @Test public void testReadBuffers() throws IOException { byte[] buf = new byte[40004]; new Random().nextBytes(buf); ByteBuffer inputBuf = ByteBuffer.wrap(buf); inputBuf.putInt(0, 40000); ByteBuffer outputBuf = Utils.readIntBuffer(new DataInputStream(new ByteBufferInputStream(inputBuf))); for (int i = 0; i < 40000; i++) { Assert.assertEquals(buf[i + 4], outputBuf.array()[i]); } // 0 size inputBuf.rewind(); inputBuf.putInt(0, 0); outputBuf = Utils.readIntBuffer(new DataInputStream(new ByteBufferInputStream(inputBuf))); Assert.assertEquals("Output should be of length 0", 0, outputBuf.array().length); // negative size inputBuf.rewind(); inputBuf.putInt(0, -1); try { Utils.readIntBuffer(new DataInputStream(new ByteBufferInputStream(inputBuf))); Assert.fail("Should have encountered exception with negative length."); } catch (IllegalArgumentException e) { } buf = new byte[10]; new Random().nextBytes(buf); inputBuf = ByteBuffer.wrap(buf); inputBuf.putShort(0, (short) 8); outputBuf = Utils.readShortBuffer(new DataInputStream(new ByteBufferInputStream(inputBuf))); for (int i = 0; i < 8; i++) { Assert.assertEquals(buf[i + 2], outputBuf.array()[i]); } // 0 size inputBuf.rewind(); inputBuf.putShort(0, (short) 0); outputBuf = Utils.readShortBuffer(new DataInputStream(new ByteBufferInputStream(inputBuf))); Assert.assertEquals("Output should be of length 0", 0, outputBuf.array().length); // negative size inputBuf.rewind(); inputBuf.putShort(0, (short) -1); try { Utils.readShortBuffer(new DataInputStream(new ByteBufferInputStream(inputBuf))); Assert.fail("Should have encountered exception with negative length."); } catch (IllegalArgumentException e) { } } @Test public void testHashCode() { String s = "test"; Integer i = new Integer(245); Double d = new Double(2.4); Object[] objs = new Object[3]; objs[0] = s; objs[1] = i; objs[2] = d; int code1 = Utils.hashcode(objs); int code2 = Utils.hashcode(objs); Assert.assertEquals(code1, code2); } @Test public void testReadWriteStringToFile() throws IOException { File file = File.createTempFile("test", "1"); file.deleteOnExit(); Utils.writeStringToFile("Test", file.getPath()); String outputString = Utils.readStringFromFile(file.getPath()); Assert.assertEquals("Test", outputString); } @Test public void testSerializeNullableString() { String randomString = getRandomString(10); ByteBuffer outputBuffer = ByteBuffer.allocate(4 + randomString.getBytes().length); Utils.serializeNullableString(outputBuffer, randomString); outputBuffer.flip(); int length = outputBuffer.getInt(); assertEquals("Input and output string lengths don't match ", randomString.getBytes().length, length); byte[] output = new byte[length]; outputBuffer.get(output); assertFalse("Output buffer shouldn't have any remaining, but has " + outputBuffer.remaining() + " bytes", outputBuffer.hasRemaining()); String outputString = new String(output); assertEquals("Input and output strings don't match", randomString, outputString); randomString = null; outputBuffer = ByteBuffer.allocate(4); Utils.serializeNullableString(outputBuffer, randomString); outputBuffer.flip(); length = outputBuffer.getInt(); assertEquals("Input and output string lengths don't match", 0, length); output = new byte[length]; outputBuffer.get(output); assertFalse("Output buffer shouldn't have any remaining, but has " + outputBuffer.remaining() + " bytes", outputBuffer.hasRemaining()); outputString = new String(output); assertEquals("Output string \"" + outputString + "\" expected to be empty", outputString, ""); } @Test public void testSerializeString() { String randomString = getRandomString(10); ByteBuffer outputBuffer = ByteBuffer.allocate(4 + randomString.getBytes().length); Utils.serializeString(outputBuffer, randomString, StandardCharsets.US_ASCII); outputBuffer.flip(); int length = outputBuffer.getInt(); assertEquals("Input and output string lengths don't match", randomString.getBytes().length, length); byte[] output = new byte[length]; outputBuffer.get(output); assertFalse("Output buffer shouldn't have any remaining, but has " + outputBuffer.remaining() + " bytes", outputBuffer.hasRemaining()); String outputString = new String(output); assertEquals("Input and output strings don't match", randomString, outputString); randomString = getRandomString(10) + "Ò"; outputBuffer = ByteBuffer.allocate(4 + randomString.getBytes().length); Utils.serializeString(outputBuffer, randomString, StandardCharsets.US_ASCII); outputBuffer.flip(); length = outputBuffer.getInt(); assertEquals("Input and output string lengths don't match ", (randomString.getBytes().length - 1), length); output = new byte[length]; outputBuffer.get(output); assertFalse("Output buffer shouldn't have any remaining, but has " + outputBuffer.remaining() + " bytes", outputBuffer.hasRemaining()); outputString = new String(output); randomString = randomString.substring(0, randomString.length() - 1) + "?"; assertEquals("Input and output strings don't match", randomString, outputString); randomString = ""; outputBuffer = ByteBuffer.allocate(4); Utils.serializeString(outputBuffer, randomString, StandardCharsets.US_ASCII); outputBuffer.flip(); length = outputBuffer.getInt(); assertEquals("Input and output string lengths don't match", 0, length); output = new byte[length]; outputBuffer.get(output); assertFalse("Output buffer shouldn't have any remaining, but has " + outputBuffer.remaining() + " bytes", outputBuffer.hasRemaining()); outputString = new String(output); assertEquals("Output string \"" + outputString + "\" expected to be empty", outputString, ""); randomString = getRandomString(10); outputBuffer = ByteBuffer.allocate(4 + randomString.getBytes().length - 1); try { Utils.serializeString(outputBuffer, randomString, StandardCharsets.US_ASCII); Assert.fail("Serialization should have failed due to insufficient space"); } catch (RuntimeException e) { } } @Test public void testDeserializeString() { String randomString = getRandomString(10); ByteBuffer outputBuffer = ByteBuffer.allocate(4 + randomString.getBytes().length); Utils.serializeString(outputBuffer, randomString, StandardCharsets.US_ASCII); outputBuffer.flip(); String outputString = Utils.deserializeString(outputBuffer, StandardCharsets.US_ASCII); assertEquals("Input and output strings don't match", randomString, outputString); randomString = getRandomString(10) + "Ò"; outputBuffer = ByteBuffer.allocate(4 + randomString.getBytes().length); Utils.serializeString(outputBuffer, randomString, StandardCharsets.US_ASCII); outputBuffer.flip(); outputString = Utils.deserializeString(outputBuffer, StandardCharsets.US_ASCII); randomString = randomString.substring(0, randomString.length() - 1) + "?"; assertEquals("Input and output strings don't match", randomString, outputString); randomString = ""; outputBuffer = ByteBuffer.allocate(4); Utils.serializeString(outputBuffer, randomString, StandardCharsets.US_ASCII); outputBuffer.flip(); outputString = Utils.deserializeString(outputBuffer, StandardCharsets.US_ASCII); assertEquals("Output string \"" + outputString + "\" expected to be empty", outputString, ""); randomString = getRandomString(10); outputBuffer = ByteBuffer.allocate(4 + randomString.getBytes().length); outputBuffer.putInt(12); outputBuffer.put(randomString.getBytes()); outputBuffer.flip(); try { outputString = Utils.deserializeString(outputBuffer, StandardCharsets.US_ASCII); Assert.fail("Deserialization should have failed " + randomString); } catch (RuntimeException e) { } } @Test public void testGetObj() { try { MockClassForTesting mockObj = Utils.getObj("com.github.ambry.utils.MockClassForTesting"); Assert.assertNotNull(mockObj); Assert.assertTrue(mockObj.noArgConstructorInvoked); mockObj = Utils.getObj("com.github.ambry.utils.MockClassForTesting", new Object()); Assert.assertNotNull(mockObj); Assert.assertTrue(mockObj.oneArgConstructorInvoked); mockObj = Utils.getObj("com.github.ambry.utils.MockClassForTesting", new Object(), new Object()); Assert.assertNotNull(mockObj); Assert.assertTrue(mockObj.twoArgConstructorInvoked); mockObj = Utils.getObj("com.github.ambry.utils.MockClassForTesting", new Object(), new Object(), new Object()); Assert.assertNotNull(mockObj); Assert.assertTrue(mockObj.threeArgConstructorInvoked); mockObj = Utils.getObj("com.github.ambry.utils.MockClassForTesting", new Object(), new Object(), new Object(), new Object()); Assert.assertNotNull(mockObj); Assert.assertTrue(mockObj.fourArgConstructorInvoked); } catch (Exception e) { Assert.assertTrue(false); } } /** * Tests {@link Utils#getRootCause(Throwable)}. */ @Test public void getRootCauseTest() { int nestingLevel = 5; String innerExceptionMsg = "InnerException"; String outerExceptionMsgBase = "OuterException"; Exception innerException = new Exception(innerExceptionMsg); Exception outerException = null; for (int i = 0; i < nestingLevel; i++) { outerException = new Exception(outerExceptionMsgBase + "-" + i, innerException); } assertEquals("Message should that of the innermost exception", innerExceptionMsg, Utils.getRootCause(outerException).getMessage()); } /** * Test {@link Utils#newScheduler(int, String, boolean)} */ @Test public void newSchedulerTest() throws Exception { ScheduledExecutorService scheduler = Utils.newScheduler(2, false); Future<String> future = scheduler.schedule(new Callable<String>() { @Override public String call() { return Thread.currentThread().getName(); } }, 50, TimeUnit.MILLISECONDS); String threadName = future.get(10, TimeUnit.SECONDS); assertTrue("Unexpected thread name returned: " + threadName, threadName.startsWith("ambry-scheduler-")); scheduler.shutdown(); } /** * Test {@link Utils#getTimeInMsToTheNearestSec(long)} */ @Test public void getTimeInMsToTheNearestSecTest() { long msValue = Utils.getRandomLong(TestUtils.RANDOM, 1000000); long expectedMsValue = (msValue / Time.MsPerSec) * Time.MsPerSec; assertEquals("Time in Ms to the nearest Sec mismatch ", expectedMsValue, Utils.getTimeInMsToTheNearestSec(msValue)); msValue = Utils.Infinite_Time; assertEquals("Time in Ms to the nearest Sec mismatch ", msValue, Utils.getTimeInMsToTheNearestSec(msValue)); } /** * Tests {@link Utils#addSecondsToEpochTime(long, long)} */ @Test public void addSecondsToEpochTimeTest() { for (int i = 0; i < 5; i++) { long epochTimeInMs = SystemTime.getInstance().milliseconds() + TestUtils.RANDOM.nextInt(10000); long deltaInSecs = TestUtils.RANDOM.nextInt(10000); assertEquals("epoch epochTimeInMs mismatch ", epochTimeInMs + (deltaInSecs * Time.MsPerSec), Utils.addSecondsToEpochTime(epochTimeInMs, deltaInSecs)); assertEquals("epoch epochTimeInMs mismatch ", Utils.Infinite_Time, Utils.addSecondsToEpochTime(Utils.Infinite_Time, deltaInSecs)); assertEquals("epoch epochTimeInMs mismatch ", Utils.Infinite_Time, Utils.addSecondsToEpochTime(epochTimeInMs, Utils.Infinite_Time)); } } private static final String CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static Random random = new Random(); public static String getRandomString(int length) { StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { sb.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length()))); } return sb.toString(); } } class MockClassForTesting { public boolean noArgConstructorInvoked = false; public boolean oneArgConstructorInvoked = false; public boolean twoArgConstructorInvoked = false; public boolean threeArgConstructorInvoked = false; public boolean fourArgConstructorInvoked = false; public MockClassForTesting() { noArgConstructorInvoked = true; } public MockClassForTesting(Object obj1) { oneArgConstructorInvoked = true; } public MockClassForTesting(Object obj1, Object obj2) { twoArgConstructorInvoked = true; } public MockClassForTesting(Object obj1, Object obj2, Object obj3) { threeArgConstructorInvoked = true; } public MockClassForTesting(Object obj1, Object obj2, Object obj3, Object obj4) { fourArgConstructorInvoked = true; } }