Repository: fppt/jedis-mock
Pull request #239 (2022-06-09 21:29:22)

store string as byte array
fix https://github.com/fppt/jedis-mock/issues/233


Patch:
diff --git a/pom.xml b/pom.xml
index ad82e3d..38f3af4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -98,6 +98,12 @@
             <artifactId>mockito-core</artifactId>
             <version>4.6.1</version>
             <scope>test</scope>
+        </dependency>
+		<dependency>
+            <groupId>nl.jqno.equalsverifier</groupId>
+            <artifactId>equalsverifier</artifactId>
+            <version>3.10</version>
+            <scope>test</scope>
         </dependency>
         <!-- SLF4J is the logging API all modules should use -->
         <dependency>
diff --git a/src/main/java/com/github/fppt/jedismock/commands/RedisCommand.java b/src/main/java/com/github/fppt/jedismock/commands/RedisCommand.java
index 20ae6cb..07f56bd 100644
--- a/src/main/java/com/github/fppt/jedismock/commands/RedisCommand.java
+++ b/src/main/java/com/github/fppt/jedismock/commands/RedisCommand.java
@@ -4,9 +4,10 @@ import com.github.fppt.jedismock.datastructures.Slice;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
-public class RedisCommand {
+public final class RedisCommand {
 
     private final List<Slice> parameters;
 
@@ -23,22 +24,15 @@ public class RedisCommand {
 
     @Override
     public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (o instanceof RedisCommand) {
-            RedisCommand that = (RedisCommand) o;
-            return (this.parameters.equals(that.parameters()));
-        }
-        return false;
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        RedisCommand that = (RedisCommand) o;
+        return parameters.equals(that.parameters);
     }
 
     @Override
     public int hashCode() {
-        int h$ = 1;
-        h$ *= 1000003;
-        h$ ^= parameters.hashCode();
-        return h$;
+        return Objects.hash(parameters);
     }
 
     @Override
diff --git a/src/main/java/com/github/fppt/jedismock/datastructures/RMString.java b/src/main/java/com/github/fppt/jedismock/datastructures/RMString.java
index c7a09a0..3ec2dd5 100644
--- a/src/main/java/com/github/fppt/jedismock/datastructures/RMString.java
+++ b/src/main/java/com/github/fppt/jedismock/datastructures/RMString.java
@@ -3,33 +3,45 @@ package com.github.fppt.jedismock.datastructures;
 import com.github.fppt.jedismock.exception.WrongValueTypeException;
 
 import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 
 public class RMString implements RMDataStructure, Serializable {
     private static final long serialVersionUID = 1L;
-    private String storedData;
+    private byte[] storedData;
 
     public RMString() {
-        storedData = "";
+        storedData = new byte[0];
     }
 
-    public RMString(String data) {
+    public RMString(byte[] data) {
         storedData = data;
     }
 
-    public String getStoredData() {
-        return storedData;
+    public byte[] getStoredData() {
+        return Arrays.copyOf(storedData, storedData.length);
     }
 
-    public static RMString create(String str) {
+    public String getStoredDataAsString() {
+        return new String(storedData, StandardCharsets.UTF_8);
+    }
+
+    public static RMString create(byte[] str) {
         return new RMString(str);
     }
 
-    public void add(String str) {
-        storedData = storedData + str;
+    public static RMString create(String str) {
+        return new RMString(str.getBytes(StandardCharsets.UTF_8));
+    }
+
+    public void add(byte[] str) {
+        int oldLength = storedData.length;
+        storedData = Arrays.copyOf(storedData, oldLength + str.length);
+        System.arraycopy(str, 0, storedData, oldLength, str.length);
     }
 
     public int size() {
-        return storedData.length();
+        return storedData.length;
     }
 
     @Override
diff --git a/src/main/java/com/github/fppt/jedismock/datastructures/RMZSet.java b/src/main/java/com/github/fppt/jedismock/datastructures/RMZSet.java
index 03117a8..66d574e 100644
--- a/src/main/java/com/github/fppt/jedismock/datastructures/RMZSet.java
+++ b/src/main/java/com/github/fppt/jedismock/datastructures/RMZSet.java
@@ -12,10 +12,10 @@ import static java.util.Collections.unmodifiableNavigableSet;
 
 
 public class RMZSet implements RMDataStructure {
-    private final Map<String, Double> scores = new HashMap<>();
+    private final Map<Slice, Double> scores = new HashMap<>();
     private final NavigableSet<ZSetEntry> entries = new TreeSet<>();
 
-    public Double put(String value, double score) {
+    public Double put(Slice value, double score) {
         Double previous = scores.put(value, score);
         if (previous != null) {
             entries.remove(new ZSetEntry(previous, value));
@@ -24,11 +24,11 @@ public class RMZSet implements RMDataStructure {
         return previous;
     }
 
-    public Double getScore(String value) {
+    public Double getScore(Slice value) {
         return scores.get(value);
     }
 
-    public boolean remove(String value) {
+    public boolean remove(Slice value) {
         final Double previous = scores.remove(value);
         if (previous == null) {
             return false;
diff --git a/src/main/java/com/github/fppt/jedismock/datastructures/Slice.java b/src/main/java/com/github/fppt/jedismock/datastructures/Slice.java
index 0dc3ac6..9b4481f 100644
--- a/src/main/java/com/github/fppt/jedismock/datastructures/Slice.java
+++ b/src/main/java/com/github/fppt/jedismock/datastructures/Slice.java
@@ -8,10 +8,12 @@ import java.io.ObjectInputStream;
 import java.io.Serializable;
 import java.util.Arrays;
 
-public class Slice implements Comparable<Slice>, Serializable {
-    private static final long serialVersionUID= 1L;
+public final class Slice implements Comparable<Slice>, Serializable {
+    private static final long serialVersionUID = 1L;
+    private static final Slice emptySlice = new Slice(new byte[0]);
     private final byte[] storedData;
 
+
     private Slice(byte[] storedData) {
         if (storedData == null) {
             throw new NullPointerException("Null data");
@@ -19,19 +21,23 @@ public class Slice implements Comparable<Slice>, Serializable {
         this.storedData = storedData;
     }
 
-    public static Slice create(byte[] data){
+    public static Slice create(byte[] data) {
         return new Slice(data);
     }
 
-    public static Slice create(String data){
+    public static Slice create(String data) {
         return create(data.getBytes().clone());
     }
 
+    public static Slice empty() {
+        return emptySlice;
+    }
+
     public byte[] data() {
         return Arrays.copyOf(storedData, storedData.length);
     }
 
-    public int length(){
+    public int length() {
         return data().length;
     }
 
@@ -82,6 +88,6 @@ public class Slice implements Comparable<Slice>, Serializable {
             }
         }
 
-        return RMString.create(this.toString());
+        return RMString.create(this.data());
     }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/datastructures/ZSetEntry.java b/src/main/java/com/github/fppt/jedismock/datastructures/ZSetEntry.java
index cb7938a..95e2863 100644
--- a/src/main/java/com/github/fppt/jedismock/datastructures/ZSetEntry.java
+++ b/src/main/java/com/github/fppt/jedismock/datastructures/ZSetEntry.java
@@ -2,18 +2,18 @@ package com.github.fppt.jedismock.datastructures;
 
 import java.util.Objects;
 
-public class ZSetEntry implements Comparable<ZSetEntry> {
+public final class ZSetEntry implements Comparable<ZSetEntry> {
     public final static Double MIN_SCORE = Double.NEGATIVE_INFINITY;
     public final static Double MAX_SCORE = Double.POSITIVE_INFINITY;
-    public final static String MIN_VALUE = "";
+    public final static Slice MIN_VALUE = Slice.empty();
     //magic string that is considered to be lexicographically greater than any other string
-    public final static String MAX_VALUE = "2815bbb7-cacd-4ce1-ba76-11fee7937b0e";
+    public final static Slice MAX_VALUE = Slice.create("2815bbb7-cacd-4ce1-ba76-11fee7937b0e");
 
     private final double score;
 
-    private final String value;
+    private final Slice value;
 
-    ZSetEntry(double score, String value) {
+    ZSetEntry(double score, Slice value) {
         Objects.requireNonNull(value);
         this.score = score;
         this.value = value;
@@ -23,7 +23,7 @@ public class ZSetEntry implements Comparable<ZSetEntry> {
         return score;
     }
 
-    public String getValue() {
+    public Slice getValue() {
         return value;
     }
 
diff --git a/src/main/java/com/github/fppt/jedismock/datastructures/ZSetEntryBound.java b/src/main/java/com/github/fppt/jedismock/datastructures/ZSetEntryBound.java
index d634160..c7f4423 100644
--- a/src/main/java/com/github/fppt/jedismock/datastructures/ZSetEntryBound.java
+++ b/src/main/java/com/github/fppt/jedismock/datastructures/ZSetEntryBound.java
@@ -4,9 +4,9 @@ import java.util.Objects;
 
 public class ZSetEntryBound {
     public static final ZSetEntryBound MINUS_INF =
-            new ZSetEntryBound(new ZSetEntry(ZSetEntry.MIN_SCORE, ""), false);
+            new ZSetEntryBound(new ZSetEntry(ZSetEntry.MIN_SCORE, Slice.empty()), false);
     public static final ZSetEntryBound PLUS_INF =
-            new ZSetEntryBound(new ZSetEntry(ZSetEntry.MAX_SCORE, ""), false);
+            new ZSetEntryBound(new ZSetEntry(ZSetEntry.MAX_SCORE, Slice.empty()), false);
 
     private final ZSetEntry bound;
     private final boolean inclusive;
@@ -17,7 +17,7 @@ public class ZSetEntryBound {
         this.inclusive = inclusive;
     }
 
-    public ZSetEntryBound(double score, String value, boolean inclusive) {
+    public ZSetEntryBound(double score, Slice value, boolean inclusive) {
         this.bound = new ZSetEntry(score, value);
         this.inclusive = inclusive;
     }
diff --git a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/AbstractByScoreOperation.java b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/AbstractByScoreOperation.java
index df6c524..7a01047 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/AbstractByScoreOperation.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/AbstractByScoreOperation.java
@@ -79,16 +79,15 @@ public abstract class AbstractByScoreOperation extends AbstractRedisOperation {
             }
         }
 
-        Stream<String> result;
+        Stream<Slice> result;
         if (withScores) {
             result = entries
                     .flatMap(e -> Stream.of(e.getValue(),
-                            Double.toString(e.getScore())));
+                            Slice.create(Double.toString(e.getScore()))));
         } else {
             result = entries.map(ZSetEntry::getValue);
         }
         final List<Slice> list = result
-                .map(Slice::create)
                 .map(Response::bulkString)
                 .collect(Collectors.toList());
 
diff --git a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZAdd.java b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZAdd.java
index dcccf22..9b1e2b3 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZAdd.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZAdd.java
@@ -30,7 +30,7 @@ class ZAdd extends AbstractRedisOperation {
             // Score must be a double. Will throw an exception if it's not.
             double s = convertToDouble(score.toString());
 
-            Double prevScore = mapDBObj.put(value.toString(), s);
+            Double prevScore = mapDBObj.put(value, s);
             if (prevScore == null) count++;
         }
         base().putValue(key, mapDBObj);
diff --git a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRange.java b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRange.java
index 866a7da..557e424 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRange.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRange.java
@@ -63,9 +63,8 @@ class ZRange extends AbstractByScoreOperation {
                 .skip(start)
                 .limit(end - start + 1)
                 .flatMap(e -> finalWithScores
-                        ? Stream.of(e.getValue(), Double.toString(e.getScore()))
+                        ? Stream.of(e.getValue(), Slice.create(Double.toString(e.getScore())))
                         : Stream.of(e.getValue()))
-                .map(Slice::create)
                 .map(Response::bulkString)
                 .collect(Collectors.toList());
 
diff --git a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRangeByLex.java b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRangeByLex.java
index 9116cf1..b151e65 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRangeByLex.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRangeByLex.java
@@ -71,7 +71,6 @@ class ZRangeByLex extends AbstractRedisOperation {
                 buildEndEntryBound(score, end))
                 .stream()
                 .map(ZSetEntry::getValue)
-                .map(Slice::create)
                 .map(Response::bulkString)
                 .collect(Collectors.toList());
     }
@@ -80,7 +79,7 @@ class ZRangeByLex extends AbstractRedisOperation {
         if (NEGATIVELY_INFINITE.equals(start)) {
             return new ZSetEntryBound(score, ZSetEntry.MIN_VALUE, true);
         } else {
-            return new ZSetEntryBound(score, start.substring(1), start.startsWith(INCLUSIVE_PREFIX));
+            return new ZSetEntryBound(score, Slice.create(start.substring(1)), start.startsWith(INCLUSIVE_PREFIX));
         }
     }
 
@@ -88,7 +87,7 @@ class ZRangeByLex extends AbstractRedisOperation {
         if (POSITIVELY_INFINITE.equals(end)) {
             return new ZSetEntryBound(score, ZSetEntry.MAX_VALUE, false);
         } else {
-            return new ZSetEntryBound(score, end.substring(1), end.startsWith(INCLUSIVE_PREFIX));
+            return new ZSetEntryBound(score, Slice.create(end.substring(1)), end.startsWith(INCLUSIVE_PREFIX));
         }
     }
 
diff --git a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRem.java b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRem.java
index b8bcf34..317e20f 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRem.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZRem.java
@@ -24,7 +24,7 @@ class ZRem extends AbstractRedisOperation {
         }
         int count = 0;
         for (int i = 1; i < params().size(); i++) {
-            if (mapDBObj.remove(params().get(i).toString())) {
+            if (mapDBObj.remove(params().get(i))) {
                 count++;
             }
         }
diff --git a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZScore.java b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZScore.java
index b14e309..88b859e 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZScore.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/sortedsets/ZScore.java
@@ -26,7 +26,7 @@ class ZScore extends AbstractRedisOperation {
 
         final RMZSet mapDBObj = getZSetFromBaseOrCreateEmpty(key);
 
-        Double score = mapDBObj.getScore(val.toString());
+        Double score = mapDBObj.getScore(val);
 
         return score == null ? Response.NULL : Response.bulkString(Slice.create(score.toString()));
     }
diff --git a/src/main/java/com/github/fppt/jedismock/operations/strings/Append.java b/src/main/java/com/github/fppt/jedismock/operations/strings/Append.java
index 816f7e4..3ee6b88 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/strings/Append.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/strings/Append.java
@@ -17,15 +17,15 @@ class Append extends AbstractRedisOperation {
 
     protected Slice response() {
         Slice key = params().get(0);
-        String value = params().get(1).toString();
+        Slice value = params().get(1);
         RMString s = base().getRMString(key);
 
         if (s == null) {
-            base().putValue(key, RMString.create(value));
+            base().putValue(key, RMString.create(value.data()));
             return Response.integer(value.length());
         }
 
-        s.add(value);
+        s.add(value.data());
         base().putValue(key, s);
         return Response.integer(s.size());
     }
diff --git a/src/main/java/com/github/fppt/jedismock/operations/strings/IncrOrDecrBy.java b/src/main/java/com/github/fppt/jedismock/operations/strings/IncrOrDecrBy.java
index 309bf60..891c42d 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/strings/IncrOrDecrBy.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/strings/IncrOrDecrBy.java
@@ -27,7 +27,7 @@ abstract class IncrOrDecrBy extends AbstractRedisOperation {
             return Response.integer(d);
         }
 
-        long r = convertToLong(v.getStoredData()) + d;
+        long r = convertToLong(v.getStoredDataAsString()) + d;
         base().putValueWithoutClearingTtl(key, RMString.create(String.valueOf(r)));
         return Response.integer(r);
     }
diff --git a/src/main/java/com/github/fppt/jedismock/operations/strings/IncrOrDecrByFloat.java b/src/main/java/com/github/fppt/jedismock/operations/strings/IncrOrDecrByFloat.java
index 6207b02..b863945 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/strings/IncrOrDecrByFloat.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/strings/IncrOrDecrByFloat.java
@@ -20,7 +20,7 @@ abstract class IncrOrDecrByFloat extends AbstractRedisOperation {
 
         RMString foundValue = base().getRMString(key);
         if (foundValue != null) {
-            numericValue = numericValue.add(new BigDecimal(foundValue.getStoredData()));
+            numericValue = numericValue.add(new BigDecimal(foundValue.getStoredDataAsString()));
         }
 
         String data = String.valueOf(BigDecimal.valueOf(numericValue.intValue()).compareTo(numericValue) == 0
diff --git a/src/main/java/com/github/fppt/jedismock/storage/RedisBase.java b/src/main/java/com/github/fppt/jedismock/storage/RedisBase.java
index 45a1d6e..0a0aaf4 100644
--- a/src/main/java/com/github/fppt/jedismock/storage/RedisBase.java
+++ b/src/main/java/com/github/fppt/jedismock/storage/RedisBase.java
@@ -95,7 +95,7 @@ public class RedisBase {
             return (RMBitMap) value;
         }
         if (value instanceof RMString) {
-            return new RMBitMap(((RMString) value).getStoredData().getBytes());
+            return new RMBitMap(((RMString) value).getStoredData());
         }
         value.raiseTypeCastException();
         return null;


Test patch:
diff --git a/src/test/java/com/github/fppt/jedismock/commands/RedisCommandTest.java b/src/test/java/com/github/fppt/jedismock/commands/RedisCommandTest.java
new file mode 100644
index 0000000..d52d609
--- /dev/null
+++ b/src/test/java/com/github/fppt/jedismock/commands/RedisCommandTest.java
@@ -0,0 +1,13 @@
+package com.github.fppt.jedismock.commands;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+class RedisCommandTest {
+    @Test
+    void equalsHashcode() {
+        EqualsVerifier.forClass(RedisCommand.class)
+                .withNonnullFields("parameters")
+                .verify();
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/github/fppt/jedismock/comparisontests/hashes/HashOperationsTest.java b/src/test/java/com/github/fppt/jedismock/comparisontests/hashes/HashOperationsTest.java
index 2f9ef13..5ee1b7a 100644
--- a/src/test/java/com/github/fppt/jedismock/comparisontests/hashes/HashOperationsTest.java
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/hashes/HashOperationsTest.java
@@ -13,6 +13,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -283,4 +284,12 @@ public class HashOperationsTest {
         jedis.hset("foo", hash);
         assertThrows(JedisDataException.class, () -> jedis.get("foo"));
     }
+
+    @TestTemplate
+    public void testHsetNonUTF8binary(Jedis jedis) {
+        byte[] msg = new byte[]{(byte) 0xbe};
+        jedis.hset("foo".getBytes(), "bar".getBytes(), msg);
+        byte[] newMsg = jedis.hget("foo".getBytes(), "bar".getBytes());
+        assertArrayEquals(msg, newMsg);
+    }
 }
diff --git a/src/test/java/com/github/fppt/jedismock/comparisontests/sets/SetOperationsTest.java b/src/test/java/com/github/fppt/jedismock/comparisontests/sets/SetOperationsTest.java
index 76a1188..a00680b 100644
--- a/src/test/java/com/github/fppt/jedismock/comparisontests/sets/SetOperationsTest.java
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/sets/SetOperationsTest.java
@@ -135,4 +135,12 @@ public class SetOperationsTest {
                 assertThrows(JedisDataException.class, () -> jedis.get("my-set-key"))
                         .getMessage().startsWith("WRONGTYPE"));
     }
+
+    @TestTemplate
+    public void testSaddNonUTF8binary(Jedis jedis) {
+        byte[] msg = new byte[]{(byte) 0xbe};
+        jedis.sadd("foo".getBytes(), msg);
+        byte[] newMsg = jedis.spop("foo".getBytes());
+        assertArrayEquals(msg, newMsg);
+    }
 }
diff --git a/src/test/java/com/github/fppt/jedismock/comparisontests/sortedsets/SortedSetOperationsTest.java b/src/test/java/com/github/fppt/jedismock/comparisontests/sortedsets/SortedSetOperationsTest.java
index d02d80f..ef7ea96 100644
--- a/src/test/java/com/github/fppt/jedismock/comparisontests/sortedsets/SortedSetOperationsTest.java
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/sortedsets/SortedSetOperationsTest.java
@@ -20,40 +20,6 @@ public class SortedSetOperationsTest {
         jedis.flushAll();
     }
 
-    @TestTemplate
-    public void zaddAddsKey(Jedis jedis) {
-        String key = "mykey";
-        double score = 10;
-        String value = "myvalue";
-
-        long result = jedis.zadd(key, score, value);
-
-        assertEquals(1L, result);
-
-        List<String> results = jedis.zrange(key, 0, -1);
-
-        assertEquals(1, results.size());
-        assertEquals(value, results.get(0));
-    }
-
-    @TestTemplate
-    public void zaddAddsKeys(Jedis jedis) {
-        String key = "mykey";
-        Map<String, Double> members = new HashMap<>();
-        members.put("myvalue1", 10d);
-        members.put("myvalue2", 20d);
-
-        long result = jedis.zadd(key, members);
-
-        assertEquals(2L, result);
-
-        List<String> results = jedis.zrange(key, 0, -1);
-
-        assertEquals(2, results.size());
-        assertEquals("myvalue1", results.get(0));
-        assertEquals("myvalue2", results.get(1));
-    }
-
     @TestTemplate
     public void zcardEmptyKey(Jedis jedis) {
         String key = "mykey";
diff --git a/src/test/java/com/github/fppt/jedismock/comparisontests/sortedsets/TestZadd.java b/src/test/java/com/github/fppt/jedismock/comparisontests/sortedsets/TestZadd.java
new file mode 100644
index 0000000..76328c2
--- /dev/null
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/sortedsets/TestZadd.java
@@ -0,0 +1,65 @@
+package com.github.fppt.jedismock.comparisontests.sortedsets;
+
+import com.github.fppt.jedismock.comparisontests.ComparisonBase;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+import redis.clients.jedis.Jedis;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@ExtendWith(ComparisonBase.class)
+public class TestZadd {
+
+    @BeforeEach
+    public void setUp(Jedis jedis) {
+        jedis.flushAll();
+    }
+
+    @TestTemplate
+    public void zaddAddsKey(Jedis jedis) {
+        String key = "mykey";
+        double score = 10;
+        String value = "myvalue";
+
+        long result = jedis.zadd(key, score, value);
+
+        assertEquals(1L, result);
+
+        List<String> results = jedis.zrange(key, 0, -1);
+
+        assertEquals(1, results.size());
+        assertEquals(value, results.get(0));
+    }
+
+    @TestTemplate
+    public void zaddAddsKeys(Jedis jedis) {
+        String key = "mykey";
+        Map<String, Double> members = new HashMap<>();
+        members.put("myvalue1", 10d);
+        members.put("myvalue2", 20d);
+
+        long result = jedis.zadd(key, members);
+
+        assertEquals(2L, result);
+
+        List<String> results = jedis.zrange(key, 0, -1);
+
+        assertEquals(2, results.size());
+        assertEquals("myvalue1", results.get(0));
+        assertEquals("myvalue2", results.get(1));
+    }
+
+    @TestTemplate
+    public void testZaddNonUTF8binary(Jedis jedis) {
+        byte[] msg = new byte[]{(byte) 0xbe};
+        jedis.zadd("foo".getBytes(), 42, msg);
+        byte[] newMsg = jedis.zrange("foo".getBytes(), 0, 0).get(0);
+        assertArrayEquals(msg, newMsg);
+    }
+}
diff --git a/src/test/java/com/github/fppt/jedismock/comparisontests/strings/TestAppend.java b/src/test/java/com/github/fppt/jedismock/comparisontests/strings/TestAppend.java
new file mode 100644
index 0000000..6c1de10
--- /dev/null
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/strings/TestAppend.java
@@ -0,0 +1,40 @@
+package com.github.fppt.jedismock.comparisontests.strings;
+
+import com.github.fppt.jedismock.comparisontests.ComparisonBase;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+import redis.clients.jedis.Jedis;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@ExtendWith(ComparisonBase.class)
+public class TestAppend {
+    @BeforeEach
+    public void setUp(Jedis jedis) {
+        jedis.flushDB();
+    }
+
+    @TestTemplate
+    public void testAppendEmpty(Jedis jedis) {
+        jedis.append("foo", "bar");
+        assertEquals("bar", jedis.get("foo"));
+    }
+
+    @TestTemplate
+    public void testAppendNonEmpty(Jedis jedis) {
+        jedis.set("baz", "foo");
+        jedis.append("baz", "bar");
+        assertEquals("foobar", jedis.get("baz"));
+    }
+
+    @TestTemplate
+    public void testAppendBinary(Jedis jedis) {
+        jedis.set("baz".getBytes(), new byte[]{(byte) 0x01, (byte) 0x02});
+        jedis.append("baz".getBytes(), new byte[]{(byte) 0x03, (byte) 0x04});
+        assertArrayEquals(new byte[]{
+                (byte) 0x01, (byte) 0x02,
+                (byte) 0x03, (byte) 0x04}, jedis.get("baz".getBytes()));
+    }
+}
diff --git a/src/test/java/com/github/fppt/jedismock/comparisontests/strings/TestSet.java b/src/test/java/com/github/fppt/jedismock/comparisontests/strings/TestSet.java
index c4ecf07..0eda7d1 100644
--- a/src/test/java/com/github/fppt/jedismock/comparisontests/strings/TestSet.java
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/strings/TestSet.java
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import redis.clients.jedis.Jedis;
 import redis.clients.jedis.params.SetParams;
 
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
@@ -19,7 +20,7 @@ public class TestSet {
 
     @BeforeEach
     public void clearKey(Jedis jedis) {
-        jedis.del(SET_KEY);
+        jedis.flushDB();
     }
 
     // SET key value NX
@@ -56,7 +57,7 @@ public class TestSet {
     @TestTemplate
     public void testSetEXNXexpires(Jedis jedis) throws InterruptedException {
         testSetValueExpires(jedis, new SetParams().ex(1L).nx());
-    } //--------------------------------------------
+    }
 
     @TestTemplate
     public void testSetEXNXnotexists(Jedis jedis) {
@@ -98,6 +99,13 @@ public class TestSet {
         testSetXXWithParams(jedis, new SetParams().px(1000L).xx());
     }
 
+    @TestTemplate
+    public void testSetNonUTF8binary(Jedis jedis) {
+        byte[] msg = new byte[]{(byte) 0xbe};
+        jedis.set("foo".getBytes(), msg);
+        assertArrayEquals(msg, jedis.get("foo".getBytes()));
+    }
+
     private void testSetValueExpires(Jedis jedis, SetParams setParams) throws InterruptedException {
         assertEquals("OK", jedis.set(SET_KEY, SET_VALUE, setParams));
         assertEquals(SET_VALUE, jedis.get(SET_KEY));
diff --git a/src/test/java/com/github/fppt/jedismock/datastructures/RMZSetTest.java b/src/test/java/com/github/fppt/jedismock/datastructures/RMZSetTest.java
index a7c59ee..5544478 100644
--- a/src/test/java/com/github/fppt/jedismock/datastructures/RMZSetTest.java
+++ b/src/test/java/com/github/fppt/jedismock/datastructures/RMZSetTest.java
@@ -1,5 +1,6 @@
 package com.github.fppt.jedismock.datastructures;
 
+import nl.jqno.equalsverifier.EqualsVerifier;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.*;
@@ -7,48 +8,57 @@ import static org.junit.jupiter.api.Assertions.*;
 class RMZSetTest {
     @Test
     void compareScore() {
-        assertTrue(new ZSetEntry(1, "a").compareTo(new ZSetEntry(2, "a")) < 0);
+        assertTrue(new ZSetEntry(1, Slice.create("a"))
+                .compareTo(new ZSetEntry(2, Slice.create("a"))) < 0);
     }
 
     @Test
     void compareLex() {
-        assertTrue(new ZSetEntry(1, "a").compareTo(new ZSetEntry(1, "b")) < 0);
+        assertTrue(new ZSetEntry(1, Slice.create("a"))
+                .compareTo(new ZSetEntry(1, Slice.create("b"))) < 0);
     }
 
     @Test
     void compareEquals() {
-        assertEquals(0, new ZSetEntry(1, "a").compareTo(new ZSetEntry(1, "a")));
-        assertEquals(new ZSetEntry(1, "a"), new ZSetEntry(1, "a"));
+        assertEquals(0, new ZSetEntry(1, Slice.create("a"))
+                .compareTo(new ZSetEntry(1, Slice.create("a"))));
+        assertEquals(new ZSetEntry(1, Slice.create("a")), new ZSetEntry(1, Slice.create("a")));
     }
 
     @Test
     void compareMinScore(){
-        assertTrue(new ZSetEntry(ZSetEntry.MIN_SCORE, "a")
-                .compareTo(new ZSetEntry(2, "a")) < 0);
+        assertTrue(new ZSetEntry(ZSetEntry.MIN_SCORE, Slice.create("a"))
+                .compareTo(new ZSetEntry(2, Slice.create("a"))) < 0);
     }
 
     @Test
     void compareMaxScore(){
-        assertTrue(new ZSetEntry(ZSetEntry.MAX_SCORE, "a")
-                .compareTo(new ZSetEntry(2, "a")) > 0);
+        assertTrue(new ZSetEntry(ZSetEntry.MAX_SCORE, Slice.create("a"))
+                .compareTo(new ZSetEntry(2, Slice.create("a"))) > 0);
     }
 
 
     @Test
     void compareMinLex(){
         assertTrue(new ZSetEntry(1, ZSetEntry.MIN_VALUE)
-                .compareTo(new ZSetEntry(1, "a")) < 0);
+                .compareTo(new ZSetEntry(1, Slice.create("a"))) < 0);
     }
 
     @Test
     void compareMaxLex(){
         assertTrue(new ZSetEntry(1, ZSetEntry.MAX_VALUE)
-                .compareTo(new ZSetEntry(1, "Z")) > 0);
+                .compareTo(new ZSetEntry(1, Slice.create("Z"))) > 0);
     }
 
     @Test
     void compareWithMaxLex(){
-        assertTrue(new ZSetEntry(1, "Z")
+        assertTrue(new ZSetEntry(1, Slice.create("Z"))
                 .compareTo(new ZSetEntry(1, ZSetEntry.MAX_VALUE)) < 0);
     }
+
+    @Test
+    void equalsHashCode() {
+        EqualsVerifier.forClass(ZSetEntry.class)
+                .withNonnullFields("value").verify();
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/com/github/fppt/jedismock/datastructures/SliceTest.java b/src/test/java/com/github/fppt/jedismock/datastructures/SliceTest.java
new file mode 100644
index 0000000..3cf3d55
--- /dev/null
+++ b/src/test/java/com/github/fppt/jedismock/datastructures/SliceTest.java
@@ -0,0 +1,13 @@
+package com.github.fppt.jedismock.datastructures;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+class SliceTest {
+    @Test
+    void equalsHashCode() {
+        EqualsVerifier.forClass(Slice.class)
+                .withNonnullFields("storedData")
+                .verify();
+    }
+}
\ No newline at end of file