blob: ae7d04da74b3b3e1f2be0fffd33c90dd2541e3d5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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 org.apache.geode.redis.internal.executor.server;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.apache.logging.log4j.util.TriConsumer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.BitOP;
import redis.clients.jedis.Jedis;
import org.apache.geode.test.awaitility.GeodeAwaitility;
import org.apache.geode.test.dunit.rules.RedisPortSupplier;
public abstract class AbstractHitsMissesIntegrationTest implements RedisPortSupplier {
private static final String HITS = "keyspace_hits";
private static final String MISSES = "keyspace_misses";
protected Jedis jedis;
private static final int REDIS_CLIENT_TIMEOUT =
Math.toIntExact(GeodeAwaitility.getTimeout().toMillis());
@Before
public void classSetup() {
jedis = new Jedis("localhost", getPort(), REDIS_CLIENT_TIMEOUT);
jedis.set("string", "yarn");
jedis.sadd("set", "cotton");
jedis.hset("hash", "green", "eggs");
}
@After
public void teardown() {
jedis.flushAll();
jedis.close();
}
// ------------ Key related commands -----------
@Test
public void testKeys() {
runCommandAndAssertNoStatUpdates("*", k -> jedis.keys(k));
}
@Test
public void testExists() {
runCommandAndAssertHitsAndMisses("string", k -> jedis.exists(k));
}
@Test
public void testType() {
runCommandAndAssertHitsAndMisses("string", k -> jedis.type(k));
}
@Test
public void testTtl() {
runCommandAndAssertHitsAndMisses("string", k -> jedis.ttl(k));
}
@Test
public void testPttl() {
runCommandAndAssertHitsAndMisses("string", k -> jedis.pttl(k));
}
@Test
public void testRename() {
runCommandAndAssertNoStatUpdates("string", (k, v) -> jedis.rename(k, v));
}
// ------------ String related commands -----------
@Test
public void testGet() {
runCommandAndAssertHitsAndMisses("string", k -> jedis.get(k));
}
@Test
public void testGetset() {
runCommandAndAssertHitsAndMisses("string", (k, v) -> jedis.getSet(k, v));
}
@Test
public void testStrlen() {
runCommandAndAssertHitsAndMisses("string", k -> jedis.strlen(k));
}
@Test
public void testDel() {
runCommandAndAssertNoStatUpdates("string", k -> jedis.del(k));
}
@Test
public void testSet() {
runCommandAndAssertNoStatUpdates("string", (k, v) -> jedis.set(k, v));
}
@Test
public void testAppend() {
runCommandAndAssertNoStatUpdates("string", (k, v) -> jedis.append(k, v));
}
@Test
public void testSetWrongType() {
runCommandAndAssertNoStatUpdates("set", (k, v) -> jedis.set(k, v));
}
// ------------ Bit related commands -----------
@Test
public void testBitcount() {
runCommandAndAssertHitsAndMisses("string", k -> jedis.bitcount(k));
}
@Test
public void testBitpos() {
Map<String, String> info = getInfo(jedis);
Long currentHits = Long.parseLong(info.get(HITS));
Long currentMisses = Long.parseLong(info.get(MISSES));
jedis.bitpos("string", true);
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 1));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses));
jedis.bitpos("missed", true);
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 1));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses + 1));
}
@Test
public void testBitop() {
Map<String, String> info = getInfo(jedis);
Long currentHits = Long.parseLong(info.get(HITS));
Long currentMisses = Long.parseLong(info.get(MISSES));
jedis.bitop(BitOP.OR, "dest", "string", "string", "dest");
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 2));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses + 1));
jedis.bitop(BitOP.OR, "dest", "string", "missed");
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 2 + 1));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses + 1 + 1));
}
// ------------ Set related commands -----------
// FYI - In Redis 5.x SPOP produces inconsistent results depending on whether a count was given
// or not. In Redis 6.x SPOP does not update any stats.
@Test
public void testSpop() {
runCommandAndAssertNoStatUpdates("set", k -> jedis.spop(k));
}
@Test
public void testSadd() {
runCommandAndAssertNoStatUpdates("set", (k, v) -> jedis.sadd(k, v));
}
@Test
public void testSrem() {
runCommandAndAssertNoStatUpdates("set", (k, v) -> jedis.srem(k, v));
}
@Test
public void testSmembers() {
runCommandAndAssertHitsAndMisses("set", k -> jedis.smembers(k));
}
@Test
public void testSismember() {
runCommandAndAssertHitsAndMisses("set", (k, v) -> jedis.sismember(k, v));
}
@Test
public void testSrandmember() {
runCommandAndAssertHitsAndMisses("set", k -> jedis.srandmember(k));
}
@Test
public void testScard() {
runCommandAndAssertHitsAndMisses("set", k -> jedis.scard(k));
}
@Test
public void testSscan() {
runCommandAndAssertHitsAndMisses("set", (k, v) -> jedis.sscan(k, v));
}
@Test
public void testSdiff() {
runDiffCommandAndAssertHitsAndMisses("set", (k, v) -> jedis.sdiff(k, v));
}
@Test
public void testSdiffstore() {
runDiffStoreCommandAndAssertNoStatUpdates("set", (k, v, s) -> jedis.sdiffstore(k, v, s));
}
@Test
public void testSinter() {
runDiffCommandAndAssertHitsAndMisses("set", (k, v) -> jedis.sinter(k, v));
}
@Test
public void testSinterstore() {
runDiffStoreCommandAndAssertNoStatUpdates("set", (k, v, s) -> jedis.sinterstore(k, v, s));
}
@Test
public void testSunion() {
runDiffCommandAndAssertHitsAndMisses("set", (k, v) -> jedis.sunion(k, v));
}
@Test
public void testSunionstore() {
runDiffStoreCommandAndAssertNoStatUpdates("set", (k, v, s) -> jedis.sunionstore(k, v, s));
}
// ------------ Hash related commands -----------
@Test
public void testHdel() {
runCommandAndAssertNoStatUpdates("hash", (k, v) -> jedis.hdel(k, v));
}
@Test
public void testHset() {
runCommandAndAssertNoStatUpdates("hash", (k, v, s) -> jedis.hset(k, v, s));
}
@Test
public void testHget() {
runCommandAndAssertHitsAndMisses("hash", (k, v) -> jedis.hget(k, v));
}
@Test
public void testHgetall() {
runCommandAndAssertHitsAndMisses("hash", k -> jedis.hgetAll(k));
}
@Test
public void testHkeys() {
runCommandAndAssertHitsAndMisses("hash", k -> jedis.hkeys(k));
}
@Test
public void testHlen() {
runCommandAndAssertHitsAndMisses("hash", k -> jedis.hlen(k));
}
@Test
public void testHvals() {
runCommandAndAssertHitsAndMisses("hash", k -> jedis.hvals(k));
}
@Test
public void testHmget() {
runCommandAndAssertHitsAndMisses("hash", (k, v) -> jedis.hmget(k, v));
}
@Test
public void testHexists() {
runCommandAndAssertHitsAndMisses("hash", (k, v) -> jedis.hexists(k, v));
}
@Test
public void testHstrlen() {
runCommandAndAssertHitsAndMisses("hash", (k, v) -> jedis.hstrlen(k, v));
}
@Test
public void testHscan() {
runCommandAndAssertHitsAndMisses("hash", (k, v) -> jedis.hscan(k, v));
}
@Test
public void testHMSet() {
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
runCommandAndAssertNoStatUpdates("key", (k) -> jedis.hmset(k, map));
}
// ------------ Key related commands -----------
@Test
public void testExpire() {
runCommandAndAssertNoStatUpdates("hash", (k) -> jedis.expire(k, 5));
}
@Test
public void testPassiveExpiration() {
runCommandAndAssertNoStatUpdates("hash", (k) -> {
jedis.expire(k, 1);
GeodeAwaitility.await().during(Duration.ofSeconds(3)).until(() -> true);
});
}
@Test
public void testExpireAt() {
runCommandAndAssertNoStatUpdates("hash", (k) -> jedis.expireAt(k, 2145916800));
}
@Test
public void testPExpire() {
runCommandAndAssertNoStatUpdates("hash", (k) -> jedis.pexpire(k, 1024));
}
@Test
public void testPExpireAt() {
runCommandAndAssertNoStatUpdates("hash", (k) -> jedis.pexpireAt(k, 1608247597));
}
@Test
public void testPersist() {
runCommandAndAssertNoStatUpdates("hash", (k) -> jedis.persist(k));
}
// ------------ Helper Methods -----------
private void runCommandAndAssertHitsAndMisses(String key, Consumer<String> command) {
Map<String, String> info = getInfo(jedis);
Long currentHits = Long.parseLong(info.get(HITS));
Long currentMisses = Long.parseLong(info.get(MISSES));
command.accept(key);
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 1));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses));
command.accept("missed");
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 1));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses + 1));
}
private void runCommandAndAssertHitsAndMisses(String key, BiConsumer<String, String> command) {
Map<String, String> info = getInfo(jedis);
Long currentHits = Long.parseLong(info.get(HITS));
Long currentMisses = Long.parseLong(info.get(MISSES));
command.accept(key, "42");
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 1));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses));
command.accept("missed", "42");
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 1));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses + 1));
}
private void runDiffCommandAndAssertHitsAndMisses(String key,
BiConsumer<String, String> command) {
Map<String, String> info = getInfo(jedis);
Long currentHits = Long.parseLong(info.get(HITS));
Long currentMisses = Long.parseLong(info.get(MISSES));
command.accept(key, key);
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 2));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses));
command.accept(key, "missed");
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(String.valueOf(currentHits + 3));
assertThat(info.get(MISSES)).isEqualTo(String.valueOf(currentMisses + 1));
}
/**
* When storing diff-ish results, hits and misses are never updated
*/
private void runDiffStoreCommandAndAssertNoStatUpdates(String key,
TriConsumer<String, String, String> command) {
Map<String, String> info = getInfo(jedis);
String currentHits = info.get(HITS);
String currentMisses = info.get(MISSES);
command.accept("destination", key, key);
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(currentHits);
assertThat(info.get(MISSES)).isEqualTo(currentMisses);
command.accept("destination", key, "missed");
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(currentHits);
assertThat(info.get(MISSES)).isEqualTo(currentMisses);
}
private void runCommandAndAssertNoStatUpdates(String key, Consumer<String> command) {
Map<String, String> info = getInfo(jedis);
String currentHits = info.get(HITS);
String currentMisses = info.get(MISSES);
command.accept(key);
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(currentHits);
assertThat(info.get(MISSES)).isEqualTo(currentMisses);
}
private void runCommandAndAssertNoStatUpdates(String key, BiConsumer<String, String> command) {
Map<String, String> info = getInfo(jedis);
String currentHits = info.get(HITS);
String currentMisses = info.get(MISSES);
command.accept(key, "42");
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(currentHits);
assertThat(info.get(MISSES)).isEqualTo(currentMisses);
}
private void runCommandAndAssertNoStatUpdates(String key,
TriConsumer<String, String, String> command) {
Map<String, String> info = getInfo(jedis);
String currentHits = info.get(HITS);
String currentMisses = info.get(MISSES);
command.accept(key, key, "42");
info = getInfo(jedis);
assertThat(info.get(HITS)).isEqualTo(currentHits);
assertThat(info.get(MISSES)).isEqualTo(currentMisses);
}
/**
* Convert the values returned by the INFO command into a basic param:value map.
*/
static Map<String, String> getInfo(Jedis jedis) {
Map<String, String> results = new HashMap<>();
String rawInfo = jedis.info();
for (String line : rawInfo.split("\r\n")) {
int colonIndex = line.indexOf(":");
if (colonIndex > 0) {
String key = line.substring(0, colonIndex);
String value = line.substring(colonIndex + 1);
results.put(key, value);
}
}
return results;
}
}