Repository: fppt/jedis-mock
Pull request #35 (2019-04-03 19:43:17)

Fix ConcurrentModificationException when deleting hashes with more than one fields
In ExpiringKeyValueStore first removes all ttls and than clears the row when `delete(Slice key)` is called to avoid ConcurrentModificationException for rows with more than one column (i.e. hashes with more than one field)


Patch:
diff --git a/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java b/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java
index c8be499..bb1adfa 100644
--- a/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java
+++ b/src/main/java/com/github/fppt/jedismock/storage/ExpiringKeyValueStorage.java
@@ -22,8 +22,10 @@ public abstract class ExpiringKeyValueStorage {
     }
 
     public void delete(Slice key) {
-        for (Slice key2 : values().row(key).keySet())
-            delete(key, key2);
+        for (Slice key2 : values().row(key).keySet()) {
+            ttls().remove(key, key2);
+        }
+        values().row(key).clear();
     }
 
     public void delete(Slice key1, Slice key2){


Test patch:
diff --git a/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java b/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java
index 32b0337..aa1b765 100644
--- a/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java
+++ b/src/test/java/com/github/fppt/jedismock/TestRedisOperationExecutor.java
@@ -29,7 +29,7 @@ public class TestRedisOperationExecutor {
         return "$" + param.length() + CRLF + param.toString() + CRLF;
     }
 
-    private static String array(CharSequence ...params) {
+    private static String array(CharSequence... params) {
         StringBuilder builder = new StringBuilder();
         builder.append('*').append(params.length).append(CRLF);
         for (CharSequence param : params) {
@@ -42,6 +42,10 @@ public class TestRedisOperationExecutor {
         return builder.toString();
     }
 
+    private static String nullArray() {
+        return "*1" + CRLF + "$-1" + CRLF;
+    }
+
     private void assertCommandEquals(String expect, String command) throws ParseErrorException, EOFException {
         assertEquals(bulkString(expect), executor.execCommand(RedisCommandParser.parse(command)).toString());
     }
@@ -50,6 +54,10 @@ public class TestRedisOperationExecutor {
         assertEquals(Response.integer(expect), executor.execCommand(RedisCommandParser.parse(command)));
     }
 
+    private void assertCommandArrayEquals(String expectedArray, String command) throws ParseErrorException, EOFException {
+        assertEquals(expectedArray, executor.execCommand(RedisCommandParser.parse(command)).toString());
+    }
+
     private void assertCommandNull(String command) throws ParseErrorException, EOFException {
         assertEquals(Response.NULL, executor.execCommand(RedisCommandParser.parse(command)));
     }
@@ -249,6 +257,20 @@ public class TestRedisOperationExecutor {
         assertCommandEquals("abc", array("getSET", "a", "abd"));
     }
 
+    @Test
+    public void testHmsetAndHmget() throws EOFException {
+        assertCommandOK(array("hmset", "h", "a", "v1", "b", "v2"));
+        assertCommandArrayEquals(array("v1"), array("hmget", "h", "a"));
+        assertCommandArrayEquals(array("v2"), array("hmget", "h", "b"));
+    }
+
+    @Test
+    public void testDelHash() throws EOFException {
+        assertCommandOK(array("hmset","h", "a", "v1", "b", "v2"));
+        assertCommandEquals(1, array("del", "h"));
+        assertCommandArrayEquals(nullArray(), array("hmget", "h", "a"));
+    }
+
     @Test
     public void testStrlen() throws ParseErrorException, EOFException {
         assertCommandEquals(0, array("strlen", "a"));