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");
+  }
+}

Reply via email to