DonalEvans commented on a change in pull request #7392:
URL: https://github.com/apache/geode/pull/7392#discussion_r819155865



##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/AbstractLInsertIntegrationTest.java
##########
@@ -0,0 +1,245 @@
+/*
+ * 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.commands.executor.list;
+
+import static 
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertExactNumberOfArgs;
+import static 
org.apache.geode.redis.internal.RedisConstants.WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static redis.clients.jedis.args.ListPosition.AFTER;
+import static redis.clients.jedis.args.ListPosition.BEFORE;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.Protocol;
+import redis.clients.jedis.exceptions.JedisDataException;
+
+import org.apache.geode.redis.ConcurrentLoopingThreads;
+import org.apache.geode.redis.RedisIntegrationTest;
+import org.apache.geode.redis.internal.RedisConstants;
+
+public abstract class AbstractLInsertIntegrationTest implements 
RedisIntegrationTest {
+  public static final String KEY = "key";
+  public static final String initialValue = "initialValue";
+  public static final String insertedValue = "insertedValue";
+  private JedisCluster jedis;
+
+  @Before
+  public void setUp() {
+    jedis = new JedisCluster(new HostAndPort(BIND_ADDRESS, getPort()), 
REDIS_CLIENT_TIMEOUT);
+  }
+
+  @After
+  public void tearDown() {
+    flushAll();
+    jedis.close();
+  }
+
+  @Test
+  public void linsertErrors_givenWrongNumberOfArguments() {
+    assertExactNumberOfArgs(jedis, Protocol.Command.LINSERT, 4);
+  }
+
+  @Test
+  public void linsert_onKeyThatDoesNotExist_doesNotCreateKey() {
+    assertThat(jedis.linsert(KEY, BEFORE, "not in here", 
insertedValue)).isZero();
+    assertThat(jedis.exists(KEY)).isFalse();
+  }
+
+  @Test
+  public void 
linsert_onKeyThatDoesNotExist_withInvalidBefore_errorsBecauseOfWrongNumOfArgs() 
{
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.LINSERT, 
KEY, "LINSERT",
+        "notBefore", "doesn't matter", insertedValue))
+            .isInstanceOf(JedisDataException.class)
+            .hasMessage(
+                String.format(WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND, 
"linsert"));
+  }

Review comment:
       This test is accidentally passing the wrong number of arguments, hence 
the `WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND` error. It should be:
   ```
     @Test
     public void linsert_onKeyThatDoesNotExist_withInvalidBefore_errors() {
       assertThatThrownBy(() -> jedis.sendCommand(KEY, 
Protocol.Command.LINSERT, KEY, "notBefore",
           "doesn't matter", insertedValue))
               .hasMessage(ERROR_SYNTAX);
     }
   ```

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/AbstractLInsertIntegrationTest.java
##########
@@ -0,0 +1,245 @@
+/*
+ * 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.commands.executor.list;
+
+import static 
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertExactNumberOfArgs;
+import static 
org.apache.geode.redis.internal.RedisConstants.WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static redis.clients.jedis.args.ListPosition.AFTER;
+import static redis.clients.jedis.args.ListPosition.BEFORE;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.Protocol;
+import redis.clients.jedis.exceptions.JedisDataException;
+
+import org.apache.geode.redis.ConcurrentLoopingThreads;
+import org.apache.geode.redis.RedisIntegrationTest;
+import org.apache.geode.redis.internal.RedisConstants;
+
+public abstract class AbstractLInsertIntegrationTest implements 
RedisIntegrationTest {
+  public static final String KEY = "key";
+  public static final String initialValue = "initialValue";
+  public static final String insertedValue = "insertedValue";
+  private JedisCluster jedis;
+
+  @Before
+  public void setUp() {
+    jedis = new JedisCluster(new HostAndPort(BIND_ADDRESS, getPort()), 
REDIS_CLIENT_TIMEOUT);
+  }
+
+  @After
+  public void tearDown() {
+    flushAll();
+    jedis.close();
+  }
+
+  @Test
+  public void linsertErrors_givenWrongNumberOfArguments() {
+    assertExactNumberOfArgs(jedis, Protocol.Command.LINSERT, 4);
+  }
+
+  @Test
+  public void linsert_onKeyThatDoesNotExist_doesNotCreateKey() {
+    assertThat(jedis.linsert(KEY, BEFORE, "not in here", 
insertedValue)).isZero();
+    assertThat(jedis.exists(KEY)).isFalse();
+  }
+
+  @Test
+  public void 
linsert_onKeyThatDoesNotExist_withInvalidBefore_errorsBecauseOfWrongNumOfArgs() 
{
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.LINSERT, 
KEY, "LINSERT",
+        "notBefore", "doesn't matter", insertedValue))
+            .isInstanceOf(JedisDataException.class)
+            .hasMessage(
+                String.format(WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND, 
"linsert"));
+  }
+
+  @Test
+  public void linsert_onKeyThatIsNotAList_Errors() {
+    jedis.sadd(KEY, initialValue);
+
+    assertThatThrownBy(() -> jedis.linsert(KEY, BEFORE, initialValue, 
insertedValue))
+        .isInstanceOf(JedisDataException.class)
+        .hasMessage(RedisConstants.ERROR_WRONG_TYPE);
+  }
+
+  @Test
+  public void linsert_withNonexistentPivot_returnsNegativeOne() {
+    jedis.lpush(KEY, initialValue);
+
+    assertThat(jedis.linsert(KEY, BEFORE, "nope", 
insertedValue)).isEqualTo(-1);
+
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue);
+    assertThat(jedis.lpop(KEY)).isNull();
+  }
+
+  @Test
+  public void linsert_withInvalidBEFORE_errors() {
+    jedis.lpush(KEY, initialValue);
+
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.LINSERT, 
KEY, "LINSERT",
+        "notBefore", initialValue, insertedValue))
+            .isInstanceOf(JedisDataException.class)
+            .hasMessage(
+                String.format(WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND, 
"linsert"));
+  }
+
+  @Test
+  public void 
linsert_BEFORE_onKeyWithMultipleValues_withValidPivot_insertsValue() {
+    jedis.lpush(KEY, initialValue + "4");
+    jedis.lpush(KEY, initialValue + "3");
+    jedis.lpush(KEY, initialValue + "2");
+    jedis.lpush(KEY, initialValue + "1");
+    jedis.lpush(KEY, initialValue + "0");
+
+    assertThat(jedis.linsert(KEY, BEFORE, initialValue + "2", 
insertedValue)).isEqualTo(6L);
+
+    assertThat(jedis.llen(KEY)).isEqualTo(6L);
+
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "0");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "1");
+    assertThat(jedis.lpop(KEY)).isEqualTo(insertedValue);
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "2");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "3");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "4");
+  }
+
+  @Test
+  public void 
linsert_BEFORE_onKeyWithMultipleDuplicateValues_withValidPivot_insertsValue() {
+    jedis.lpush(KEY, initialValue + "3");
+    jedis.lpush(KEY, initialValue + "2");
+    jedis.lpush(KEY, initialValue + "1");
+    jedis.lpush(KEY, initialValue + "3");
+    jedis.lpush(KEY, initialValue + "0");
+
+    assertThat(jedis.linsert(KEY, BEFORE, initialValue + "3", 
insertedValue)).isEqualTo(6L);
+
+    assertThat(jedis.llen(KEY)).isEqualTo(6L);
+
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "0");
+    assertThat(jedis.lpop(KEY)).isEqualTo(insertedValue);
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "3");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "1");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "2");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "3");
+  }
+
+  @Test
+  public void 
linsert_AFTER_onKeyWithMultipleDuplicateValues_withValidPivot_insertsValue() {
+    jedis.lpush(KEY, initialValue + "3");
+    jedis.lpush(KEY, initialValue + "2");
+    jedis.lpush(KEY, initialValue + "1");
+    jedis.lpush(KEY, initialValue + "3");
+    jedis.lpush(KEY, initialValue + "0");
+
+    assertThat(jedis.linsert(KEY, AFTER, initialValue + "3", 
insertedValue)).isEqualTo(6L);
+
+    assertThat(jedis.llen(KEY)).isEqualTo(6L);
+
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "0");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "3");
+    assertThat(jedis.lpop(KEY)).isEqualTo(insertedValue);
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "1");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "2");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "3");
+  }
+
+  @Test
+  public void 
linsert_AFTER_onKeyWithMultipleValues_withValidPivot_insertsValue() {
+    jedis.lpush(KEY, initialValue + "4");
+    jedis.lpush(KEY, initialValue + "3");
+    jedis.lpush(KEY, initialValue + "2");
+    jedis.lpush(KEY, initialValue + "1");
+    jedis.lpush(KEY, initialValue + "0");
+
+    assertThat(jedis.linsert(KEY, AFTER, initialValue + "2", 
insertedValue)).isEqualTo(6L);
+
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "0");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "1");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "2");
+    assertThat(jedis.lpop(KEY)).isEqualTo(insertedValue);
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "3");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "4");
+  }
+
+  @Test
+  public void linsert_BEFORE_firstElement() {
+    jedis.lpush(KEY, initialValue + "4");
+    jedis.lpush(KEY, initialValue + "3");
+    jedis.lpush(KEY, initialValue + "2");
+    jedis.lpush(KEY, initialValue + "1");
+    jedis.lpush(KEY, initialValue + "0");
+
+    assertThat(jedis.linsert(KEY, BEFORE, initialValue + "0", 
insertedValue)).isEqualTo(6L);
+
+    assertThat(jedis.lpop(KEY)).isEqualTo(insertedValue);
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "0");
+  }
+
+  @Test
+  public void linsert_AFTER_lastElement() {
+    jedis.lpush(KEY, initialValue + "4");
+    jedis.lpush(KEY, initialValue + "3");
+    jedis.lpush(KEY, initialValue + "2");
+    jedis.lpush(KEY, initialValue + "1");
+    jedis.lpush(KEY, initialValue + "0");
+
+    assertThat(jedis.linsert(KEY, AFTER, initialValue + "4", 
insertedValue)).isEqualTo(6L);
+
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "0");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "1");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "2");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "3");
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue + "4");
+    assertThat(jedis.lpop(KEY)).isEqualTo(insertedValue);
+    assertThat(jedis.lpop(KEY)).isNull();
+  }
+
+  @Test
+  public void testConcurrentLInserts() {
+    // we should never see "snake", "inserted value", "snake", etc.
+    String[] initialElements = {"lizard", "lizard", "lizard", "snake", 
"lizard", "lizard"};
+    String[] elementsToPush = {"snake", "snake", "snake", "snake", "snake", 
"snake"};
+
+    jedis.lpush(KEY, initialElements);
+
+    new ConcurrentLoopingThreads(1000,
+        i -> jedis.lpush(KEY, elementsToPush),
+        i -> jedis.linsert(KEY, BEFORE, "snake", 
insertedValue)).runWithAction(() -> {
+          assertThat(jedis.llen(KEY)).isEqualTo(initialElements.length + 
elementsToPush.length + 1);
+
+          assertThat(jedis).satisfiesAnyOf(
+              // LINSERT happened first
+              jedisClient -> {
+                assertThat(jedisClient.lindex(KEY, 8))
+                    .as("failure 1")
+                    .isEqualTo(insertedValue);
+                assertThat(jedisClient.lindex(KEY, 0)).isEqualTo("snake");
+              },
+              // LPUSH happened first
+              jedisClient -> assertThat(jedisClient.lindex(KEY, 0))
+                  .as("failure 1").isEqualTo(insertedValue));

Review comment:
       These failure messages could do with being a little more descriptive.

##########
File path: 
geode-for-redis/src/distributedTest/java/org/apache/geode/redis/internal/commands/executor/list/LInsertDUnitTest.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.commands.executor.list;
+
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static redis.clients.jedis.args.ListPosition.AFTER;
+import static redis.clients.jedis.args.ListPosition.BEFORE;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.args.ListPosition;
+
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.dunit.rules.RedisClusterStartupRule;
+import org.apache.geode.test.junit.rules.ExecutorServiceRule;
+
+public class LInsertDUnitTest {
+  public static final int INITIAL_LIST_SIZE = 500;
+
+  @Rule
+  public RedisClusterStartupRule clusterStartUp = new 
RedisClusterStartupRule();
+
+  @Rule
+  public ExecutorServiceRule executor = new ExecutorServiceRule();
+
+  private static JedisCluster jedis;
+  private static final String insertedValue = "insertedValue";
+
+  @Before
+  public void testSetup() {
+    MemberVM locator = clusterStartUp.startLocatorVM(0);
+    clusterStartUp.startRedisVM(1, locator.getPort());
+    clusterStartUp.startRedisVM(2, locator.getPort());
+    clusterStartUp.startRedisVM(3, locator.getPort());
+    int redisServerPort = clusterStartUp.getRedisPort(1);
+    jedis = new JedisCluster(new HostAndPort(BIND_ADDRESS, redisServerPort), 
REDIS_CLIENT_TIMEOUT);
+    clusterStartUp.flushAll();
+  }
+
+  @After
+  public void tearDown() {
+    jedis.close();
+  }
+
+  @Test
+  public void shouldDistributeDataAmongCluster_andRetainDataAfterServerCrash() 
{
+    String key = makeListKeyWithHashtag(1, 
clusterStartUp.getKeyOnServer("linsert", 1));
+    List<String> elementList = makeElementList(key, INITIAL_LIST_SIZE);
+    lpushPerformAndVerify(key, elementList);
+
+    assertThat(jedis.linsert(key, BEFORE, jedis.lindex(key, 2), insertedValue))
+        .isEqualTo(elementList.size() + 1);

Review comment:
       Could this test also do an LINSERT using AFTER to make sure that the 
delta propagation for that is working correctly too, just in case?

##########
File path: 
geode-for-redis/src/distributedTest/java/org/apache/geode/redis/internal/commands/executor/list/LInsertDUnitTest.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.commands.executor.list;
+
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static redis.clients.jedis.args.ListPosition.AFTER;
+import static redis.clients.jedis.args.ListPosition.BEFORE;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.args.ListPosition;
+
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.dunit.rules.RedisClusterStartupRule;
+import org.apache.geode.test.junit.rules.ExecutorServiceRule;
+
+public class LInsertDUnitTest {
+  public static final int INITIAL_LIST_SIZE = 500;
+
+  @Rule
+  public RedisClusterStartupRule clusterStartUp = new 
RedisClusterStartupRule();
+
+  @Rule
+  public ExecutorServiceRule executor = new ExecutorServiceRule();
+
+  private static JedisCluster jedis;
+  private static final String insertedValue = "insertedValue";
+
+  @Before
+  public void testSetup() {
+    MemberVM locator = clusterStartUp.startLocatorVM(0);
+    clusterStartUp.startRedisVM(1, locator.getPort());
+    clusterStartUp.startRedisVM(2, locator.getPort());
+    clusterStartUp.startRedisVM(3, locator.getPort());
+    int redisServerPort = clusterStartUp.getRedisPort(1);
+    jedis = new JedisCluster(new HostAndPort(BIND_ADDRESS, redisServerPort), 
REDIS_CLIENT_TIMEOUT);
+    clusterStartUp.flushAll();
+  }
+
+  @After
+  public void tearDown() {
+    jedis.close();
+  }
+
+  @Test
+  public void shouldDistributeDataAmongCluster_andRetainDataAfterServerCrash() 
{
+    String key = makeListKeyWithHashtag(1, 
clusterStartUp.getKeyOnServer("linsert", 1));
+    List<String> elementList = makeElementList(key, INITIAL_LIST_SIZE);
+    lpushPerformAndVerify(key, elementList);
+
+    assertThat(jedis.linsert(key, BEFORE, jedis.lindex(key, 2), insertedValue))
+        .isEqualTo(elementList.size() + 1);
+
+    assertThat(jedis.lindex(key, 2)).isEqualTo(insertedValue);
+
+    clusterStartUp.crashVM(1); // kill primary
+
+    assertThat(jedis.llen(key)).isEqualTo(elementList.size() + 1);
+    assertThat(jedis.lindex(key, 2)).isEqualTo(insertedValue);
+    assertThat(jedis.lindex(key, 
3)).isEqualTo(elementList.get(INITIAL_LIST_SIZE - 3));
+  }
+
+  @Test
+  public void givenBucketsMoveDuringLInsert_operationsAreNotLost() throws 
Exception {
+    AtomicLong numInserts = new AtomicLong(0);
+    List<String> listHashtags = makeListHashtags();
+    List<String> keys = makeListKeys(listHashtags);
+
+    List<String> elementList1 = makeElementList(keys.get(0), 
INITIAL_LIST_SIZE);
+    List<String> elementList2 = makeElementList(keys.get(1), 
INITIAL_LIST_SIZE);
+    List<String> elementList3 = makeElementList(keys.get(2), 
INITIAL_LIST_SIZE);
+
+    lpushPerformAndVerify(keys.get(0), elementList1);
+    lpushPerformAndVerify(keys.get(1), elementList2);
+    lpushPerformAndVerify(keys.get(2), elementList3);
+
+    Runnable task1 = () -> linsertPerformAndVerify(keys.get(0), BEFORE,
+        jedis.lindex(keys.get(0), 2), insertedValue, numInserts);
+    Runnable task2 = () -> linsertPerformAndVerify(keys.get(1), AFTER,
+        jedis.lindex(keys.get(1), 2), insertedValue, numInserts);
+    Runnable task3 = () -> linsertPerformAndVerify(keys.get(2), AFTER,
+        jedis.lindex(keys.get(2), 2), insertedValue, numInserts);
+
+    Future<Void> future1 = executor.runAsync(task1);
+    Future<Void> future2 = executor.runAsync(task2);
+    Future<Void> future3 = executor.runAsync(task3);
+
+    for (int i = 0; i < 50 && numInserts.get() > 0; i++) {
+      clusterStartUp.moveBucketForKey(listHashtags.get(i % 
listHashtags.size()));
+      Thread.sleep(500);
+    }
+
+    numInserts.set(0);
+
+    future1.get();
+    future2.get();
+    future3.get();
+  }
+
+  private List<String> makeListHashtags() {
+    List<String> listHashtags = new ArrayList<>();
+    listHashtags.add(clusterStartUp.getKeyOnServer("lpop", 1));
+    listHashtags.add(clusterStartUp.getKeyOnServer("lpop", 2));
+    listHashtags.add(clusterStartUp.getKeyOnServer("lpop", 3));
+    return listHashtags;
+  }
+
+  private List<String> makeListKeys(List<String> listHashtags) {
+    List<String> keys = new ArrayList<>();
+    keys.add(makeListKeyWithHashtag(1, listHashtags.get(0)));
+    keys.add(makeListKeyWithHashtag(2, listHashtags.get(1)));
+    keys.add(makeListKeyWithHashtag(3, listHashtags.get(2)));
+    return keys;
+  }
+
+  private void linsertPerformAndVerify(String key, ListPosition pos, String 
pivot, String value,
+      AtomicLong runningCount) {
+    long startLength = jedis.llen(key);
+    assertThat(jedis.linsert(key, pos, pivot, value)).isEqualTo(startLength + 
1);
+    runningCount.incrementAndGet();

Review comment:
       This running count needs to be incremented immediately before we start 
calling LINSERT and then either decremented again once we've done some maximum 
number of repeats or set to 0 if one of the threads doing the LINSERT hits an 
exception. The way the `lpopPerformAndVerify()` method in `LPopDUnitTest` 
handles this repeating/stopping behaviour is a pretty good guide.

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/AbstractLInsertIntegrationTest.java
##########
@@ -0,0 +1,245 @@
+/*
+ * 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.commands.executor.list;
+
+import static 
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertExactNumberOfArgs;
+import static 
org.apache.geode.redis.internal.RedisConstants.WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static redis.clients.jedis.args.ListPosition.AFTER;
+import static redis.clients.jedis.args.ListPosition.BEFORE;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.Protocol;
+import redis.clients.jedis.exceptions.JedisDataException;
+
+import org.apache.geode.redis.ConcurrentLoopingThreads;
+import org.apache.geode.redis.RedisIntegrationTest;
+import org.apache.geode.redis.internal.RedisConstants;
+
+public abstract class AbstractLInsertIntegrationTest implements 
RedisIntegrationTest {
+  public static final String KEY = "key";
+  public static final String initialValue = "initialValue";
+  public static final String insertedValue = "insertedValue";
+  private JedisCluster jedis;
+
+  @Before
+  public void setUp() {
+    jedis = new JedisCluster(new HostAndPort(BIND_ADDRESS, getPort()), 
REDIS_CLIENT_TIMEOUT);
+  }
+
+  @After
+  public void tearDown() {
+    flushAll();
+    jedis.close();
+  }
+
+  @Test
+  public void linsertErrors_givenWrongNumberOfArguments() {
+    assertExactNumberOfArgs(jedis, Protocol.Command.LINSERT, 4);
+  }
+
+  @Test
+  public void linsert_onKeyThatDoesNotExist_doesNotCreateKey() {
+    assertThat(jedis.linsert(KEY, BEFORE, "not in here", 
insertedValue)).isZero();
+    assertThat(jedis.exists(KEY)).isFalse();
+  }
+
+  @Test
+  public void 
linsert_onKeyThatDoesNotExist_withInvalidBefore_errorsBecauseOfWrongNumOfArgs() 
{
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.LINSERT, 
KEY, "LINSERT",
+        "notBefore", "doesn't matter", insertedValue))
+            .isInstanceOf(JedisDataException.class)
+            .hasMessage(
+                String.format(WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND, 
"linsert"));
+  }
+
+  @Test
+  public void linsert_onKeyThatIsNotAList_Errors() {
+    jedis.sadd(KEY, initialValue);
+
+    assertThatThrownBy(() -> jedis.linsert(KEY, BEFORE, initialValue, 
insertedValue))
+        .isInstanceOf(JedisDataException.class)

Review comment:
       We shouldn't be asserting on the exception type returned here, or 
anywhere else in this class, because that's behaviour controlled by Jedis, not 
geode. Just asserting that the message is correct is enough.

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/AbstractLInsertIntegrationTest.java
##########
@@ -0,0 +1,245 @@
+/*
+ * 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.commands.executor.list;
+
+import static 
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertExactNumberOfArgs;
+import static 
org.apache.geode.redis.internal.RedisConstants.WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static redis.clients.jedis.args.ListPosition.AFTER;
+import static redis.clients.jedis.args.ListPosition.BEFORE;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.Protocol;
+import redis.clients.jedis.exceptions.JedisDataException;
+
+import org.apache.geode.redis.ConcurrentLoopingThreads;
+import org.apache.geode.redis.RedisIntegrationTest;
+import org.apache.geode.redis.internal.RedisConstants;
+
+public abstract class AbstractLInsertIntegrationTest implements 
RedisIntegrationTest {
+  public static final String KEY = "key";
+  public static final String initialValue = "initialValue";
+  public static final String insertedValue = "insertedValue";
+  private JedisCluster jedis;
+
+  @Before
+  public void setUp() {
+    jedis = new JedisCluster(new HostAndPort(BIND_ADDRESS, getPort()), 
REDIS_CLIENT_TIMEOUT);
+  }
+
+  @After
+  public void tearDown() {
+    flushAll();
+    jedis.close();
+  }
+
+  @Test
+  public void linsertErrors_givenWrongNumberOfArguments() {
+    assertExactNumberOfArgs(jedis, Protocol.Command.LINSERT, 4);
+  }
+
+  @Test
+  public void linsert_onKeyThatDoesNotExist_doesNotCreateKey() {
+    assertThat(jedis.linsert(KEY, BEFORE, "not in here", 
insertedValue)).isZero();
+    assertThat(jedis.exists(KEY)).isFalse();
+  }
+
+  @Test
+  public void 
linsert_onKeyThatDoesNotExist_withInvalidBefore_errorsBecauseOfWrongNumOfArgs() 
{
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.LINSERT, 
KEY, "LINSERT",
+        "notBefore", "doesn't matter", insertedValue))
+            .isInstanceOf(JedisDataException.class)
+            .hasMessage(
+                String.format(WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND, 
"linsert"));
+  }
+
+  @Test
+  public void linsert_onKeyThatIsNotAList_Errors() {
+    jedis.sadd(KEY, initialValue);
+
+    assertThatThrownBy(() -> jedis.linsert(KEY, BEFORE, initialValue, 
insertedValue))
+        .isInstanceOf(JedisDataException.class)
+        .hasMessage(RedisConstants.ERROR_WRONG_TYPE);
+  }
+
+  @Test
+  public void linsert_withNonexistentPivot_returnsNegativeOne() {
+    jedis.lpush(KEY, initialValue);
+
+    assertThat(jedis.linsert(KEY, BEFORE, "nope", 
insertedValue)).isEqualTo(-1);
+
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue);
+    assertThat(jedis.lpop(KEY)).isNull();
+  }
+
+  @Test
+  public void linsert_withInvalidBEFORE_errors() {
+    jedis.lpush(KEY, initialValue);
+
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.LINSERT, 
KEY, "LINSERT",
+        "notBefore", initialValue, insertedValue))
+            .isInstanceOf(JedisDataException.class)
+            .hasMessage(
+                String.format(WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND, 
"linsert"));
+  }
+
+  @Test
+  public void 
linsert_BEFORE_onKeyWithMultipleValues_withValidPivot_insertsValue() {
+    jedis.lpush(KEY, initialValue + "4");
+    jedis.lpush(KEY, initialValue + "3");
+    jedis.lpush(KEY, initialValue + "2");
+    jedis.lpush(KEY, initialValue + "1");
+    jedis.lpush(KEY, initialValue + "0");

Review comment:
       This (and other instances of multiple successive calls to `lpush()`) 
could be simplified to:
   ```
   jedis.lpush(KEY, "4", "3", "2", "1", "0");
   ```
   We also don't need to be prefixing each element with `initialValue`.

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/list/AbstractLInsertIntegrationTest.java
##########
@@ -0,0 +1,245 @@
+/*
+ * 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.commands.executor.list;
+
+import static 
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertExactNumberOfArgs;
+import static 
org.apache.geode.redis.internal.RedisConstants.WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static redis.clients.jedis.args.ListPosition.AFTER;
+import static redis.clients.jedis.args.ListPosition.BEFORE;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.Protocol;
+import redis.clients.jedis.exceptions.JedisDataException;
+
+import org.apache.geode.redis.ConcurrentLoopingThreads;
+import org.apache.geode.redis.RedisIntegrationTest;
+import org.apache.geode.redis.internal.RedisConstants;
+
+public abstract class AbstractLInsertIntegrationTest implements 
RedisIntegrationTest {
+  public static final String KEY = "key";
+  public static final String initialValue = "initialValue";
+  public static final String insertedValue = "insertedValue";
+  private JedisCluster jedis;
+
+  @Before
+  public void setUp() {
+    jedis = new JedisCluster(new HostAndPort(BIND_ADDRESS, getPort()), 
REDIS_CLIENT_TIMEOUT);
+  }
+
+  @After
+  public void tearDown() {
+    flushAll();
+    jedis.close();
+  }
+
+  @Test
+  public void linsertErrors_givenWrongNumberOfArguments() {
+    assertExactNumberOfArgs(jedis, Protocol.Command.LINSERT, 4);
+  }
+
+  @Test
+  public void linsert_onKeyThatDoesNotExist_doesNotCreateKey() {
+    assertThat(jedis.linsert(KEY, BEFORE, "not in here", 
insertedValue)).isZero();
+    assertThat(jedis.exists(KEY)).isFalse();
+  }
+
+  @Test
+  public void 
linsert_onKeyThatDoesNotExist_withInvalidBefore_errorsBecauseOfWrongNumOfArgs() 
{
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.LINSERT, 
KEY, "LINSERT",
+        "notBefore", "doesn't matter", insertedValue))
+            .isInstanceOf(JedisDataException.class)
+            .hasMessage(
+                String.format(WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND, 
"linsert"));
+  }
+
+  @Test
+  public void linsert_onKeyThatIsNotAList_Errors() {
+    jedis.sadd(KEY, initialValue);
+
+    assertThatThrownBy(() -> jedis.linsert(KEY, BEFORE, initialValue, 
insertedValue))
+        .isInstanceOf(JedisDataException.class)
+        .hasMessage(RedisConstants.ERROR_WRONG_TYPE);
+  }
+
+  @Test
+  public void linsert_withNonexistentPivot_returnsNegativeOne() {
+    jedis.lpush(KEY, initialValue);
+
+    assertThat(jedis.linsert(KEY, BEFORE, "nope", 
insertedValue)).isEqualTo(-1);
+
+    assertThat(jedis.lpop(KEY)).isEqualTo(initialValue);
+    assertThat(jedis.lpop(KEY)).isNull();
+  }
+
+  @Test
+  public void linsert_withInvalidBEFORE_errors() {
+    jedis.lpush(KEY, initialValue);
+
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.LINSERT, 
KEY, "LINSERT",
+        "notBefore", initialValue, insertedValue))
+            .isInstanceOf(JedisDataException.class)
+            .hasMessage(
+                String.format(WRONG_NUMBER_OF_ARGUMENTS_FOR_COMMAND, 
"linsert"));
+  }

Review comment:
       This test is also using the wrong number of arguments for the 
`sendCommand()` method. It should be:
   ```
     @Test
     public void linsert_withInvalidBEFORE_errors() {
       jedis.lpush(KEY, initialValue);
   
       assertThatThrownBy(() -> jedis.sendCommand(KEY, 
Protocol.Command.LINSERT, KEY, "notBefore",
           initialValue, insertedValue))
               .hasMessage(ERROR_SYNTAX);
     }
   ```

##########
File path: 
geode-for-redis/src/distributedTest/java/org/apache/geode/redis/internal/commands/executor/list/LInsertDUnitTest.java
##########
@@ -0,0 +1,182 @@
+/*
+ * 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.commands.executor.list;
+
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.BIND_ADDRESS;
+import static 
org.apache.geode.test.dunit.rules.RedisClusterStartupRule.REDIS_CLIENT_TIMEOUT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static redis.clients.jedis.args.ListPosition.AFTER;
+import static redis.clients.jedis.args.ListPosition.BEFORE;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.args.ListPosition;
+
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.dunit.rules.RedisClusterStartupRule;
+import org.apache.geode.test.junit.rules.ExecutorServiceRule;
+
+public class LInsertDUnitTest {
+  public static final int INITIAL_LIST_SIZE = 500;
+
+  @Rule
+  public RedisClusterStartupRule clusterStartUp = new 
RedisClusterStartupRule();
+
+  @Rule
+  public ExecutorServiceRule executor = new ExecutorServiceRule();
+
+  private static JedisCluster jedis;
+  private static final String insertedValue = "insertedValue";
+
+  @Before
+  public void testSetup() {
+    MemberVM locator = clusterStartUp.startLocatorVM(0);
+    clusterStartUp.startRedisVM(1, locator.getPort());
+    clusterStartUp.startRedisVM(2, locator.getPort());
+    clusterStartUp.startRedisVM(3, locator.getPort());
+    int redisServerPort = clusterStartUp.getRedisPort(1);
+    jedis = new JedisCluster(new HostAndPort(BIND_ADDRESS, redisServerPort), 
REDIS_CLIENT_TIMEOUT);
+    clusterStartUp.flushAll();
+  }
+
+  @After
+  public void tearDown() {
+    jedis.close();
+  }
+
+  @Test
+  public void shouldDistributeDataAmongCluster_andRetainDataAfterServerCrash() 
{
+    String key = makeListKeyWithHashtag(1, 
clusterStartUp.getKeyOnServer("linsert", 1));
+    List<String> elementList = makeElementList(key, INITIAL_LIST_SIZE);
+    lpushPerformAndVerify(key, elementList);
+
+    assertThat(jedis.linsert(key, BEFORE, jedis.lindex(key, 2), insertedValue))
+        .isEqualTo(elementList.size() + 1);
+
+    assertThat(jedis.lindex(key, 2)).isEqualTo(insertedValue);
+
+    clusterStartUp.crashVM(1); // kill primary
+
+    assertThat(jedis.llen(key)).isEqualTo(elementList.size() + 1);
+    assertThat(jedis.lindex(key, 2)).isEqualTo(insertedValue);
+    assertThat(jedis.lindex(key, 
3)).isEqualTo(elementList.get(INITIAL_LIST_SIZE - 3));
+  }
+
+  @Test
+  public void givenBucketsMoveDuringLInsert_operationsAreNotLost() throws 
Exception {

Review comment:
       This test is currently only doing 1 single LINSERT for each key. In 
order to test behaviour during bucket moves effectively, it needs to do 
repeated LINSERT calls until we've done some reasonable number of bucket moves 
(about 20 should be good).




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscr...@geode.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to