blob: 22acdff64443557a3da52f8baecd555e4f08bfb2 [file] [log] [blame]
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
/**
* A test harness for the Redis API built on rocksdb.
*
* USAGE: Build with: "make redis_test" (in rocksdb directory).
* Run unit tests with: "./redis_test"
* Manual/Interactive user testing: "./redis_test -m"
* Manual user testing + restart database: "./redis_test -m -d"
*
* TODO: Add LARGE random test cases to verify efficiency and scalability
*
* @author Deon Nicholas (dnicholas@fb.com)
*/
#ifndef ROCKSDB_LITE
#include <iostream>
#include <cctype>
#include "redis_lists.h"
#include "util/testharness.h"
#include "util/random.h"
using namespace rocksdb;
namespace rocksdb {
class RedisListsTest : public testing::Test {
public:
static const std::string kDefaultDbName;
static Options options;
RedisListsTest() {
options.create_if_missing = true;
}
};
const std::string RedisListsTest::kDefaultDbName =
test::TmpDir() + "/redis_lists_test";
Options RedisListsTest::options = Options();
// operator== and operator<< are defined below for vectors (lists)
// Needed for ASSERT_EQ
namespace {
void AssertListEq(const std::vector<std::string>& result,
const std::vector<std::string>& expected_result) {
ASSERT_EQ(result.size(), expected_result.size());
for (size_t i = 0; i < result.size(); ++i) {
ASSERT_EQ(result[i], expected_result[i]);
}
}
} // namespace
// PushRight, Length, Index, Range
TEST_F(RedisListsTest, SimpleTest) {
RedisLists redis(kDefaultDbName, options, true); // Destructive
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// Simple PushRight (should return the new length each time)
ASSERT_EQ(redis.PushRight("k1", "v1"), 1);
ASSERT_EQ(redis.PushRight("k1", "v2"), 2);
ASSERT_EQ(redis.PushRight("k1", "v3"), 3);
// Check Length and Index() functions
ASSERT_EQ(redis.Length("k1"), 3); // Check length
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "v1"); // Check valid indices
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "v2");
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "v3");
// Check range function and vectors
std::vector<std::string> result = redis.Range("k1", 0, 2); // Get the list
std::vector<std::string> expected_result(3);
expected_result[0] = "v1";
expected_result[1] = "v2";
expected_result[2] = "v3";
AssertListEq(result, expected_result);
}
// PushLeft, Length, Index, Range
TEST_F(RedisListsTest, SimpleTest2) {
RedisLists redis(kDefaultDbName, options, true); // Destructive
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// Simple PushRight
ASSERT_EQ(redis.PushLeft("k1", "v3"), 1);
ASSERT_EQ(redis.PushLeft("k1", "v2"), 2);
ASSERT_EQ(redis.PushLeft("k1", "v1"), 3);
// Check Length and Index() functions
ASSERT_EQ(redis.Length("k1"), 3); // Check length
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "v1"); // Check valid indices
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "v2");
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "v3");
// Check range function and vectors
std::vector<std::string> result = redis.Range("k1", 0, 2); // Get the list
std::vector<std::string> expected_result(3);
expected_result[0] = "v1";
expected_result[1] = "v2";
expected_result[2] = "v3";
AssertListEq(result, expected_result);
}
// Exhaustive test of the Index() function
TEST_F(RedisListsTest, IndexTest) {
RedisLists redis(kDefaultDbName, options, true); // Destructive
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// Empty Index check (return empty and should not crash or edit tempv)
tempv = "yo";
ASSERT_TRUE(!redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "yo");
ASSERT_TRUE(!redis.Index("fda", 3, &tempv));
ASSERT_EQ(tempv, "yo");
ASSERT_TRUE(!redis.Index("random", -12391, &tempv));
ASSERT_EQ(tempv, "yo");
// Simple Pushes (will yield: [v6, v4, v4, v1, v2, v3]
redis.PushRight("k1", "v1");
redis.PushRight("k1", "v2");
redis.PushRight("k1", "v3");
redis.PushLeft("k1", "v4");
redis.PushLeft("k1", "v4");
redis.PushLeft("k1", "v6");
// Simple, non-negative indices
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "v6");
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "v4");
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "v4");
ASSERT_TRUE(redis.Index("k1", 3, &tempv));
ASSERT_EQ(tempv, "v1");
ASSERT_TRUE(redis.Index("k1", 4, &tempv));
ASSERT_EQ(tempv, "v2");
ASSERT_TRUE(redis.Index("k1", 5, &tempv));
ASSERT_EQ(tempv, "v3");
// Negative indices
ASSERT_TRUE(redis.Index("k1", -6, &tempv));
ASSERT_EQ(tempv, "v6");
ASSERT_TRUE(redis.Index("k1", -5, &tempv));
ASSERT_EQ(tempv, "v4");
ASSERT_TRUE(redis.Index("k1", -4, &tempv));
ASSERT_EQ(tempv, "v4");
ASSERT_TRUE(redis.Index("k1", -3, &tempv));
ASSERT_EQ(tempv, "v1");
ASSERT_TRUE(redis.Index("k1", -2, &tempv));
ASSERT_EQ(tempv, "v2");
ASSERT_TRUE(redis.Index("k1", -1, &tempv));
ASSERT_EQ(tempv, "v3");
// Out of bounds (return empty, no crash)
ASSERT_TRUE(!redis.Index("k1", 6, &tempv));
ASSERT_TRUE(!redis.Index("k1", 123219, &tempv));
ASSERT_TRUE(!redis.Index("k1", -7, &tempv));
ASSERT_TRUE(!redis.Index("k1", -129, &tempv));
}
// Exhaustive test of the Range() function
TEST_F(RedisListsTest, RangeTest) {
RedisLists redis(kDefaultDbName, options, true); // Destructive
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// Simple Pushes (will yield: [v6, v4, v4, v1, v2, v3])
redis.PushRight("k1", "v1");
redis.PushRight("k1", "v2");
redis.PushRight("k1", "v3");
redis.PushLeft("k1", "v4");
redis.PushLeft("k1", "v4");
redis.PushLeft("k1", "v6");
// Sanity check (check the length; make sure it's 6)
ASSERT_EQ(redis.Length("k1"), 6);
// Simple range
std::vector<std::string> res = redis.Range("k1", 1, 4);
ASSERT_EQ((int)res.size(), 4);
ASSERT_EQ(res[0], "v4");
ASSERT_EQ(res[1], "v4");
ASSERT_EQ(res[2], "v1");
ASSERT_EQ(res[3], "v2");
// Negative indices (i.e.: measured from the end)
res = redis.Range("k1", 2, -1);
ASSERT_EQ((int)res.size(), 4);
ASSERT_EQ(res[0], "v4");
ASSERT_EQ(res[1], "v1");
ASSERT_EQ(res[2], "v2");
ASSERT_EQ(res[3], "v3");
res = redis.Range("k1", -6, -4);
ASSERT_EQ((int)res.size(), 3);
ASSERT_EQ(res[0], "v6");
ASSERT_EQ(res[1], "v4");
ASSERT_EQ(res[2], "v4");
res = redis.Range("k1", -1, 5);
ASSERT_EQ((int)res.size(), 1);
ASSERT_EQ(res[0], "v3");
// Partial / Broken indices
res = redis.Range("k1", -3, 1000000);
ASSERT_EQ((int)res.size(), 3);
ASSERT_EQ(res[0], "v1");
ASSERT_EQ(res[1], "v2");
ASSERT_EQ(res[2], "v3");
res = redis.Range("k1", -1000000, 1);
ASSERT_EQ((int)res.size(), 2);
ASSERT_EQ(res[0], "v6");
ASSERT_EQ(res[1], "v4");
// Invalid indices
res = redis.Range("k1", 7, 9);
ASSERT_EQ((int)res.size(), 0);
res = redis.Range("k1", -8, -7);
ASSERT_EQ((int)res.size(), 0);
res = redis.Range("k1", 3, 2);
ASSERT_EQ((int)res.size(), 0);
res = redis.Range("k1", 5, -2);
ASSERT_EQ((int)res.size(), 0);
// Range matches Index
res = redis.Range("k1", -6, -4);
ASSERT_TRUE(redis.Index("k1", -6, &tempv));
ASSERT_EQ(tempv, res[0]);
ASSERT_TRUE(redis.Index("k1", -5, &tempv));
ASSERT_EQ(tempv, res[1]);
ASSERT_TRUE(redis.Index("k1", -4, &tempv));
ASSERT_EQ(tempv, res[2]);
// Last check
res = redis.Range("k1", 0, -6);
ASSERT_EQ((int)res.size(), 1);
ASSERT_EQ(res[0], "v6");
}
// Exhaustive test for InsertBefore(), and InsertAfter()
TEST_F(RedisListsTest, InsertTest) {
RedisLists redis(kDefaultDbName, options, true);
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// Insert on empty list (return 0, and do not crash)
ASSERT_EQ(redis.InsertBefore("k1", "non-exist", "a"), 0);
ASSERT_EQ(redis.InsertAfter("k1", "other-non-exist", "c"), 0);
ASSERT_EQ(redis.Length("k1"), 0);
// Push some preliminary stuff [g, f, e, d, c, b, a]
redis.PushLeft("k1", "a");
redis.PushLeft("k1", "b");
redis.PushLeft("k1", "c");
redis.PushLeft("k1", "d");
redis.PushLeft("k1", "e");
redis.PushLeft("k1", "f");
redis.PushLeft("k1", "g");
ASSERT_EQ(redis.Length("k1"), 7);
// Test InsertBefore
int newLength = redis.InsertBefore("k1", "e", "hello");
ASSERT_EQ(newLength, 8);
ASSERT_EQ(redis.Length("k1"), newLength);
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "f");
ASSERT_TRUE(redis.Index("k1", 3, &tempv));
ASSERT_EQ(tempv, "e");
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "hello");
// Test InsertAfter
newLength = redis.InsertAfter("k1", "c", "bye");
ASSERT_EQ(newLength, 9);
ASSERT_EQ(redis.Length("k1"), newLength);
ASSERT_TRUE(redis.Index("k1", 6, &tempv));
ASSERT_EQ(tempv, "bye");
// Test bad value on InsertBefore
newLength = redis.InsertBefore("k1", "yo", "x");
ASSERT_EQ(newLength, 9);
ASSERT_EQ(redis.Length("k1"), newLength);
// Test bad value on InsertAfter
newLength = redis.InsertAfter("k1", "xxxx", "y");
ASSERT_EQ(newLength, 9);
ASSERT_EQ(redis.Length("k1"), newLength);
// Test InsertBefore beginning
newLength = redis.InsertBefore("k1", "g", "begggggggggggggggg");
ASSERT_EQ(newLength, 10);
ASSERT_EQ(redis.Length("k1"), newLength);
// Test InsertAfter end
newLength = redis.InsertAfter("k1", "a", "enddd");
ASSERT_EQ(newLength, 11);
ASSERT_EQ(redis.Length("k1"), newLength);
// Make sure nothing weird happened.
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "begggggggggggggggg");
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "g");
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "f");
ASSERT_TRUE(redis.Index("k1", 3, &tempv));
ASSERT_EQ(tempv, "hello");
ASSERT_TRUE(redis.Index("k1", 4, &tempv));
ASSERT_EQ(tempv, "e");
ASSERT_TRUE(redis.Index("k1", 5, &tempv));
ASSERT_EQ(tempv, "d");
ASSERT_TRUE(redis.Index("k1", 6, &tempv));
ASSERT_EQ(tempv, "c");
ASSERT_TRUE(redis.Index("k1", 7, &tempv));
ASSERT_EQ(tempv, "bye");
ASSERT_TRUE(redis.Index("k1", 8, &tempv));
ASSERT_EQ(tempv, "b");
ASSERT_TRUE(redis.Index("k1", 9, &tempv));
ASSERT_EQ(tempv, "a");
ASSERT_TRUE(redis.Index("k1", 10, &tempv));
ASSERT_EQ(tempv, "enddd");
}
// Exhaustive test of Set function
TEST_F(RedisListsTest, SetTest) {
RedisLists redis(kDefaultDbName, options, true);
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// Set on empty list (return false, and do not crash)
ASSERT_EQ(redis.Set("k1", 7, "a"), false);
ASSERT_EQ(redis.Set("k1", 0, "a"), false);
ASSERT_EQ(redis.Set("k1", -49, "cx"), false);
ASSERT_EQ(redis.Length("k1"), 0);
// Push some preliminary stuff [g, f, e, d, c, b, a]
redis.PushLeft("k1", "a");
redis.PushLeft("k1", "b");
redis.PushLeft("k1", "c");
redis.PushLeft("k1", "d");
redis.PushLeft("k1", "e");
redis.PushLeft("k1", "f");
redis.PushLeft("k1", "g");
ASSERT_EQ(redis.Length("k1"), 7);
// Test Regular Set
ASSERT_TRUE(redis.Set("k1", 0, "0"));
ASSERT_TRUE(redis.Set("k1", 3, "3"));
ASSERT_TRUE(redis.Set("k1", 6, "6"));
ASSERT_TRUE(redis.Set("k1", 2, "2"));
ASSERT_TRUE(redis.Set("k1", 5, "5"));
ASSERT_TRUE(redis.Set("k1", 1, "1"));
ASSERT_TRUE(redis.Set("k1", 4, "4"));
ASSERT_EQ(redis.Length("k1"), 7); // Size should not change
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "0");
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "1");
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "2");
ASSERT_TRUE(redis.Index("k1", 3, &tempv));
ASSERT_EQ(tempv, "3");
ASSERT_TRUE(redis.Index("k1", 4, &tempv));
ASSERT_EQ(tempv, "4");
ASSERT_TRUE(redis.Index("k1", 5, &tempv));
ASSERT_EQ(tempv, "5");
ASSERT_TRUE(redis.Index("k1", 6, &tempv));
ASSERT_EQ(tempv, "6");
// Set with negative indices
ASSERT_TRUE(redis.Set("k1", -7, "a"));
ASSERT_TRUE(redis.Set("k1", -4, "d"));
ASSERT_TRUE(redis.Set("k1", -1, "g"));
ASSERT_TRUE(redis.Set("k1", -5, "c"));
ASSERT_TRUE(redis.Set("k1", -2, "f"));
ASSERT_TRUE(redis.Set("k1", -6, "b"));
ASSERT_TRUE(redis.Set("k1", -3, "e"));
ASSERT_EQ(redis.Length("k1"), 7); // Size should not change
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "a");
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "b");
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "c");
ASSERT_TRUE(redis.Index("k1", 3, &tempv));
ASSERT_EQ(tempv, "d");
ASSERT_TRUE(redis.Index("k1", 4, &tempv));
ASSERT_EQ(tempv, "e");
ASSERT_TRUE(redis.Index("k1", 5, &tempv));
ASSERT_EQ(tempv, "f");
ASSERT_TRUE(redis.Index("k1", 6, &tempv));
ASSERT_EQ(tempv, "g");
// Bad indices (just out-of-bounds / off-by-one check)
ASSERT_EQ(redis.Set("k1", -8, "off-by-one in negative index"), false);
ASSERT_EQ(redis.Set("k1", 7, "off-by-one-error in positive index"), false);
ASSERT_EQ(redis.Set("k1", 43892, "big random index should fail"), false);
ASSERT_EQ(redis.Set("k1", -21391, "large negative index should fail"), false);
// One last check (to make sure nothing weird happened)
ASSERT_EQ(redis.Length("k1"), 7); // Size should not change
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "a");
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "b");
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "c");
ASSERT_TRUE(redis.Index("k1", 3, &tempv));
ASSERT_EQ(tempv, "d");
ASSERT_TRUE(redis.Index("k1", 4, &tempv));
ASSERT_EQ(tempv, "e");
ASSERT_TRUE(redis.Index("k1", 5, &tempv));
ASSERT_EQ(tempv, "f");
ASSERT_TRUE(redis.Index("k1", 6, &tempv));
ASSERT_EQ(tempv, "g");
}
// Testing Insert, Push, and Set, in a mixed environment
TEST_F(RedisListsTest, InsertPushSetTest) {
RedisLists redis(kDefaultDbName, options, true); // Destructive
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// A series of pushes and insertions
// Will result in [newbegin, z, a, aftera, x, newend]
// Also, check the return value sometimes (should return length)
int lengthCheck;
lengthCheck = redis.PushLeft("k1", "a");
ASSERT_EQ(lengthCheck, 1);
redis.PushLeft("k1", "z");
redis.PushRight("k1", "x");
lengthCheck = redis.InsertAfter("k1", "a", "aftera");
ASSERT_EQ(lengthCheck , 4);
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore beginning of list
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
// Check
std::vector<std::string> res = redis.Range("k1", 0, -1); // Get the list
ASSERT_EQ((int)res.size(), 6);
ASSERT_EQ(res[0], "newbegin");
ASSERT_EQ(res[5], "newend");
ASSERT_EQ(res[3], "aftera");
// Testing duplicate values/pivots (multiple occurrences of 'a')
ASSERT_TRUE(redis.Set("k1", 0, "a")); // [a, z, a, aftera, x, newend]
redis.InsertAfter("k1", "a", "happy"); // [a, happy, z, a, aftera, ...]
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "happy");
redis.InsertBefore("k1", "a", "sad"); // [sad, a, happy, z, a, aftera, ...]
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "sad");
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "happy");
ASSERT_TRUE(redis.Index("k1", 5, &tempv));
ASSERT_EQ(tempv, "aftera");
redis.InsertAfter("k1", "a", "zz"); // [sad, a, zz, happy, z, a, aftera, ...]
ASSERT_TRUE(redis.Index("k1", 2, &tempv));
ASSERT_EQ(tempv, "zz");
ASSERT_TRUE(redis.Index("k1", 6, &tempv));
ASSERT_EQ(tempv, "aftera");
ASSERT_TRUE(redis.Set("k1", 1, "nota")); // [sad, nota, zz, happy, z, a, ...]
redis.InsertBefore("k1", "a", "ba"); // [sad, nota, zz, happy, z, ba, a, ...]
ASSERT_TRUE(redis.Index("k1", 4, &tempv));
ASSERT_EQ(tempv, "z");
ASSERT_TRUE(redis.Index("k1", 5, &tempv));
ASSERT_EQ(tempv, "ba");
ASSERT_TRUE(redis.Index("k1", 6, &tempv));
ASSERT_EQ(tempv, "a");
// We currently have: [sad, nota, zz, happy, z, ba, a, aftera, x, newend]
// redis.Print("k1"); // manually check
// Test Inserting before/after non-existent values
lengthCheck = redis.Length("k1"); // Ensure that the length doesn't change
ASSERT_EQ(lengthCheck, 10);
ASSERT_EQ(redis.InsertBefore("k1", "non-exist", "randval"), lengthCheck);
ASSERT_EQ(redis.InsertAfter("k1", "nothing", "a"), lengthCheck);
ASSERT_EQ(redis.InsertAfter("randKey", "randVal", "ranValue"), 0); // Empty
ASSERT_EQ(redis.Length("k1"), lengthCheck); // The length should not change
// Simply Test the Set() function
redis.Set("k1", 5, "ba2");
redis.InsertBefore("k1", "ba2", "beforeba2");
ASSERT_TRUE(redis.Index("k1", 4, &tempv));
ASSERT_EQ(tempv, "z");
ASSERT_TRUE(redis.Index("k1", 5, &tempv));
ASSERT_EQ(tempv, "beforeba2");
ASSERT_TRUE(redis.Index("k1", 6, &tempv));
ASSERT_EQ(tempv, "ba2");
ASSERT_TRUE(redis.Index("k1", 7, &tempv));
ASSERT_EQ(tempv, "a");
// We have: [sad, nota, zz, happy, z, beforeba2, ba2, a, aftera, x, newend]
// Set() with negative indices
redis.Set("k1", -1, "endprank");
ASSERT_TRUE(!redis.Index("k1", 11, &tempv));
ASSERT_TRUE(redis.Index("k1", 10, &tempv));
ASSERT_EQ(tempv, "endprank"); // Ensure Set worked correctly
redis.Set("k1", -11, "t");
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "t");
// Test out of bounds Set
ASSERT_EQ(redis.Set("k1", -12, "ssd"), false);
ASSERT_EQ(redis.Set("k1", 11, "sasd"), false);
ASSERT_EQ(redis.Set("k1", 1200, "big"), false);
}
// Testing Trim, Pop
TEST_F(RedisListsTest, TrimPopTest) {
RedisLists redis(kDefaultDbName, options, true); // Destructive
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// A series of pushes and insertions
// Will result in [newbegin, z, a, aftera, x, newend]
redis.PushLeft("k1", "a");
redis.PushLeft("k1", "z");
redis.PushRight("k1", "x");
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore start of list
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
redis.InsertAfter("k1", "a", "aftera");
// Simple PopLeft/Right test
ASSERT_TRUE(redis.PopLeft("k1", &tempv));
ASSERT_EQ(tempv, "newbegin");
ASSERT_EQ(redis.Length("k1"), 5);
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "z");
ASSERT_TRUE(redis.PopRight("k1", &tempv));
ASSERT_EQ(tempv, "newend");
ASSERT_EQ(redis.Length("k1"), 4);
ASSERT_TRUE(redis.Index("k1", -1, &tempv));
ASSERT_EQ(tempv, "x");
// Now have: [z, a, aftera, x]
// Test Trim
ASSERT_TRUE(redis.Trim("k1", 0, -1)); // [z, a, aftera, x] (do nothing)
ASSERT_EQ(redis.Length("k1"), 4);
ASSERT_TRUE(redis.Trim("k1", 0, 2)); // [z, a, aftera]
ASSERT_EQ(redis.Length("k1"), 3);
ASSERT_TRUE(redis.Index("k1", -1, &tempv));
ASSERT_EQ(tempv, "aftera");
ASSERT_TRUE(redis.Trim("k1", 1, 1)); // [a]
ASSERT_EQ(redis.Length("k1"), 1);
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "a");
// Test out of bounds (empty) trim
ASSERT_TRUE(redis.Trim("k1", 1, 0));
ASSERT_EQ(redis.Length("k1"), 0);
// Popping with empty list (return empty without error)
ASSERT_TRUE(!redis.PopLeft("k1", &tempv));
ASSERT_TRUE(!redis.PopRight("k1", &tempv));
ASSERT_TRUE(redis.Trim("k1", 0, 5));
// Exhaustive Trim test (negative and invalid indices)
// Will start in [newbegin, z, a, aftera, x, newend]
redis.PushLeft("k1", "a");
redis.PushLeft("k1", "z");
redis.PushRight("k1", "x");
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore start of list
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
redis.InsertAfter("k1", "a", "aftera");
ASSERT_TRUE(redis.Trim("k1", -6, -1)); // Should do nothing
ASSERT_EQ(redis.Length("k1"), 6);
ASSERT_TRUE(redis.Trim("k1", 1, -2));
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "z");
ASSERT_TRUE(redis.Index("k1", 3, &tempv));
ASSERT_EQ(tempv, "x");
ASSERT_EQ(redis.Length("k1"), 4);
ASSERT_TRUE(redis.Trim("k1", -3, -2));
ASSERT_EQ(redis.Length("k1"), 2);
}
// Testing Remove, RemoveFirst, RemoveLast
TEST_F(RedisListsTest, RemoveTest) {
RedisLists redis(kDefaultDbName, options, true); // Destructive
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// A series of pushes and insertions
// Will result in [newbegin, z, a, aftera, x, newend, a, a]
redis.PushLeft("k1", "a");
redis.PushLeft("k1", "z");
redis.PushRight("k1", "x");
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore start of list
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
redis.InsertAfter("k1", "a", "aftera");
redis.PushRight("k1", "a");
redis.PushRight("k1", "a");
// Verify
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "newbegin");
ASSERT_TRUE(redis.Index("k1", -1, &tempv));
ASSERT_EQ(tempv, "a");
// Check RemoveFirst (Remove the first two 'a')
// Results in [newbegin, z, aftera, x, newend, a]
int numRemoved = redis.Remove("k1", 2, "a");
ASSERT_EQ(numRemoved, 2);
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "newbegin");
ASSERT_TRUE(redis.Index("k1", 1, &tempv));
ASSERT_EQ(tempv, "z");
ASSERT_TRUE(redis.Index("k1", 4, &tempv));
ASSERT_EQ(tempv, "newend");
ASSERT_TRUE(redis.Index("k1", 5, &tempv));
ASSERT_EQ(tempv, "a");
ASSERT_EQ(redis.Length("k1"), 6);
// Repopulate some stuff
// Results in: [x, x, x, x, x, newbegin, z, x, aftera, x, newend, a, x]
redis.PushLeft("k1", "x");
redis.PushLeft("k1", "x");
redis.PushLeft("k1", "x");
redis.PushLeft("k1", "x");
redis.PushLeft("k1", "x");
redis.PushRight("k1", "x");
redis.InsertAfter("k1", "z", "x");
// Test removal from end
numRemoved = redis.Remove("k1", -2, "x");
ASSERT_EQ(numRemoved, 2);
ASSERT_TRUE(redis.Index("k1", 8, &tempv));
ASSERT_EQ(tempv, "aftera");
ASSERT_TRUE(redis.Index("k1", 9, &tempv));
ASSERT_EQ(tempv, "newend");
ASSERT_TRUE(redis.Index("k1", 10, &tempv));
ASSERT_EQ(tempv, "a");
ASSERT_TRUE(!redis.Index("k1", 11, &tempv));
numRemoved = redis.Remove("k1", -2, "x");
ASSERT_EQ(numRemoved, 2);
ASSERT_TRUE(redis.Index("k1", 4, &tempv));
ASSERT_EQ(tempv, "newbegin");
ASSERT_TRUE(redis.Index("k1", 6, &tempv));
ASSERT_EQ(tempv, "aftera");
// We now have: [x, x, x, x, newbegin, z, aftera, newend, a]
ASSERT_EQ(redis.Length("k1"), 9);
ASSERT_TRUE(redis.Index("k1", -1, &tempv));
ASSERT_EQ(tempv, "a");
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "x");
// Test over-shooting (removing more than there exists)
numRemoved = redis.Remove("k1", -9000, "x");
ASSERT_EQ(numRemoved , 4); // Only really removed 4
ASSERT_EQ(redis.Length("k1"), 5);
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "newbegin");
numRemoved = redis.Remove("k1", 1, "x");
ASSERT_EQ(numRemoved, 0);
// Try removing ALL!
numRemoved = redis.Remove("k1", 0, "newbegin"); // REMOVE 0 will remove all!
ASSERT_EQ(numRemoved, 1);
// Removal from an empty-list
ASSERT_TRUE(redis.Trim("k1", 1, 0));
numRemoved = redis.Remove("k1", 1, "z");
ASSERT_EQ(numRemoved, 0);
}
// Test Multiple keys and Persistence
TEST_F(RedisListsTest, PersistenceMultiKeyTest) {
std::string tempv; // Used below for all Index(), PopRight(), PopLeft()
// Block one: populate a single key in the database
{
RedisLists redis(kDefaultDbName, options, true); // Destructive
// A series of pushes and insertions
// Will result in [newbegin, z, a, aftera, x, newend, a, a]
redis.PushLeft("k1", "a");
redis.PushLeft("k1", "z");
redis.PushRight("k1", "x");
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore start of list
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
redis.InsertAfter("k1", "a", "aftera");
redis.PushRight("k1", "a");
redis.PushRight("k1", "a");
ASSERT_TRUE(redis.Index("k1", 3, &tempv));
ASSERT_EQ(tempv, "aftera");
}
// Block two: make sure changes were saved and add some other key
{
RedisLists redis(kDefaultDbName, options, false); // Persistent, non-destructive
// Check
ASSERT_EQ(redis.Length("k1"), 8);
ASSERT_TRUE(redis.Index("k1", 3, &tempv));
ASSERT_EQ(tempv, "aftera");
redis.PushRight("k2", "randomkey");
redis.PushLeft("k2", "sas");
redis.PopLeft("k1", &tempv);
}
// Block three: Verify the changes from block 2
{
RedisLists redis(kDefaultDbName, options, false); // Persistent, non-destructive
// Check
ASSERT_EQ(redis.Length("k1"), 7);
ASSERT_EQ(redis.Length("k2"), 2);
ASSERT_TRUE(redis.Index("k1", 0, &tempv));
ASSERT_EQ(tempv, "z");
ASSERT_TRUE(redis.Index("k2", -2, &tempv));
ASSERT_EQ(tempv, "sas");
}
}
/// THE manual REDIS TEST begins here
/// THIS WILL ONLY OCCUR IF YOU RUN: ./redis_test -m
namespace {
void MakeUpper(std::string* const s) {
int len = static_cast<int>(s->length());
for (int i = 0; i < len; ++i) {
(*s)[i] = toupper((*s)[i]); // C-version defined in <ctype.h>
}
}
/// Allows the user to enter in REDIS commands into the command-line.
/// This is useful for manual / interacticve testing / debugging.
/// Use destructive=true to clean the database before use.
/// Use destructive=false to remember the previous state (i.e.: persistent)
/// Should be called from main function.
int manual_redis_test(bool destructive){
RedisLists redis(RedisListsTest::kDefaultDbName,
RedisListsTest::options,
destructive);
// TODO: Right now, please use spaces to separate each word.
// In actual redis, you can use quotes to specify compound values
// Example: RPUSH mylist "this is a compound value"
std::string command;
while(true) {
std::cin >> command;
MakeUpper(&command);
if (command == "LINSERT") {
std::string k, t, p, v;
std::cin >> k >> t >> p >> v;
MakeUpper(&t);
if (t=="BEFORE") {
std::cout << redis.InsertBefore(k, p, v) << std::endl;
} else if (t=="AFTER") {
std::cout << redis.InsertAfter(k, p, v) << std::endl;
}
} else if (command == "LPUSH") {
std::string k, v;
std::cin >> k >> v;
redis.PushLeft(k, v);
} else if (command == "RPUSH") {
std::string k, v;
std::cin >> k >> v;
redis.PushRight(k, v);
} else if (command == "LPOP") {
std::string k;
std::cin >> k;
std::string res;
redis.PopLeft(k, &res);
std::cout << res << std::endl;
} else if (command == "RPOP") {
std::string k;
std::cin >> k;
std::string res;
redis.PopRight(k, &res);
std::cout << res << std::endl;
} else if (command == "LREM") {
std::string k;
int amt;
std::string v;
std::cin >> k >> amt >> v;
std::cout << redis.Remove(k, amt, v) << std::endl;
} else if (command == "LLEN") {
std::string k;
std::cin >> k;
std::cout << redis.Length(k) << std::endl;
} else if (command == "LRANGE") {
std::string k;
int i, j;
std::cin >> k >> i >> j;
std::vector<std::string> res = redis.Range(k, i, j);
for (auto it = res.begin(); it != res.end(); ++it) {
std::cout << " " << (*it);
}
std::cout << std::endl;
} else if (command == "LTRIM") {
std::string k;
int i, j;
std::cin >> k >> i >> j;
redis.Trim(k, i, j);
} else if (command == "LSET") {
std::string k;
int idx;
std::string v;
std::cin >> k >> idx >> v;
redis.Set(k, idx, v);
} else if (command == "LINDEX") {
std::string k;
int idx;
std::cin >> k >> idx;
std::string res;
redis.Index(k, idx, &res);
std::cout << res << std::endl;
} else if (command == "PRINT") { // Added by Deon
std::string k;
std::cin >> k;
redis.Print(k);
} else if (command == "QUIT") {
return 0;
} else {
std::cout << "unknown command: " << command << std::endl;
}
}
}
} // namespace
} // namespace rocksdb
// USAGE: "./redis_test" for default (unit tests)
// "./redis_test -m" for manual testing (redis command api)
// "./redis_test -m -d" for destructive manual test (erase db before use)
namespace {
// Check for "want" argument in the argument list
bool found_arg(int argc, char* argv[], const char* want){
for(int i=1; i<argc; ++i){
if (strcmp(argv[i], want) == 0) {
return true;
}
}
return false;
}
} // namespace
// Will run unit tests.
// However, if -m is specified, it will do user manual/interactive testing
// -m -d is manual and destructive (will clear the database before use)
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
if (found_arg(argc, argv, "-m")) {
bool destructive = found_arg(argc, argv, "-d");
return rocksdb::manual_redis_test(destructive);
} else {
return RUN_ALL_TESTS();
}
}
#else
#include <stdio.h>
int main(int argc, char* argv[]) {
fprintf(stderr, "SKIPPED as redis is not supported in ROCKSDB_LITE\n");
return 0;
}
#endif // !ROCKSDB_LITE