This is an automated email from the ASF dual-hosted git repository.
dschneider pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/develop by this push:
new 90a3d50 GEODE-7979: Implement tests for Redis PERSIST (#4941)
90a3d50 is described below
commit 90a3d50998308150c355422a22fc5d192c009ea2
Author: Sarah Abbey <[email protected]>
AuthorDate: Tue Apr 14 16:31:54 2020 -0400
GEODE-7979: Implement tests for Redis PERSIST (#4941)
The Redis PERSIST command removes the existing timeout on a key
(https://redis.io/commands/persist). The PERSIST command was previously
implemented, but not tested. This commit adds tests for this command.
---
.../geode/redis/ExpireAtDockerAcceptanceTest.java | 8 +
.../geode/redis/ExpireDockerAcceptanceTest.java | 8 +
...eTest.java => PersistDockerAcceptanceTest.java} | 35 ++--
.../geode/redis/PexpireDockerAcceptanceTest.java | 8 +
.../geode/redis/RenameDockerAcceptanceTest.java | 8 +
.../geode/redis/executors/PersistDUnitTest.java | 142 ++++++++++++++
.../geode/redis/general/ExpireIntegrationTest.java | 13 --
.../redis/general/PersistIntegrationTest.java | 216 +++++++++++++++++++++
.../redis/internal/executor/PersistExecutor.java | 2 +-
.../executor/general/PersistExecutorJUnitTest.java | 91 +++++++++
10 files changed, 493 insertions(+), 38 deletions(-)
diff --git
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/ExpireAtDockerAcceptanceTest.java
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/ExpireAtDockerAcceptanceTest.java
index 017239d..32e2785 100644
---
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/ExpireAtDockerAcceptanceTest.java
+++
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/ExpireAtDockerAcceptanceTest.java
@@ -17,16 +17,24 @@ package org.apache.geode.redis;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.experimental.categories.Category;
+import org.junit.rules.TestRule;
import org.testcontainers.containers.GenericContainer;
import redis.clients.jedis.Jedis;
import org.apache.geode.redis.general.ExpireAtIntegrationTest;
import org.apache.geode.test.junit.categories.RedisTest;
+import org.apache.geode.test.junit.rules.IgnoreOnWindowsRule;
@Category({RedisTest.class})
public class ExpireAtDockerAcceptanceTest extends ExpireAtIntegrationTest {
+ // Docker compose does not work on windows in CI. Ignore this test on windows
+ // Using a RuleChain to make sure we ignore the test before the rule comes
into play
+ @ClassRule
+ public static TestRule ignoreOnWindowsRule = new IgnoreOnWindowsRule();
+
@BeforeClass
public static void setUp() {
GenericContainer redisContainer = new
GenericContainer<>("redis:5.0.6").withExposedPorts(6379);
diff --git
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/ExpireDockerAcceptanceTest.java
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/ExpireDockerAcceptanceTest.java
index 06edafb..03982b8 100644
---
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/ExpireDockerAcceptanceTest.java
+++
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/ExpireDockerAcceptanceTest.java
@@ -17,18 +17,26 @@ package org.apache.geode.redis;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.experimental.categories.Category;
+import org.junit.rules.TestRule;
import org.testcontainers.containers.GenericContainer;
import redis.clients.jedis.Jedis;
import org.apache.geode.redis.general.ExpireIntegrationTest;
import org.apache.geode.test.junit.categories.RedisTest;
+import org.apache.geode.test.junit.rules.IgnoreOnWindowsRule;
@Category({RedisTest.class})
public class ExpireDockerAcceptanceTest extends ExpireIntegrationTest {
private static GenericContainer redisContainer;
+ // Docker compose does not work on windows in CI. Ignore this test on windows
+ // Using a RuleChain to make sure we ignore the test before the rule comes
into play
+ @ClassRule
+ public static TestRule ignoreOnWindowsRule = new IgnoreOnWindowsRule();
+
@BeforeClass
public static void setUp() {
redisContainer = new
GenericContainer<>("redis:5.0.6").withExposedPorts(6379);
diff --git
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/RenameDockerAcceptanceTest.java
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/PersistDockerAcceptanceTest.java
similarity index 70%
copy from
geode-redis/src/acceptanceTest/java/org/apache/geode/redis/RenameDockerAcceptanceTest.java
copy to
geode-redis/src/acceptanceTest/java/org/apache/geode/redis/PersistDockerAcceptanceTest.java
index afbea15..7b4121e 100644
---
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/RenameDockerAcceptanceTest.java
+++
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/PersistDockerAcceptanceTest.java
@@ -15,30 +15,34 @@
package org.apache.geode.redis;
-
-import java.util.Random;
-
import org.junit.AfterClass;
import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.ClassRule;
import org.junit.experimental.categories.Category;
+import org.junit.rules.TestRule;
import org.testcontainers.containers.GenericContainer;
import redis.clients.jedis.Jedis;
+import org.apache.geode.redis.general.PersistIntegrationTest;
import org.apache.geode.test.junit.categories.RedisTest;
+import org.apache.geode.test.junit.rules.IgnoreOnWindowsRule;
@Category({RedisTest.class})
-public class RenameDockerAcceptanceTest extends RenameIntegrationTest {
+public class PersistDockerAcceptanceTest extends PersistIntegrationTest {
private static GenericContainer redisContainer;
+ // Docker compose does not work on windows in CI. Ignore this test on windows
+ // Using a RuleChain to make sure we ignore the test before the rule comes
into play
+ @ClassRule
+ public static TestRule ignoreOnWindowsRule = new IgnoreOnWindowsRule();
+
@BeforeClass
public static void setUp() {
- rand = new Random();
redisContainer = new
GenericContainer<>("redis:5.0.6").withExposedPorts(6379);
redisContainer.start();
jedis = new Jedis("localhost", redisContainer.getFirstMappedPort(),
REDIS_CLIENT_TIMEOUT);
+ jedis2 = new Jedis("localhost", redisContainer.getFirstMappedPort(),
REDIS_CLIENT_TIMEOUT);
}
@AfterClass
@@ -46,21 +50,4 @@ public class RenameDockerAcceptanceTest extends
RenameIntegrationTest {
jedis.close();
}
- public int getPort() {
- return redisContainer.getFirstMappedPort();
- }
-
- @Test
- public void testSortedSet() {
- // TODO: GEODE-7910 Update RENAME command in Geode Redis to match native
Redis
- }
-
- @Test
- public void testList() {
- // TODO: GEODE-7910 Update RENAME command in Geode Redis to match native
Redis
- }
-
- @Test
- @Ignore("Test only applies to Geode Redis, ignored for native Redis")
- public void testProtectedString() {}
}
diff --git
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/PexpireDockerAcceptanceTest.java
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/PexpireDockerAcceptanceTest.java
index cdf5fa6..9c605aa 100644
---
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/PexpireDockerAcceptanceTest.java
+++
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/PexpireDockerAcceptanceTest.java
@@ -17,18 +17,26 @@ package org.apache.geode.redis;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.experimental.categories.Category;
+import org.junit.rules.TestRule;
import org.testcontainers.containers.GenericContainer;
import redis.clients.jedis.Jedis;
import org.apache.geode.redis.general.PexpireIntegrationTest;
import org.apache.geode.test.junit.categories.RedisTest;
+import org.apache.geode.test.junit.rules.IgnoreOnWindowsRule;
@Category({RedisTest.class})
public class PexpireDockerAcceptanceTest extends PexpireIntegrationTest {
private static GenericContainer redisContainer;
+ // Docker compose does not work on windows in CI. Ignore this test on windows
+ // Using a RuleChain to make sure we ignore the test before the rule comes
into play
+ @ClassRule
+ public static TestRule ignoreOnWindowsRule = new IgnoreOnWindowsRule();
+
@BeforeClass
public static void setUp() {
redisContainer = new
GenericContainer<>("redis:5.0.6").withExposedPorts(6379);
diff --git
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/RenameDockerAcceptanceTest.java
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/RenameDockerAcceptanceTest.java
index afbea15..40df38c 100644
---
a/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/RenameDockerAcceptanceTest.java
+++
b/geode-redis/src/acceptanceTest/java/org/apache/geode/redis/RenameDockerAcceptanceTest.java
@@ -20,19 +20,27 @@ import java.util.Random;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
+import org.junit.rules.TestRule;
import org.testcontainers.containers.GenericContainer;
import redis.clients.jedis.Jedis;
import org.apache.geode.test.junit.categories.RedisTest;
+import org.apache.geode.test.junit.rules.IgnoreOnWindowsRule;
@Category({RedisTest.class})
public class RenameDockerAcceptanceTest extends RenameIntegrationTest {
private static GenericContainer redisContainer;
+ // Docker compose does not work on windows in CI. Ignore this test on windows
+ // Using a RuleChain to make sure we ignore the test before the rule comes
into play
+ @ClassRule
+ public static TestRule ignoreOnWindowsRule = new IgnoreOnWindowsRule();
+
@BeforeClass
public static void setUp() {
rand = new Random();
diff --git
a/geode-redis/src/distributedTest/java/org/apache/geode/redis/executors/PersistDUnitTest.java
b/geode-redis/src/distributedTest/java/org/apache/geode/redis/executors/PersistDUnitTest.java
new file mode 100644
index 0000000..fb086df
--- /dev/null
+++
b/geode-redis/src/distributedTest/java/org/apache/geode/redis/executors/PersistDUnitTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.executors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.Serializable;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.params.SetParams;
+
+import org.apache.geode.internal.AvailablePortHelper;
+import org.apache.geode.test.awaitility.GeodeAwaitility;
+import org.apache.geode.test.dunit.AsyncInvocation;
+import org.apache.geode.test.dunit.SerializableCallable;
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.categories.RedisTest;
+
+@Category({RedisTest.class})
+public class PersistDUnitTest implements Serializable {
+
+ @ClassRule
+ public static ClusterStartupRule cluster = new ClusterStartupRule(5);
+
+ private static String LOCALHOST = "localhost";
+
+ public static final String KEY = "key";
+ private static VM client1;
+ private static VM client2;
+
+ private static int server1Port;
+ private static int server2Port;
+
+ private static final int JEDIS_TIMEOUT =
+ Math.toIntExact(GeodeAwaitility.getTimeout().toMillis());
+
+ private abstract static class ClientTestBase extends SerializableCallable {
+ int port;
+
+ protected ClientTestBase(int port) {
+ this.port = port;
+ }
+ }
+
+ @BeforeClass
+ public static void setup() {
+ final int[] ports = AvailablePortHelper.getRandomAvailableTCPPorts(2);
+ server1Port = ports[0];
+ server2Port = ports[1];
+
+ MemberVM locator = cluster.startLocatorVM(0);
+
+ Properties redisProps = new Properties();
+ redisProps.setProperty("redis-bind-address", LOCALHOST);
+ redisProps.setProperty("redis-port", Integer.toString(ports[0]));
+ redisProps.setProperty("log-level", "warn");
+ cluster.startServerVM(1, redisProps, locator.getPort());
+
+ redisProps.setProperty("redis-port", Integer.toString(ports[1]));
+ cluster.startServerVM(2, redisProps, locator.getPort());
+
+ client1 = cluster.getVM(3);
+ client2 = cluster.getVM(4);
+ }
+
+ class ConcurrentPersistOperation extends ClientTestBase {
+
+ private final String keyBaseName;
+ private AtomicLong persistedCount;
+ private Long iterationCount;
+
+ protected ConcurrentPersistOperation(int port, String keyBaseName, Long
iterationCount) {
+ super(port);
+ this.keyBaseName = keyBaseName;
+ this.persistedCount = new AtomicLong(0);
+ this.iterationCount = iterationCount;
+ }
+
+ @Override
+ public AtomicLong call() {
+ Jedis jedis = new Jedis(LOCALHOST, port, JEDIS_TIMEOUT);
+
+ for (int i = 0; i < this.iterationCount; i++) {
+ String key = this.keyBaseName + i;
+ this.persistedCount.addAndGet(jedis.persist(key));
+ }
+
+ return this.persistedCount;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testConcurrentPersistOperations() throws InterruptedException {
+ Long iterationCount = 5000L;
+ Jedis jedis = new Jedis(LOCALHOST, server1Port, JEDIS_TIMEOUT);
+ setKeysWithExpiration(jedis, iterationCount, "key");
+
+ AsyncInvocation<AtomicLong> remotePersistInvocationClient1 =
+ (AsyncInvocation<AtomicLong>) client1
+ .invokeAsync(new ConcurrentPersistOperation(server1Port, "key",
iterationCount));
+ AtomicLong remotePersistInvocationClient2 =
+ (AtomicLong) client2.invoke("remotePersistInvocation2",
+ new ConcurrentPersistOperation(server2Port, "key",
iterationCount));
+
+ remotePersistInvocationClient1.await();
+
+ // Sum of persisted counts returned from both clients should equal total
number of keys set
+ // (iteration count)
+ assertThat(remotePersistInvocationClient2.get() +
remotePersistInvocationClient1.get().get())
+ .isEqualTo(iterationCount);
+ }
+
+ private void setKeysWithExpiration(Jedis jedis, Long iterationCount, String
key) {
+ for (int i = 0; i < iterationCount; i++) {
+ SetParams setParams = new SetParams();
+ setParams.ex(600);
+
+ jedis.set(key + i, "value" + i, setParams);
+ }
+ }
+}
diff --git
a/geode-redis/src/integrationTest/java/org/apache/geode/redis/general/ExpireIntegrationTest.java
b/geode-redis/src/integrationTest/java/org/apache/geode/redis/general/ExpireIntegrationTest.java
index 79c47b6..9c43339 100644
---
a/geode-redis/src/integrationTest/java/org/apache/geode/redis/general/ExpireIntegrationTest.java
+++
b/geode-redis/src/integrationTest/java/org/apache/geode/redis/general/ExpireIntegrationTest.java
@@ -437,19 +437,6 @@ public class ExpireIntegrationTest {
}
@Test
- public void PERSISTCommand_ShouldClearExpirationTimeForGivenKey() {
- String key = "key";
- String value = "value";
- jedis.set(key, value);
- jedis.expire(key, 20);
-
- jedis.persist(key);
-
- Long timeToLive = jedis.ttl(key);
- assertThat(timeToLive).isEqualTo(-1);
- }
-
- @Test
@Ignore("this test needs to pass to have feature parity with native redis")
public void SettingExiprationToNegativeValue_ShouldDeleteKey() {
diff --git
a/geode-redis/src/integrationTest/java/org/apache/geode/redis/general/PersistIntegrationTest.java
b/geode-redis/src/integrationTest/java/org/apache/geode/redis/general/PersistIntegrationTest.java
new file mode 100644
index 0000000..ccbc1e4
--- /dev/null
+++
b/geode-redis/src/integrationTest/java/org/apache/geode/redis/general/PersistIntegrationTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.general;
+
+import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_LEVEL;
+import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.params.SetParams;
+
+import org.apache.geode.cache.CacheFactory;
+import org.apache.geode.cache.GemFireCache;
+import org.apache.geode.internal.AvailablePortHelper;
+import org.apache.geode.redis.GeodeRedisServer;
+import org.apache.geode.test.awaitility.GeodeAwaitility;
+
+public class PersistIntegrationTest {
+
+ public static Jedis jedis;
+ public static Jedis jedis2;
+ public static int REDIS_CLIENT_TIMEOUT =
+ Math.toIntExact(GeodeAwaitility.getTimeout().toMillis());;
+ private static GeodeRedisServer server;
+ private static GemFireCache cache;
+
+ @BeforeClass
+ public static void setUp() {
+ CacheFactory cf = new CacheFactory();
+ cf.set(LOG_LEVEL, "error");
+ cf.set(MCAST_PORT, "0");
+ cf.set(LOCATORS, "");
+ cache = cf.create();
+ int port = AvailablePortHelper.getRandomAvailableTCPPort();
+ server = new GeodeRedisServer("localhost", port);
+
+ server.start();
+ jedis = new Jedis("localhost", port, REDIS_CLIENT_TIMEOUT);
+ jedis2 = new Jedis("localhost", port, REDIS_CLIENT_TIMEOUT);
+ }
+
+ @After
+ public void flushAll() {
+ jedis.flushAll();
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ jedis.close();
+ cache.close();
+ server.shutdown();
+ }
+
+ @Test
+ public void shouldPersistKey_givenKeyWith_stringValue() {
+ String stringKey = "stringKey";
+ String stringValue = "stringValue";
+ jedis.set(stringKey, stringValue);
+ jedis.expire(stringKey, 20);
+
+ assertThat(jedis.persist(stringKey)).isEqualTo(1L);
+ assertThat(jedis.ttl(stringKey)).isEqualTo(-1L);
+ }
+
+ @Test
+ public void shouldReturnZero_givenKeyDoesNotExist() {
+ assertThat(jedis.persist("doesNotExist")).isEqualTo(0L);
+ }
+
+ @Test
+ public void shouldPersistKey_givenKeyWith_setValue() {
+ String setKey = "setKey";
+ String setMember = "setValue";
+
+ jedis.sadd(setKey, setMember);
+ jedis.expire(setKey, 20);
+
+ assertThat(jedis.persist(setKey)).isEqualTo(1L);
+ assertThat(jedis.ttl(setKey)).isEqualTo(-1L);
+ }
+
+ @Test
+ public void shouldPersistKey_givenKeyWith_sortedSetValue() {
+ String sortedSetKey = "sortedSetKey";
+ double score = 2.0;
+ String sortedSetMember = "sortedSetMember";
+
+ jedis.zadd(sortedSetKey, score, sortedSetMember);
+ jedis.expire(sortedSetKey, 20);
+
+ assertThat(jedis.persist(sortedSetKey)).isEqualTo(1L);
+ assertThat(jedis.ttl(sortedSetKey)).isEqualTo(-1L);
+ }
+
+ @Test
+ public void shouldPersistKey_givenKeyWith_hashValue() {
+ String hashKey = "hashKey";
+ String hashField = "hashField";
+ String hashValue = "hashValue";
+
+ jedis.hset(hashKey, hashField, hashValue);
+ jedis.expire(hashKey, 20);
+
+ assertThat(jedis.persist(hashKey)).isEqualTo(1L);
+ assertThat(jedis.ttl(hashKey)).isEqualTo(-1L);
+ }
+
+ @Test
+ public void shouldPersistKey_givenKeyWith_geoValue() {
+ String geoKey = "sicily";
+ double latitude = 13.361389;
+ double longitude = 38.115556;
+ String geoMember = "Palermo Catina";
+
+ jedis.geoadd(geoKey, latitude, longitude, geoMember);
+ jedis.expire(geoKey, 20);
+
+ assertThat(jedis.persist(geoKey)).isEqualTo(1L);
+ assertThat(jedis.ttl(geoKey)).isEqualTo(-1L);
+ }
+
+ @Test
+ public void shouldPersistKey_givenKeyWith_hyperLogLogValue() {
+ String hyperLogLogKey = "crawled:127.0.0.2";
+ String hyperLogLogValue = "www.insideTheHouse.com";
+
+ jedis.pfadd(hyperLogLogKey, hyperLogLogValue);
+ jedis.expire(hyperLogLogKey, 20);
+
+ assertThat(jedis.persist(hyperLogLogKey)).isEqualTo(1L);
+ assertThat(jedis.ttl(hyperLogLogKey)).isEqualTo(-1L);
+ }
+
+ @Test
+ public void shouldPersistKey_givenKeyWith_listValue() {
+ String listKey = "listKey";
+ String listValue = "listValue";
+
+ jedis.lpush(listKey, listValue);
+ jedis.expire(listKey, 20);
+
+ assertThat(jedis.persist(listKey)).isEqualTo(1L);
+ assertThat(jedis.ttl(listKey)).isEqualTo(-1L);
+ }
+
+ @Test
+ public void shouldPersistKey_givenKeyWith_bitMapValue() {
+ String bitMapKey = "bitMapKey";
+ long offset = 1L;
+ String bitMapValue = "0";
+
+ jedis.setbit(bitMapKey, offset, bitMapValue);
+ jedis.expire(bitMapKey, 20);
+
+ assertThat(jedis.persist(bitMapKey)).isEqualTo(1L);
+ assertThat(jedis.ttl(bitMapKey)).isEqualTo(-1L);
+ }
+
+ @Test
+ public void shouldPersistKeysConcurrently() throws InterruptedException {
+ int iterationCount = 5000;
+ setKeysWithExpiration(jedis, iterationCount);
+
+ AtomicLong persistedFromThread1 = new AtomicLong(0);
+ AtomicLong persistedFromThread2 = new AtomicLong(0);
+
+ Runnable runnable1 = () -> persistKeys(persistedFromThread1, jedis,
iterationCount);
+ Runnable runnable2 = () -> persistKeys(persistedFromThread2, jedis2,
iterationCount);
+
+ Thread thread1 = new Thread(runnable1);
+ Thread thread2 = new Thread(runnable2);
+
+ thread1.start();
+ thread2.start();
+ thread1.join();
+ thread2.join();
+
+ assertThat(persistedFromThread1.get() +
persistedFromThread2.get()).isEqualTo(iterationCount);
+ }
+
+ private void setKeysWithExpiration(Jedis jedis, int iterationCount) {
+ for (int i = 0; i < iterationCount; i++) {
+ SetParams setParams = new SetParams();
+ setParams.ex(600);
+
+ jedis.set("key" + i, "value" + i, setParams);
+ }
+ }
+
+ private void persistKeys(AtomicLong atomicLong, Jedis jedis, int
iterationCount) {
+ for (int i = 0; i < iterationCount; i++) {
+ String key = "key" + i;
+ atomicLong.addAndGet(jedis.persist(key));
+ }
+ }
+}
diff --git
a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/PersistExecutor.java
b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/PersistExecutor.java
index cc0fe77..75892dc 100755
---
a/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/PersistExecutor.java
+++
b/geode-redis/src/main/java/org/apache/geode/redis/internal/executor/PersistExecutor.java
@@ -32,7 +32,7 @@ public class PersistExecutor extends AbstractExecutor {
public void executeCommand(Command command, ExecutionHandlerContext context)
{
List<byte[]> commandElems = command.getProcessedCommand();
- if (commandElems.size() < 2) {
+ if (commandElems.size() != 2) {
command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(),
ArityDef.PERSIST));
return;
}
diff --git
a/geode-redis/src/test/java/org/apache/geode/redis/internal/executor/general/PersistExecutorJUnitTest.java
b/geode-redis/src/test/java/org/apache/geode/redis/internal/executor/general/PersistExecutorJUnitTest.java
new file mode 100644
index 0000000..ab4675b
--- /dev/null
+++
b/geode-redis/src/test/java/org/apache/geode/redis/internal/executor/general/PersistExecutorJUnitTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.general;
+
+import static java.nio.charset.Charset.defaultCharset;
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import org.apache.geode.redis.internal.Command;
+import org.apache.geode.redis.internal.ExecutionHandlerContext;
+import org.apache.geode.redis.internal.Executor;
+import org.apache.geode.redis.internal.executor.PersistExecutor;
+
+public class PersistExecutorJUnitTest {
+
+ private ExecutionHandlerContext context;
+ private Command command;
+ private UnpooledByteBufAllocator byteBuf;
+
+ @Before
+ public void setUp() {
+ context = mock(ExecutionHandlerContext.class);
+ command = mock(Command.class);
+ byteBuf = new UnpooledByteBufAllocator(false);
+ }
+
+ @Test
+ public void calledWithTooFewCommandArguments_returnsError() {
+ Executor executor = new PersistExecutor();
+ List<byte[]> commandsAsBytesWithTooFewArguments = new ArrayList<>();
+ commandsAsBytesWithTooFewArguments.add("PERSIST".getBytes());
+
+ ArgumentCaptor<ByteBuf> argsErrorCaptor =
ArgumentCaptor.forClass(ByteBuf.class);
+
+ when(context.getByteBufAllocator()).thenReturn(byteBuf);
+
when(command.getProcessedCommand()).thenReturn(commandsAsBytesWithTooFewArguments);
+
+ executor.executeCommand(command, context);
+ verify(command, times(1)).setResponse(argsErrorCaptor.capture());
+
+ List<ByteBuf> capturedErrors = argsErrorCaptor.getAllValues();
+ assertThat(capturedErrors.get(0).toString(defaultCharset()))
+ .startsWith("-ERR The wrong number of arguments");
+ }
+
+ @Test
+ public void calledWithTooManyCommandArguments_returnsErrorMessage() {
+ Executor executor = new PersistExecutor();
+ List<byte[]> commandsAsBytesWithTooManyArguments = new ArrayList<>();
+ commandsAsBytesWithTooManyArguments.add("PERSIST".getBytes());
+ commandsAsBytesWithTooManyArguments.add("key".getBytes());
+ commandsAsBytesWithTooManyArguments.add("Bonus!".getBytes());
+
+ ArgumentCaptor<ByteBuf> argsErrorCaptor =
ArgumentCaptor.forClass(ByteBuf.class);
+
+ when(context.getByteBufAllocator()).thenReturn(byteBuf);
+
when(command.getProcessedCommand()).thenReturn(commandsAsBytesWithTooManyArguments);
+
+ executor.executeCommand(command, context);
+ verify(command, times(1)).setResponse(argsErrorCaptor.capture());
+
+ List<ByteBuf> capturedErrors = argsErrorCaptor.getAllValues();
+ assertThat(capturedErrors.get(0).toString(defaultCharset()))
+ .startsWith("-ERR The wrong number of arguments");
+ }
+}