/*******************************************************************************
* Copyright 2015 Netflix
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.netflix.dyno.jedis;
import com.netflix.dyno.connectionpool.*;
import com.netflix.dyno.connectionpool.impl.ConnectionPoolImpl;
import com.netflix.dyno.connectionpool.impl.LastOperationMonitor;
import com.netflix.dyno.connectionpool.impl.utils.ZipUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import redis.clients.jedis.Jedis;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests compression commands.
*
* Note - The underlying jedis client has been mocked to echo back the value given for SET operations and to
* ensure values over a 2KB threshold are compressed for HMSET operations.
*/
public class CompressionTest {
private DynoJedisClient client;
private ConnectionPool<Jedis> connectionPool;
private OperationMonitor opMonitor;
@Mock
DynoJedisPipelineMonitor pipelineMonitor;
@Mock
ConnectionPoolMonitor cpMonitor;
@Mock
ConnectionPoolConfiguration config;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
when(config.getValueCompressionThreshold()).thenReturn(2 * 1024);
opMonitor = new LastOperationMonitor();
connectionPool = new UnitTestConnectionPool(config, opMonitor);
client = new DynoJedisClient.TestBuilder()
.withAppname("CompressionTest")
.withConnectionPool(connectionPool)
.build();
}
@Test
public void testDynoJedis_Set_UnderCompressionThreshold() {
String result = client.set("keyFor1KBValue", VALUE_1KB);
Assert.assertEquals(VALUE_1KB, result); // value should not be compressed
}
@Test
public void testDynoJedis_Set_AboveCompressionThreshold() throws IOException {
String result = client.set("keyFor3KBValue", VALUE_3KB);
Assert.assertTrue(result.length() < 3072);
Assert.assertTrue(ZipUtils.isCompressed(result));
}
@Test
public void testDynoJedis_Get_UnderCompressionThreshold() {
client.set(KEY_1KB, VALUE_1KB);
String result = client.get(KEY_1KB);
Assert.assertEquals(VALUE_1KB, result);
}
@Test
public void testDynoJedis_Get_AboveCompressionThreshold() throws IOException {
client.set(KEY_3KB, VALUE_3KB);
String result = client.get(KEY_3KB);
Assert.assertTrue(!ZipUtils.isCompressed(result));
Assert.assertEquals(VALUE_3KB, result);
}
@Test
public void testDynoJedis_Mget() throws IOException {
client.set(KEY_1KB, VALUE_1KB);
client.set(KEY_2KB, VALUE_2KB);
client.set(KEY_3KB, VALUE_3KB);
// Expect one key as missing in datastore
//client.set(KEY_4KB, VALUE_4KB);
client.set(KEY_5KB, VALUE_5KB);
final int MGET_KEYS = 10;
String[] keys = {KEY_1KB, KEY_2KB, KEY_3KB, KEY_4KB, KEY_5KB};
// expected value list
String[] values = {VALUE_1KB, VALUE_2KB, VALUE_3KB, null, VALUE_5KB};
List<String> result = client.mget(keys);
Assert.assertEquals(result.size(), keys.length);
for (int i = 0; i< keys.length; i++) {
String value = result.get(i);
Assert.assertEquals(value, values[i]);
}
}
@Test
public void testDynoJedis_Hmset_AboveCompressionThreshold() throws IOException {
final Map<String, String> map = new HashMap<String, String>();
map.put(KEY_1KB, VALUE_1KB);
map.put(KEY_3KB, VALUE_3KB);
client.d_hmset("compressionTestKey", map);
LastOperationMonitor monitor = (LastOperationMonitor) opMonitor;
Assert.assertTrue(1 == monitor.getSuccessCount(OpName.HMSET.name(), true));
}
@Test
public void testZipUtilsDecompressBytesNonBase64() throws Exception {
String s = "ABCDEFG__abcdefg__1234567890'\"\\+=-::ABCDEFG__abcdefg__1234567890'\"\\+=-::ABCDEFG__abcdefg__1234567890'\"\\+=-";
byte[] val = s.getBytes();
byte[] compressed = ZipUtils.compressBytesNonBase64(val);
Assert.assertTrue(compressed.length < val.length);
byte[] decompressed = ZipUtils.decompressBytesNonBase64(compressed);
Assert.assertEquals(s, new String(decompressed));
}
// @Test
// public void testDynoJedisPipeline_Binary_HGETALL() throws Exception {
// Map<byte[], byte[]>
//
// ConnectionPoolImpl cp = mock(ConnectionPoolImpl.class);
//
// DynoJedisPipeline pipeline = new
// DynoJedisPipeline(cp, pipelineMonitor, cpMonitor);
//
// //pipeline.hgetAll();
//
// }
public static final String KEY_1KB = "keyFor1KBValue";
public static final String KEY_2KB = "keyFor2KBValue";
public static final String KEY_3KB = "keyFor3KBValue";
public static final String KEY_4KB = "keyFor4KBValue";
public static final String KEY_5KB = "keyFor5KBValue";
public static final String VALUE_1KB = generateValue(1);
public static final String VALUE_2KB = generateValue(2);
public static final String VALUE_3KB = generateValue(3);
public static final String VALUE_4KB = generateValue(4);
public static final String VALUE_5KB = generateValue(5);
private static String generateValue(int kilobytes) {
StringBuilder sb = new StringBuilder(kilobytes * 512); // estimating 2 bytes per char
for (int i = 0; i < kilobytes; i++) {
for (int j = 0; j < 10; j++) {
sb.append("abcdefghijklmnopqrstuvwxzy0123456789a1b2c3d4f5g6h7"); // 50 characters (~100 bytes)
sb.append(":");
sb.append("abcdefghijklmnopqrstuvwxzy0123456789a1b2c3d4f5g6h7");
sb.append(":");
}
}
return sb.toString();
}
}