// ---------------------------------------------------------------------- // File: state-machine.cc // Author: Georgios Bitzes - CERN // ---------------------------------------------------------------------- /************************************************************************ * quarkdb - a redis-like highly available key-value store * * Copyright (C) 2016 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "storage/KeyDescriptor.hh" #include "storage/StagingArea.hh" #include "storage/ReverseLocator.hh" #include "storage/PatternMatching.hh" #include "storage/ExpirationEventIterator.hh" #include "storage/ConsistencyScanner.hh" #include "StateMachine.hh" #include "test-utils.hh" #include using namespace quarkdb; #define ASSERT_OK(msg) ASSERT_TRUE(msg.ok()) #define ASSERT_NOTFOUND(msg) ASSERT_TRUE(msg.IsNotFound()) #define ASSERT_NOT_OK(msg) ASSERT_FALSE(msg.ok()) class State_Machine : public TestCluster3NodesFixture {}; TEST_F(State_Machine, test_write_transactions) { ASSERT_EQ(stateMachine()->getLastApplied(), 0); ASSERT_THROW(stateMachine()->set("abc", "123", 2), FatalException); ASSERT_OK(stateMachine()->set("abc", "123", 1)); ASSERT_EQ(stateMachine()->getLastApplied(), 1); ASSERT_OK(stateMachine()->set("abc", "122", 2)); ASSERT_EQ(stateMachine()->getLastApplied(), 2); bool created; ASSERT_OK(stateMachine()->hset("myhash", "key1", "value", created, 3)); ASSERT_TRUE(created); ASSERT_EQ(stateMachine()->getLastApplied(), 3); std::string tmp; ASSERT_OK(stateMachine()->hget("myhash", "key1", tmp)); ASSERT_EQ(tmp, "value"); RedisRequest elem { "key1", "key2" }; int64_t count; ASSERT_OK(stateMachine()->hdel("myhash", elem.begin(), elem.end(), count, 4)); ASSERT_EQ(count, 1); ASSERT_NOTFOUND(stateMachine()->hget("myhash", "key1", tmp)); ASSERT_EQ(stateMachine()->getLastApplied(), 4); ASSERT_OK(stateMachine()->hdel("myhash", elem.begin(), elem.begin()+1, count, 5)); ASSERT_EQ(count, 0); ASSERT_EQ(stateMachine()->getLastApplied(), 5); elem = {"not-existing"}; ASSERT_OK(stateMachine()->del(elem.begin(), elem.end(), count, 6)); ASSERT_EQ(count, 0); ASSERT_EQ(stateMachine()->getLastApplied(), 6); ASSERT_OK(stateMachine()->hset("hash2", "key1", "v2", created, 7)); ASSERT_TRUE(created); ASSERT_EQ(stateMachine()->getLastApplied(), 7); ASSERT_NOT_OK(stateMachine()->set("hash2", "wrong type", 8)); ASSERT_EQ(stateMachine()->getLastApplied(), 8); elem = {"hash2", "asdfasdfad"}; ASSERT_OK(stateMachine()->del(elem.begin(), elem.end(), count, 9)); ASSERT_EQ(count, 1); ASSERT_EQ(stateMachine()->getLastApplied(), 9); int64_t added; RedisRequest elementsToAdd { "elem1", "elem2" }; ASSERT_OK(stateMachine()->sadd("set1", elementsToAdd.begin(), elementsToAdd.end(), added, 10)); ASSERT_EQ(added, 2); ASSERT_EQ(stateMachine()->getLastApplied(), 10); int64_t removed; RedisRequest elementsToRem { "elem2", "elem3" }; ASSERT_OK(stateMachine()->srem("set1", elementsToRem.begin(), elementsToRem.end(), removed, 11)); ASSERT_EQ(removed, 1); ASSERT_EQ(stateMachine()->getLastApplied(), 11); ASSERT_OK(stateMachine()->noop(12)); ASSERT_EQ(stateMachine()->getLastApplied(), 12); // Now try artificially resetting lastApplied stateMachine()->forceResetLastApplied(32); ASSERT_EQ(stateMachine()->getLastApplied(), 32); ASSERT_OK(stateMachine()->noop(33)); ASSERT_EQ(stateMachine()->getLastApplied(), 33); ASSERT_THROW(stateMachine()->noop(12), FatalException); ASSERT_THROW(stateMachine()->noop(33), FatalException); ASSERT_THROW(stateMachine()->noop(13), FatalException); } TEST_F(State_Machine, test_hincrby) { ASSERT_EQ(stateMachine()->getLastApplied(), 0); int64_t result; ASSERT_OK(stateMachine()->hincrby("myhash", "counter", "1", result, 1)); ASSERT_EQ(result, 1); ASSERT_EQ(stateMachine()->getLastApplied(), 1); ASSERT_NOT_OK(stateMachine()->hincrby("myhash", "counter", "asdf", result, 2)); ASSERT_EQ(stateMachine()->getLastApplied(), 2); ASSERT_OK(stateMachine()->hincrby("myhash", "counter", "5", result, 3)); ASSERT_EQ(result, 6); ASSERT_EQ(stateMachine()->getLastApplied(), 3); bool created; ASSERT_OK(stateMachine()->hset("myhash", "str", "asdf", created, 4)); ASSERT_TRUE(created); ASSERT_EQ(stateMachine()->getLastApplied(), 4); ASSERT_NOT_OK(stateMachine()->hincrby("myhash", "str", "5", result, 5)); ASSERT_EQ(stateMachine()->getLastApplied(), 5); ASSERT_OK(stateMachine()->hincrby("myhash", "counter", "-30", result, 6)); ASSERT_EQ(stateMachine()->getLastApplied(), 6); ASSERT_EQ(result, -24); } TEST_F(State_Machine, test_hsetnx) { ASSERT_EQ(stateMachine()->getLastApplied(), 0); bool created; ASSERT_OK(stateMachine()->hsetnx("myhash", "field", "v1", created, 1)); ASSERT_TRUE(created); ASSERT_EQ(stateMachine()->getLastApplied(), 1); ASSERT_OK(stateMachine()->hsetnx("myhash", "field", "v2", created, 2)); ASSERT_FALSE(created); ASSERT_EQ(stateMachine()->getLastApplied(), 2); std::string value; ASSERT_OK(stateMachine()->hget("myhash", "field", value)); ASSERT_EQ(value, "v1"); } TEST_F(State_Machine, test_hincrbyfloat) { ASSERT_EQ(stateMachine()->getLastApplied(), 0); double result; ASSERT_OK(stateMachine()->hincrbyfloat("myhash", "field", "0.5", result, 1)); ASSERT_EQ(stateMachine()->getLastApplied(), 1); ASSERT_EQ(result, 0.5); std::string tmp; ASSERT_OK(stateMachine()->hget("myhash", "field", tmp)); ASSERT_EQ(tmp, "0.500000"); ASSERT_OK(stateMachine()->hincrbyfloat("myhash", "field", "0.3", result, 2)); ASSERT_EQ(stateMachine()->getLastApplied(), 2); ASSERT_OK(stateMachine()->hget("myhash", "field", tmp)); ASSERT_EQ(tmp, "0.800000"); ASSERT_EQ(result, 0.8); bool created; ASSERT_OK(stateMachine()->hset("myhash", "field2", "not-a-float", created, 3)); ASSERT_TRUE(created); rocksdb::Status st = stateMachine()->hincrbyfloat("myhash", "field2", "0.1", result, 4); ASSERT_EQ(st.ToString(), "Invalid argument: hash value is not a float"); ASSERT_EQ(stateMachine()->getLastApplied(), 4); } TEST_F(State_Machine, basic_sanity) { std::string buffer; std::vector vec, vec2; ASSERT_OK(stateMachine()->set("abc", "cde")); ASSERT_OK(stateMachine()->get("abc", buffer)); ASSERT_EQ(buffer, "cde"); RedisRequest elem = {"abc"}; int64_t count; ASSERT_OK(stateMachine()->del(elem.begin(), elem.end(), count)); ASSERT_EQ(count, 1); ASSERT_NOTFOUND(stateMachine()->get("abc", buffer)); ASSERT_OK(stateMachine()->exists(elem.begin(), elem.end(), count)); ASSERT_EQ(count, 0); elem = {"abc"}; ASSERT_OK(stateMachine()->del(elem.begin(), elem.end(), count)); ASSERT_EQ(count, 0); ASSERT_OK(stateMachine()->set("123", "345")); ASSERT_OK(stateMachine()->set("qwerty", "asdf")); ASSERT_OK(stateMachine()->keys("*", vec)); vec2 = {"123", "qwerty"}; ASSERT_EQ(vec, vec2); ASSERT_OK(stateMachine()->flushall()); elem = {"123", "qwerty" }; ASSERT_OK(stateMachine()->exists(elem.begin(), elem.end(), count)); ASSERT_EQ(count, 0); ASSERT_OK(stateMachine()->keys("*", vec)); ASSERT_EQ(vec.size(), 0u); int64_t num = 0; RedisRequest elements { "qqq" }; ASSERT_OK(stateMachine()->sadd("myset", elements.begin(), elements.end(), num)); ASSERT_EQ(num, 1); ASSERT_OK(stateMachine()->sismember("myset", "qqq")); ASSERT_NOTFOUND(stateMachine()->sismember("myset", "ppp")); num = 0; elements = { "ppp" }; ASSERT_OK(stateMachine()->sadd("myset", elements.begin(), elements.end(), num)); ASSERT_EQ(num, 1); num = 0; ASSERT_OK(stateMachine()->sadd("myset", elements.begin(), elements.end(), num)); ASSERT_EQ(num, 0); ASSERT_OK(stateMachine()->sismember("myset", "ppp")); size_t size; ASSERT_OK(stateMachine()->scard("myset", size)); ASSERT_EQ(size, 2u); ASSERT_OK(stateMachine()->smembers("myset", vec)); vec2 = {"ppp", "qqq"}; ASSERT_EQ(vec, vec2); elements = { "ppp" }; ASSERT_OK(stateMachine()->srem("myset", elements.begin(), elements.end(), num)); ASSERT_EQ(num, 1); elements = { "www" }; ASSERT_OK(stateMachine()->srem("myset", elements.begin(), elements.end(), num)); ASSERT_EQ(num, 0); elements = { "ppp" }; ASSERT_OK(stateMachine()->srem("myset", elements.begin(), elements.end(), num)); ASSERT_EQ(num, 0); ASSERT_OK(stateMachine()->scard("myset", size)); ASSERT_EQ(size, 1u); ASSERT_OK(stateMachine()->smembers("myset", vec)); vec2 = {"qqq"}; ASSERT_EQ(vec, vec2); ASSERT_NOTFOUND(stateMachine()->hget("myhash", "123", buffer)); bool created; ASSERT_OK(stateMachine()->hset("myhash", "abc", "123", created)); ASSERT_TRUE(created); ASSERT_OK(stateMachine()->hset("myhash", "abc", "234", created)); ASSERT_FALSE(created); ASSERT_OK(stateMachine()->hset("myhash", "abc", "345", created)); ASSERT_FALSE(created); ASSERT_OK(stateMachine()->hlen("myhash", size)); ASSERT_EQ(size, 1u); ASSERT_OK(stateMachine()->hget("myhash", "abc", buffer)); ASSERT_EQ(buffer, "345"); ASSERT_OK(stateMachine()->hset("myhash", "qqq", "ppp", created)); ASSERT_TRUE(created); ASSERT_OK(stateMachine()->hlen("myhash", size)); ASSERT_EQ(size, 2u); ASSERT_OK(stateMachine()->hexists("myhash", "qqq")); ASSERT_NOTFOUND(stateMachine()->hexists("myhash", "aaa")); ASSERT_OK(stateMachine()->hkeys("myhash", vec)); vec2 = {"abc", "qqq"}; ASSERT_EQ(vec, vec2); ASSERT_OK(stateMachine()->hvals("myhash", vec)); vec2 = {"345", "ppp"}; ASSERT_EQ(vec, vec2); ASSERT_OK(stateMachine()->hgetall("myhash", vec)); vec2 = {"abc", "345", "qqq", "ppp"}; ASSERT_EQ(vec, vec2); ASSERT_OK(stateMachine()->hincrby("myhash", "val", "1", num)); ASSERT_EQ(num, 1); ASSERT_OK(stateMachine()->hincrby("myhash", "val", "3", num)); ASSERT_EQ(num, 4); ASSERT_OK(stateMachine()->hincrby("myhash", "val", "-3", num)); ASSERT_EQ(num, 1); ASSERT_OK(stateMachine()->hlen("myhash", size)); ASSERT_EQ(size, 3u); elements = { "val" }; ASSERT_OK(stateMachine()->hdel("myhash", elements.begin(), elements.end(), count)); ASSERT_EQ(count, 1); ASSERT_OK(stateMachine()->hlen("myhash", size)); ASSERT_EQ(size, 2u); ASSERT_NOTFOUND(stateMachine()->hexists("myhash", "val")); ASSERT_OK(stateMachine()->verifyChecksum()); } TEST_F(State_Machine, consistency_check) { for(size_t i = 1; i < 10; i++) { bool created; ASSERT_OK(stateMachine()->hset("hash", SSTR("f" << i), SSTR("v" << i), created)); ASSERT_TRUE(created); } ASSERT_OK(stateMachine()->verifyChecksum()); ASSERT_EQ(ConsistencyScanner::obtainScanPeriod(*stateMachine()), ConsistencyScanner::kDefaultPeriod); ASSERT_OK(stateMachine()->configSet(ConsistencyScanner::kConfigurationKey, "1")); ASSERT_EQ(ConsistencyScanner::obtainScanPeriod(*stateMachine()), std::chrono::seconds(1)); ASSERT_OK(stateMachine()->configSet(ConsistencyScanner::kConfigurationKey, "asdf")); ASSERT_EQ(ConsistencyScanner::obtainScanPeriod(*stateMachine()), ConsistencyScanner::kDefaultPeriod); ASSERT_OK(stateMachine()->configSet(ConsistencyScanner::kConfigurationKey, std::to_string(60 * 60 * 24))); ASSERT_EQ(ConsistencyScanner::obtainScanPeriod(*stateMachine()), std::chrono::hours(24)); } TEST_F(State_Machine, hscan) { std::vector vec; for(size_t i = 1; i < 10; i++) { bool created; ASSERT_OK(stateMachine()->hset("hash", SSTR("f" << i), SSTR("v" << i), created)); ASSERT_TRUE(created); } std::string newcursor; ASSERT_OK(stateMachine()->hscan("hash", "", 3, newcursor, vec)); ASSERT_EQ(vec, make_vec("f1", "v1", "f2", "v2", "f3", "v3")); ASSERT_EQ(newcursor, "f4"); ASSERT_OK(stateMachine()->hscan("hash", "f4", 4, newcursor, vec)); ASSERT_EQ(vec, make_vec("f4", "v4", "f5", "v5", "f6", "v6", "f7", "v7")); ASSERT_EQ(newcursor, "f8"); ASSERT_OK(stateMachine()->hscan("hash", "f8", 4, newcursor, vec)); ASSERT_EQ(vec, make_vec("f8", "v8", "f9", "v9")); ASSERT_EQ(newcursor, ""); ASSERT_OK(stateMachine()->hscan("hash", "zz", 4, newcursor, vec)); ASSERT_TRUE(vec.empty()); ASSERT_EQ(newcursor, ""); } TEST_F(State_Machine, hmset) { // std::vector vec; RedisRequest vec; for(size_t i = 1; i <= 3; i++) { vec.push_back(SSTR("f" << i)); vec.push_back(SSTR("v" << i)); } ASSERT_OK(stateMachine()->hmset("hash", vec.begin(), vec.end())); for(size_t i = 1; i <= 3; i++) { std::string tmp; ASSERT_OK(stateMachine()->hget("hash", SSTR("f" << i), tmp)); ASSERT_EQ(tmp, SSTR("v" << i)); } size_t size; ASSERT_OK(stateMachine()->hlen("hash", size)); ASSERT_EQ(size, 3u); ASSERT_THROW(stateMachine()->hmset("hash", vec.begin()+1, vec.end()), FatalException); } TEST_F(State_Machine, DequeOperations) { RedisRequest vec = {"item1", "item2", "item3"}; int64_t length; ASSERT_OK(stateMachine()->dequePushFront("my_list", vec.begin(), vec.end(), length)); ASSERT_EQ(length, 3); std::string item; ASSERT_OK(stateMachine()->dequePopFront("my_list", item)); ASSERT_EQ(item, "item3"); ASSERT_OK(stateMachine()->dequePopFront("my_list", item)); ASSERT_EQ(item, "item2"); vec = { "item4" }; ASSERT_OK(stateMachine()->dequePushFront("my_list", vec.begin(), vec.end(), length)); ASSERT_EQ(length, 2); ASSERT_OK(stateMachine()->dequePopFront("my_list", item)); ASSERT_EQ(item, "item4"); ASSERT_OK(stateMachine()->dequePopFront("my_list", item)); ASSERT_EQ(item, "item1"); ASSERT_NOTFOUND(stateMachine()->dequePopFront("my_list", item)); } TEST_F(State_Machine, DequeTrimming) { RedisRequest vec = {"1", "2", "3", "4", "5", "6", "7"}; int64_t length; ASSERT_OK(stateMachine()->dequePushBack("my-deque", vec.begin(), vec.end(), length)); ASSERT_EQ(length, 7); ASSERT_OK(stateMachine()->dequeTrimFront("my-deque", "50", length)); ASSERT_EQ(length, 0); size_t len; ASSERT_OK(stateMachine()->dequeLen("my-deque", len)); ASSERT_EQ(len, 7u); ASSERT_OK(stateMachine()->dequeTrimFront("my-deque", "5", length)); ASSERT_EQ(length, 2); ASSERT_OK(stateMachine()->dequeLen("my-deque", len)); ASSERT_EQ(len, 5u); std::string item; ASSERT_OK(stateMachine()->dequePopFront("my-deque", item)); ASSERT_EQ(item, "3"); ASSERT_OK(stateMachine()->dequePopFront("my-deque", item)); ASSERT_EQ(item, "4"); ASSERT_OK(stateMachine()->dequePopFront("my-deque", item)); ASSERT_EQ(item, "5"); ASSERT_OK(stateMachine()->dequePopFront("my-deque", item)); ASSERT_EQ(item, "6"); ASSERT_OK(stateMachine()->dequePopFront("my-deque", item)); ASSERT_EQ(item, "7"); ASSERT_NOTFOUND(stateMachine()->dequePopFront("my-deque", item)); } TEST_F(State_Machine, DequeOperations2) { RedisRequest vec = {"item1", "item2", "item3", "item4"}; int64_t length; ASSERT_OK(stateMachine()->dequePushBack("my_list", vec.begin(), vec.end(), length)); ASSERT_EQ(length, 4); size_t len; ASSERT_OK(stateMachine()->dequeLen("my_list", len)); ASSERT_EQ(len, 4u); std::string item; ASSERT_OK(stateMachine()->dequePopFront("my_list", item)); ASSERT_EQ(item, "item1"); ASSERT_OK(stateMachine()->dequeLen("my_list", len)); ASSERT_EQ(len, 3u); ASSERT_OK(stateMachine()->dequePopFront("my_list", item)); ASSERT_EQ(item, "item2"); vec = { "item5" }; ASSERT_OK(stateMachine()->dequePushFront("my_list", vec.begin(), vec.end(), length)); ASSERT_EQ(length, 3); ASSERT_OK(stateMachine()->dequePopFront("my_list", item)); ASSERT_EQ(item, "item5"); ASSERT_OK(stateMachine()->dequeLen("my_list", len)); ASSERT_EQ(len, 2u); ASSERT_OK(stateMachine()->dequePopBack("my_list", item)); ASSERT_EQ(item, "item4"); ASSERT_OK(stateMachine()->dequePopFront("my_list", item)); ASSERT_EQ(item, "item3"); ASSERT_NOTFOUND(stateMachine()->dequePopFront("my_list", item)); ASSERT_NOTFOUND(stateMachine()->dequePopBack("my_list", item)); ASSERT_OK(stateMachine()->dequeLen("my_list", len)); ASSERT_EQ(len, 0u); } TEST_F(State_Machine, config) { LogIndex commitIndex = 0; std::string item; ASSERT_NOTFOUND(stateMachine()->configGet("raft.resilvering", item)); ASSERT_OK(stateMachine()->configSet("raft.resilvering", "TRUE", ++commitIndex)); ASSERT_OK(stateMachine()->configGet("raft.resilvering", item)); ASSERT_EQ(item, "TRUE"); ASSERT_OK(stateMachine()->configSet("raft.trimming.step", "123", ++commitIndex)); ASSERT_OK(stateMachine()->configSet("raft.trimming.limit", "1000", ++commitIndex)); ASSERT_OK(stateMachine()->configGet("raft.trimming.step", item)); ASSERT_EQ(item, "123"); ASSERT_OK(stateMachine()->configGet("raft.trimming.limit", item)); ASSERT_EQ(item, "1000"); RedisRequest elem = { "raft.trimming.limit", "raft.trimming.step" }; int64_t count; ASSERT_OK(stateMachine()->exists(elem.begin(), elem.end(), count)); ASSERT_EQ(count, 0u); ASSERT_OK(stateMachine()->set("raft.trimming.step", "evil", ++commitIndex)); ASSERT_OK(stateMachine()->configGet("raft.trimming.step", item)); ASSERT_EQ(item, "123"); elem = {"raft.trimming.limit"}; ASSERT_OK(stateMachine()->exists(elem.begin(), elem.end(), count)); ASSERT_EQ(count, 0); elem = {"raft.trimming.step"}; ASSERT_OK(stateMachine()->exists(elem.begin(), elem.end(), count)); ASSERT_EQ(count, 1); RedisRequest keysToDelete = {"raft.trimming.step"}; int64_t num = 0; ASSERT_OK(stateMachine()->del(keysToDelete.begin(), keysToDelete.end(), num, ++commitIndex)); ASSERT_EQ(num, 1); ASSERT_OK(stateMachine()->configGet("raft.trimming.step", item)); ASSERT_EQ(item, "123"); elem = {"raft.trimming.limit"}; ASSERT_OK(stateMachine()->exists(elem.begin(), elem.end(), count)); ASSERT_EQ(count, 0); ASSERT_OK(stateMachine()->set("random key", "random value", ++commitIndex)); ASSERT_OK(stateMachine()->set("random key 2", "random value 2", ++commitIndex)); std::vector allkeys; ASSERT_OK(stateMachine()->keys("*", allkeys)); ASSERT_EQ(allkeys, make_vec("random key", "random key 2")); ASSERT_OK(stateMachine()->flushall(++commitIndex)); ASSERT_OK(stateMachine()->keys("*", allkeys)); ASSERT_EQ(allkeys, make_vec()); ASSERT_OK(stateMachine()->configGet("raft.trimming.step", item)); ASSERT_EQ(item, "123"); std::vector contents; ASSERT_OK(stateMachine()->configGetall(contents)); ASSERT_EQ(contents, make_vec("raft.resilvering", "TRUE", "raft.trimming.limit", "1000", "raft.trimming.step", "123")); } TEST_F(State_Machine, keys) { ASSERT_OK(stateMachine()->set("one", "1")); ASSERT_OK(stateMachine()->set("two", "2")); ASSERT_OK(stateMachine()->set("three", "3")); ASSERT_OK(stateMachine()->set("four", "4")); std::vector keys; ASSERT_OK(stateMachine()->keys("*o*", keys)); ASSERT_EQ(keys, make_vec("four", "one", "two")); ASSERT_OK(stateMachine()->keys("t??", keys)); ASSERT_EQ(keys, make_vec("two")); ASSERT_OK(stateMachine()->keys("*", keys)); ASSERT_EQ(keys, make_vec("four", "one", "three", "two")); ASSERT_OK(stateMachine()->set("hello", "1")); ASSERT_OK(stateMachine()->set("hallo", "2")); ASSERT_OK(stateMachine()->set("hillo", "3")); ASSERT_OK(stateMachine()->set("hllo", "4")); ASSERT_OK(stateMachine()->set("heeeello", "5")); ASSERT_OK(stateMachine()->keys("h[ae]llo", keys)); ASSERT_EQ(keys, make_vec("hallo", "hello")); ASSERT_OK(stateMachine()->keys("h*llo", keys)); ASSERT_EQ(keys, make_vec("hallo", "heeeello", "hello", "hillo", "hllo")); ASSERT_OK(stateMachine()->keys("h[^e]llo", keys)); ASSERT_EQ(keys, make_vec("hallo", "hillo")); ASSERT_OK(stateMachine()->set("*", "1")); ASSERT_OK(stateMachine()->keys("\\*", keys)); ASSERT_EQ(keys, make_vec("*")); } TEST_F(State_Machine, BatchedWrites) { std::string keyDescr; { StagingArea stagingArea(*stateMachine()); bool fieldcreated; ASSERT_OK(stateMachine()->set(stagingArea, "one", "1")); ASSERT_OK(stateMachine()->set(stagingArea, "two", "2")); ASSERT_OK(stateMachine()->hset(stagingArea, "key", "field", "value", fieldcreated)); ASSERT_TRUE(fieldcreated); ASSERT_OK(stateMachine()->hset(stagingArea, "key", "field", "value", fieldcreated)); ASSERT_FALSE(fieldcreated); ASSERT_OK(stagingArea.readFromWriteBatch("!key", keyDescr)); KeyDescriptor descr(keyDescr); ASSERT_EQ(descr.getKeyType(), KeyType::kHash); ASSERT_EQ(descr.getSize(), 1); stagingArea.commit(1); } std::string val; ASSERT_OK(stateMachine()->get("one", val)); ASSERT_EQ(val, "1"); ASSERT_OK(stateMachine()->get("two", val)); ASSERT_EQ(val, "2"); ASSERT_OK(stateMachine()->hget("key", "field", val)); ASSERT_EQ(val, "value"); StagingArea stagingArea2(*stateMachine()); std::string keyDescr2; ASSERT_NOTFOUND(stagingArea2.readFromWriteBatch("!key", keyDescr2)); ASSERT_OK(stagingArea2.get("!key", keyDescr2)); ASSERT_EQ(keyDescr, keyDescr2); } TEST_F(State_Machine, scan) { ASSERT_OK(stateMachine()->set("key1", "1")); ASSERT_OK(stateMachine()->set("key2", "2")); ASSERT_OK(stateMachine()->set("key3", "3")); ASSERT_OK(stateMachine()->set("key4", "4")); ASSERT_OK(stateMachine()->set("key5", "4")); ASSERT_OK(stateMachine()->set("key6", "4")); ASSERT_OK(stateMachine()->set("otherkey1", "5")); ASSERT_OK(stateMachine()->set("otherkey2", "6")); ASSERT_OK(stateMachine()->set("otherkey3", "7")); ASSERT_OK(stateMachine()->set("otherkey4", "8")); std::string newcursor; std::vector keys; ASSERT_OK(stateMachine()->scan("", "key*", 2, newcursor, keys)); ASSERT_EQ(keys, make_vec("key1", "key2")); ASSERT_EQ(newcursor, "key3"); keys.clear(); ASSERT_OK(stateMachine()->scan(newcursor, "key*", 2, newcursor, keys)); ASSERT_EQ(keys, make_vec("key3", "key4")); ASSERT_EQ(newcursor, "key5"); keys.clear(); ASSERT_OK(stateMachine()->scan(newcursor, "key*", 2, newcursor, keys)); ASSERT_EQ(keys, make_vec("key5", "key6")); ASSERT_EQ(newcursor, ""); keys.clear(); ASSERT_OK(stateMachine()->scan("", "*key1", 2, newcursor, keys)); ASSERT_EQ(keys, make_vec("key1")); ASSERT_EQ(newcursor, "key3"); keys.clear(); ASSERT_OK(stateMachine()->scan(newcursor, "*key1", 2, newcursor, keys)); ASSERT_TRUE(keys.empty()); ASSERT_EQ(newcursor, "key5"); keys.clear(); ASSERT_OK(stateMachine()->scan(newcursor, "*key1", 2, newcursor, keys)); ASSERT_TRUE(keys.empty()); ASSERT_EQ(newcursor, "otherkey1"); keys.clear(); ASSERT_OK(stateMachine()->scan(newcursor, "*key1", 2, newcursor, keys)); ASSERT_EQ(keys, make_vec("otherkey1")); ASSERT_EQ(newcursor, "otherkey3"); keys.clear(); ASSERT_OK(stateMachine()->scan(newcursor, "*key1", 2, newcursor, keys)); ASSERT_TRUE(keys.empty()); ASSERT_EQ(newcursor, ""); ASSERT_OK(stateMachine()->set("aba", "6")); ASSERT_OK(stateMachine()->set("abb", "7")); ASSERT_OK(stateMachine()->set("abc", "8")); ASSERT_OK(stateMachine()->set("abcd", "8")); keys.clear(); ASSERT_OK(stateMachine()->scan("", "ab?", 3, newcursor, keys)); ASSERT_EQ(keys, make_vec("aba", "abb", "abc")); ASSERT_EQ(newcursor, "abcd"); keys.clear(); ASSERT_OK(stateMachine()->scan(newcursor, "ab?", 3, newcursor, keys)); ASSERT_TRUE(keys.empty()); ASSERT_EQ(newcursor, ""); // Using a non-sense cursor keys.clear(); ASSERT_OK(stateMachine()->scan("zz", "ab?", 100, newcursor, keys)); ASSERT_TRUE(keys.empty()); ASSERT_EQ(newcursor, ""); // Match only a single key keys.clear(); ASSERT_OK(stateMachine()->scan("", "abc", 100, newcursor, keys)); ASSERT_EQ(keys, make_vec("abc")); ASSERT_EQ(newcursor, ""); } TEST_F(State_Machine, SnapshotReads) { std::unique_ptr readArea(new StagingArea(*stateMachine(), true)); std::string tmp; ASSERT_NOTFOUND(stateMachine()->get(*readArea, "mykey", tmp)); ASSERT_OK(stateMachine()->set("mykey", "someval")); // readArea still uses the old snapshot, updates to "mykey" should // not be visible. ASSERT_NOTFOUND(stateMachine()->get(*readArea, "mykey", tmp)); // Refresh snapshot. readArea.reset(new StagingArea(*stateMachine(), true)); ASSERT_OK(stateMachine()->get(*readArea, "mykey", tmp)); ASSERT_EQ(tmp, "someval"); ASSERT_OK(stateMachine()->set("mykey-2", "someval-2")); ASSERT_NOTFOUND(stateMachine()->get(*readArea, "mykey-2", tmp)); ASSERT_OK(stateMachine()->get("mykey-2", tmp)); ASSERT_EQ(tmp, "someval-2"); int64_t count = 0; RedisRequest vals = {"mykey", "mykey-2"}; ASSERT_OK(stateMachine()->exists(*readArea, vals.begin(), vals.end(), count)); ASSERT_EQ(count, 1); } TEST_F(State_Machine, Clock) { ClockValue clk; stateMachine()->getClock(clk); ASSERT_EQ(clk, 0u); stateMachine()->advanceClock(ClockValue(123)); stateMachine()->getClock(clk); ASSERT_EQ(clk, 123u); stateMachine()->advanceClock(ClockValue(234)); stateMachine()->getClock(clk); ASSERT_EQ(clk, 234u); ASSERT_THROW(stateMachine()->advanceClock(ClockValue(233)), FatalException); stateMachine()->getClock(clk); ASSERT_EQ(clk, 234u); stateMachine()->advanceClock(ClockValue(234)); stateMachine()->getClock(clk); ASSERT_EQ(clk, 234u); stateMachine()->advanceClock(ClockValue(345)); stateMachine()->getClock(clk); ASSERT_EQ(clk, 345u); } TEST_F(State_Machine, VersionedHash) { uint64_t version; std::vector results, exp; ASSERT_OK(stateMachine()->vhgetall("my-key", results, version)); ASSERT_TRUE(results.empty()); ASSERT_EQ(version, 0u); ASSERT_OK(stateMachine()->vhset("my-key", "f1", "v1", version, 1)); ASSERT_EQ(version, 1u); ASSERT_OK(stateMachine()->vhgetall("my-key", results, version)); exp = { "f1", "v1" }; ASSERT_EQ(results, exp); ASSERT_EQ(version, 1u); ASSERT_OK(stateMachine()->vhset("my-key", "f2", "v2", version, 2)); ASSERT_EQ(version, 2u); ASSERT_OK(stateMachine()->vhgetall("my-key", results, version)); exp = { "f1", "v1", "f2", "v2" }; ASSERT_EQ(results, exp); ASSERT_EQ(version, 2u); ASSERT_OK(stateMachine()->vhset("my-key", "f2", "v3", version, 3)); ASSERT_EQ(version, 3u); ASSERT_OK(stateMachine()->vhgetall("my-key", results, version)); exp = { "f1", "v1", "f2", "v3" }; ASSERT_EQ(results, exp); ASSERT_EQ(version, 3u); ASSERT_OK(stateMachine()->vhset("my-key", "f4", "v4", version, 4)); ASSERT_EQ(version, 4u); ASSERT_OK(stateMachine()->vhgetall("my-key", results, version)); exp = { "f1", "v1", "f2", "v3", "f4", "v4" }; ASSERT_EQ(results, exp); ASSERT_EQ(version, 4u); // Two updates in a single transaction - version must only jump once { StagingArea stagingArea(*stateMachine()); ASSERT_OK(stateMachine()->vhset(stagingArea, "my-key", "f5", "v5", version)); ASSERT_EQ(version, 5u); ASSERT_OK(stateMachine()->vhset(stagingArea, "my-key", "f6", "v1", version)); ASSERT_EQ(version, 5u); stagingArea.commit(5); ASSERT_OK(stateMachine()->vhgetall("my-key", results, version)); exp = {"f1", "v1", "f2", "v3", "f4", "v4", "f5", "v5", "f6", "v1"}; ASSERT_EQ(results, exp); ASSERT_EQ(version, 5u); } } TEST_F(State_Machine, Leases) { ClockValue clk; stateMachine()->getClock(clk); ASSERT_EQ(clk, 0u); { StagingArea stagingArea(*stateMachine()); ExpirationEventIterator iterator(stagingArea); ASSERT_FALSE(iterator.valid()); } LeaseInfo info; ASSERT_EQ(stateMachine()->lease_acquire("my-lease", "some-string", ClockValue(1), 10, info), LeaseAcquisitionStatus::kAcquired); ASSERT_EQ(info.getDeadline(), 11u); ASSERT_EQ(info.getLastRenewal(), 1u); ASSERT_EQ(info.getValue(), "some-string"); stateMachine()->getClock(clk); ASSERT_EQ(clk, 1u); { StagingArea stagingArea(*stateMachine()); ExpirationEventIterator iterator(stagingArea); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 11u); ASSERT_EQ(iterator.getRedisKey(), "my-lease"); iterator.next(); ASSERT_FALSE(iterator.valid()); } ASSERT_EQ(stateMachine()->lease_acquire("my-lease", "some-string", ClockValue(9), 10, info), LeaseAcquisitionStatus::kRenewed ); ASSERT_EQ(info.getDeadline(), 19u); ASSERT_EQ(info.getLastRenewal(), 9u); ASSERT_EQ(info.getValue(), "some-string"); stateMachine()->getClock(clk); ASSERT_EQ(clk, 9u); { StagingArea stagingArea(*stateMachine()); ExpirationEventIterator iterator(stagingArea); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 19u); ASSERT_EQ(iterator.getRedisKey(), "my-lease"); iterator.next(); ASSERT_FALSE(iterator.valid()); } ASSERT_EQ(stateMachine()->lease_acquire("my-lease", "some-other-string", ClockValue(12), 10, info), LeaseAcquisitionStatus::kFailedDueToOtherOwner); ASSERT_EQ(info.getDeadline(), 19u); ASSERT_EQ(info.getLastRenewal(), 9u); ASSERT_EQ(info.getValue(), "some-string"); stateMachine()->getClock(clk); ASSERT_EQ(clk, 12u); ASSERT_EQ(stateMachine()->lease_acquire("my-lease-2", "some-other-string", ClockValue(13), 10, info), LeaseAcquisitionStatus::kAcquired); ASSERT_EQ(info.getDeadline(), 23u); ASSERT_EQ(info.getLastRenewal(), 13u); ASSERT_EQ(info.getValue(), "some-other-string"); { StagingArea stagingArea(*stateMachine()); ExpirationEventIterator iterator(stagingArea); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 19u); ASSERT_EQ(iterator.getRedisKey(), "my-lease"); iterator.next(); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 23u); ASSERT_EQ(iterator.getRedisKey(), "my-lease-2"); iterator.next(); ASSERT_FALSE(iterator.valid()); ClockValue staticClock; ClockValue dynamicClock; std::vector events; stateMachine()->lease_get_pending_expiration_events(stagingArea, staticClock, dynamicClock, events); ASSERT_EQ(staticClock, 13u); ASSERT_EQ(events.size(), 2u); ASSERT_EQ(events[0].key, "my-lease"); ASSERT_EQ(events[0].deadline, 19u); ASSERT_EQ(events[1].key, "my-lease-2"); ASSERT_EQ(events[1].deadline, 23u); } ASSERT_OK(stateMachine()->lease_release("my-lease-2", ClockValue(13))); int64_t count = 0; RedisRequest keys = { "my-lease-2" }; ASSERT_OK(stateMachine()->exists(keys.begin(), keys.end(), count) ); ASSERT_EQ(count, 0); ASSERT_NOTFOUND(stateMachine()->lease_release("not-existing", ClockValue(13))); { StagingArea stagingArea(*stateMachine()); DescriptorLocator locator("my-lease"); std::string tmp; ASSERT_OK(stagingArea.get(locator.toView(), tmp)); KeyDescriptor descr(tmp); ASSERT_EQ(descr.getSize(), 11u); ASSERT_EQ(descr.getStartIndex(), 9u); ASSERT_EQ(descr.getEndIndex(), 19u); } { StagingArea stagingArea(*stateMachine()); ExpirationEventIterator iterator(stagingArea); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 19u); ASSERT_EQ(iterator.getRedisKey(), "my-lease"); iterator.next(); ASSERT_FALSE(iterator.valid()); } ASSERT_EQ(stateMachine()->lease_acquire("my-lease-3", "some-other-string", ClockValue(18), 10, info), LeaseAcquisitionStatus::kAcquired); ASSERT_EQ(info.getDeadline(), 28u); ASSERT_EQ(info.getLastRenewal(), 18u); ASSERT_EQ(info.getValue(), "some-other-string"); ASSERT_EQ(stateMachine()->lease_acquire("my-lease-4", "some-other-string", ClockValue(18), 10, info), LeaseAcquisitionStatus::kAcquired); ASSERT_EQ(info.getDeadline(), 28u); ASSERT_EQ(info.getLastRenewal(), 18u); ASSERT_EQ(info.getValue(), "some-other-string"); stateMachine()->getClock(clk); ASSERT_EQ(clk, 18u); { StagingArea stagingArea(*stateMachine()); ExpirationEventIterator iterator(stagingArea); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 19u); ASSERT_EQ(iterator.getRedisKey(), "my-lease"); iterator.next(); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 28u); ASSERT_EQ(iterator.getRedisKey(), "my-lease-3"); iterator.next(); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 28u); ASSERT_EQ(iterator.getRedisKey(), "my-lease-4"); iterator.next(); ASSERT_FALSE(iterator.valid()); } ASSERT_EQ(stateMachine()->lease_acquire("my-lease-4", "some-other-string", ClockValue(25), 10, info), LeaseAcquisitionStatus::kRenewed); ASSERT_EQ(info.getDeadline(), 35u); ASSERT_EQ(info.getLastRenewal(), 25u); ASSERT_EQ(info.getValue(), "some-other-string"); { StagingArea stagingArea(*stateMachine()); ExpirationEventIterator iterator(stagingArea); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 28u); ASSERT_EQ(iterator.getRedisKey(), "my-lease-3"); iterator.next(); ASSERT_TRUE(iterator.valid()); ASSERT_EQ(iterator.getDeadline(), 35u); ASSERT_EQ(iterator.getRedisKey(), "my-lease-4"); iterator.next(); ASSERT_FALSE(iterator.valid()); } ASSERT_OK(stateMachine()->lease_get("my-lease-4", ClockValue(25), info)); ASSERT_EQ(info.getLastRenewal(), ClockValue(25)); ASSERT_EQ(info.getDeadline(), ClockValue(35)); ASSERT_EQ(info.getValue(), "some-other-string"); ASSERT_NOTFOUND(stateMachine()->lease_get("does-not-exist", ClockValue(25), info)); } TEST(StateMachine, RawScanTombstones) { ASSERT_EQ(system("rm -rf /tmp/quarkdb-tombstone-scan-test"), 0); StateMachine stateMachine("/tmp/quarkdb-tombstone-scan-test"); ASSERT_OK(stateMachine.set("test-key", "test-data")); int64_t removed; RedisRequest todel = {"test-key"}; ASSERT_OK(stateMachine.del(todel.begin(), todel.end(), removed)); ASSERT_EQ(removed, 1); std::vector elements; ASSERT_OK(stateMachine.rawScanTombstones("", 10, elements));; ASSERT_EQ(elements.size(), 2u); ASSERT_EQ(elements[0], "!test-key"); ASSERT_EQ(elements[1], "atest-key"); } static std::string sliceToString(const std::string_view &slice) { return std::string(slice.data(), slice.size()); } void assertEqualDescriptors(KeyDescriptor &desc, KeyDescriptor &desc2) { ASSERT_EQ(desc.getKeyType(), desc2.getKeyType()); ASSERT_EQ(desc, desc2); ASSERT_EQ(desc2, desc); ASSERT_EQ(desc.serialize(), desc2.serialize()); } TEST(KeyDescriptor, BasicSanity) { KeyDescriptor stringDesc; ASSERT_THROW(stringDesc.serialize(), FatalException); stringDesc.setKeyType(KeyType::kString); stringDesc.setSize(3); ASSERT_THROW(stringDesc.setStartIndex(2), FatalException); ASSERT_THROW(stringDesc.setStartIndex(4), FatalException); ASSERT_EQ(stringDesc, stringDesc); KeyDescriptor stringDesc2(sliceToString(stringDesc.serialize())); ASSERT_EQ(stringDesc2.getKeyType(), KeyType::kString); assertEqualDescriptors(stringDesc, stringDesc2); KeyDescriptor hashDesc; hashDesc.setKeyType(KeyType::kHash); hashDesc.setSize(7); ASSERT_THROW(hashDesc.setStartIndex(2), FatalException); ASSERT_THROW(hashDesc.setStartIndex(4), FatalException); KeyDescriptor hashDesc2(sliceToString(hashDesc.serialize())); ASSERT_EQ(hashDesc2.getKeyType(), KeyType::kHash); assertEqualDescriptors(hashDesc, hashDesc2); ASSERT_FALSE(stringDesc == hashDesc); KeyDescriptor listDesc; listDesc.setKeyType(KeyType::kDeque); listDesc.setSize(10); listDesc.setStartIndex(1500); listDesc.setEndIndex(1000); ASSERT_THROW(listDesc.serialize(), FatalException); listDesc.setEndIndex(1600); KeyDescriptor listDesc2(sliceToString(listDesc.serialize())); assertEqualDescriptors(listDesc, listDesc2); KeyDescriptor setDesc; setDesc.setKeyType(KeyType::kSet); setDesc.setSize(9); ASSERT_THROW(setDesc.setStartIndex(2), FatalException); ASSERT_THROW(setDesc.setStartIndex(4), FatalException); KeyDescriptor setDesc2(sliceToString(setDesc.serialize())); ASSERT_EQ(setDesc2.getKeyType(), KeyType::kSet); ASSERT_EQ(setDesc2.getSize(), 9); assertEqualDescriptors(setDesc, setDesc2); KeyDescriptor leaseDescr; leaseDescr.setKeyType(KeyType::kLease); leaseDescr.setSize(10); leaseDescr.setStartIndex(10); leaseDescr.setEndIndex(15); KeyDescriptor leaseDescr2(sliceToString(leaseDescr.serialize())); ASSERT_EQ(leaseDescr.getKeyType(), KeyType::kLease); ASSERT_EQ(leaseDescr.getStartIndex(), 10u); ASSERT_EQ(leaseDescr.getEndIndex(), 15u); } TEST(KeyDescriptor, VersionedHash) { KeyDescriptor vhash; vhash.setKeyType(KeyType::kVersionedHash); vhash.setSize(19); vhash.setStartIndex(99); ASSERT_THROW(vhash.setEndIndex(11), FatalException); ASSERT_EQ(vhash.getKeyType(), KeyType::kVersionedHash); ASSERT_EQ(vhash.getSize(), 19); ASSERT_EQ(vhash.getStartIndex(), 99u); ASSERT_THROW(vhash.getEndIndex(), FatalException); KeyDescriptor vhash2(vhash.serialize()); ASSERT_TRUE(vhash == vhash2); ASSERT_EQ(vhash2.getSize(), 19); ASSERT_EQ(vhash2.getStartIndex(), 99u); ASSERT_THROW(vhash2.getEndIndex(), FatalException); } TEST(KeyType, AsString) { ASSERT_THROW(keyTypeAsString(KeyType::kParseError), FatalException); ASSERT_EQ(keyTypeAsString(KeyType::kNull), "none"); ASSERT_EQ(keyTypeAsString(KeyType::kLease), "lease"); ASSERT_EQ(keyTypeAsString(KeyType::kString), "string"); ASSERT_EQ(keyTypeAsString(KeyType::kSet), "set"); ASSERT_EQ(keyTypeAsString(KeyType::kHash), "hash"); ASSERT_EQ(keyTypeAsString(KeyType::kLocalityHash), "locality hash"); ASSERT_EQ(keyTypeAsString(KeyType::kDeque), "deque"); } TEST(FieldLocator, BasicSanity) { FieldLocator locator1(KeyType::kHash, "some_key"); locator1.resetField("my_field"); ASSERT_EQ(locator1.toView(), SSTR(char(KeyType::kHash) << "some_key##my_field")); FieldLocator locator2(KeyType::kSet, "key#with#hashes"); locator2.resetField("field#with#hashes"); ASSERT_EQ(locator2.toView(), SSTR(char(KeyType::kSet) << "key|#with|#hashes##field#with#hashes")); ASSERT_EQ(locator2.getPrefix(), SSTR(char(KeyType::kSet) << "key|#with|#hashes##")); FieldLocator locator3(KeyType::kSet, "evil#key|"); locator3.resetField("evil#field"); ASSERT_EQ(locator3.toView(), SSTR(char(KeyType::kSet) << "evil|#key|##evil#field")); ASSERT_EQ(locator3.getPrefix(), SSTR(char(KeyType::kSet) << "evil|#key|##")); } TEST(FieldLocator, VersionedHash) { FieldLocator locator(KeyType::kVersionedHash, "my_versioned_hash"); locator.resetField("some-field"); ASSERT_EQ(locator.toView(), SSTR(char(KeyType::kVersionedHash) << "my_versioned_hash##some-field")); ASSERT_EQ(locator.getPrefix(), SSTR(char(KeyType::kVersionedHash) << "my_versioned_hash##")); } TEST(ReverseLocator, BasicSanity) { FieldLocator locator1(KeyType::kHash, "some_key"); locator1.resetField("some_field"); ReverseLocator revlocator(locator1.toView()); ASSERT_EQ(revlocator.getKeyType(), KeyType::kHash); ASSERT_EQ(revlocator.getOriginalKey(), "some_key"); ASSERT_EQ(revlocator.getField(), "some_field"); ASSERT_EQ(revlocator.getRawPrefixUntilBoundary(), locator1.getPrefix()); const std::string evilkey("evil#key#with|#hashes#|###"); FieldLocator locator2(KeyType::kSet, evilkey); locator2.resetField("field#with#hashes"); revlocator = ReverseLocator(locator2.toView()); ASSERT_EQ(revlocator.getKeyType(), KeyType::kSet); ASSERT_EQ(revlocator.getOriginalKey(), evilkey); ASSERT_EQ(revlocator.getRawPrefixUntilBoundary(), locator2.getPrefix()); ASSERT_EQ(revlocator.getField(), "field#with#hashes"); StringLocator locator3("random_string###|###"); revlocator = ReverseLocator(locator3.toView()); ASSERT_EQ(revlocator.getKeyType(), KeyType::kString); ASSERT_EQ(revlocator.getOriginalKey(), "random_string###|###"); ASSERT_THROW(revlocator.getRawPrefixUntilBoundary(), FatalException); ASSERT_THROW(revlocator.getField(), FatalException); revlocator = ReverseLocator("zdfdas"); ASSERT_EQ(revlocator.getKeyType(), KeyType::kParseError); revlocator = ReverseLocator(SSTR(char(KeyType::kHash) << "abc#bcd")); ASSERT_EQ(revlocator.getKeyType(), KeyType::kParseError); } TEST(LocalityFieldLocator, BasicSanity) { LocalityFieldLocator locator1("some_key"); ASSERT_EQ(locator1.toView(), "esome_key##d"); ReverseLocator revlocator(locator1.toView()); ASSERT_EQ(revlocator.getOriginalKey(), "some_key"); ASSERT_EQ(revlocator.getKeyType(), KeyType::kLocalityHash); ASSERT_THROW(LocalityFieldLocator(""), FatalException); ASSERT_THROW(locator1.resetField("aaa"), FatalException); // need to specify hint first locator1.resetHint("my-locality-hint"); ASSERT_EQ(locator1.toView(), "esome_key##dmy-locality-hint##"); revlocator = ReverseLocator(locator1.toView()); ASSERT_EQ(revlocator.getOriginalKey(), "some_key"); ASSERT_EQ(revlocator.getKeyType(), KeyType::kLocalityHash); locator1.resetField("field##with##hashes"); ASSERT_EQ(locator1.toView(), "esome_key##dmy-locality-hint##field##with##hashes"); revlocator = ReverseLocator(locator1.toView()); ASSERT_EQ(revlocator.getOriginalKey(), "some_key"); ASSERT_EQ(revlocator.getKeyType(), KeyType::kLocalityHash); locator1.resetHint("evil-hint##with##hashes"); locator1.resetField("a-field"); ASSERT_EQ(locator1.toView(), "esome_key##devil-hint|#|#with|#|#hashes##a-field"); revlocator = ReverseLocator(locator1.toView()); ASSERT_EQ(revlocator.getOriginalKey(), "some_key"); ASSERT_EQ(revlocator.getKeyType(), KeyType::kLocalityHash); locator1.resetKey("#evil#key#"); locator1.resetHint("#evil#hint#"); locator1.resetField("#evil#field#"); ASSERT_EQ(locator1.toView(), "e|#evil|#key|###d|#evil|#hint|####evil#field#"); revlocator = ReverseLocator(locator1.toView()); ASSERT_EQ(revlocator.getOriginalKey(), "#evil#key#"); ASSERT_EQ(revlocator.getKeyType(), KeyType::kLocalityHash); locator1.resetKey("my-key"); locator1.resetHint("my-hint"); locator1.resetField("my-field"); ASSERT_EQ(locator1.toView(), "emy-key##dmy-hint##my-field"); revlocator = ReverseLocator(locator1.toView()); ASSERT_EQ(revlocator.getOriginalKey(), "my-key"); ASSERT_EQ(revlocator.getKeyType(), KeyType::kLocalityHash); } TEST(LocalityIndexLocator, BasicSanity) { LocalityIndexLocator locator1("my-key", "my-field"); ASSERT_EQ(locator1.toView(), "emy-key##imy-field"); ReverseLocator revlocator(locator1.toView()); ASSERT_EQ(revlocator.getKeyType(), KeyType::kLocalityHash); ASSERT_EQ(revlocator.getOriginalKey(), "my-key"); locator1.resetKey("key##with##hashes"); ASSERT_EQ(locator1.toView(), "ekey|#|#with|#|#hashes##i"); revlocator = ReverseLocator(locator1.toView()); ASSERT_EQ(revlocator.getKeyType(), KeyType::kLocalityHash); ASSERT_EQ(revlocator.getOriginalKey(), "key##with##hashes"); locator1.resetField("aaaaa"); ASSERT_EQ(locator1.toView(), "ekey|#|#with|#|#hashes##iaaaaa"); revlocator = ReverseLocator(locator1.toView()); ASSERT_EQ(revlocator.getKeyType(), KeyType::kLocalityHash); ASSERT_EQ(revlocator.getOriginalKey(), "key##with##hashes"); } TEST(LeaseLocator, BasicSanity) { LeaseLocator locator1("my-key"); ASSERT_EQ(locator1.toView(), "fmy-key"); LeaseLocator locator2("my#key"); ASSERT_EQ(locator2.toView(), "fmy#key"); } TEST(ExpirationEventLocator, BasicSanity) { ExpirationEventLocator locator1(ClockValue(123u), "some-key"); ASSERT_EQ(locator1.toView(), SSTR("@" << unsignedIntToBinaryString(123u) << "some-key")); } TEST(ConfigurationLocator, BasicSanity) { ConfigurationLocator locator("test-test-##-test"); ASSERT_EQ(locator.toView(), "~test-test-##-test"); } TEST(PatternMatching, BasicSanity) { ASSERT_EQ(extractPatternPrefix("abc*"), "abc"); ASSERT_EQ(extractPatternPrefix("abc"), "abc"); ASSERT_EQ(extractPatternPrefix("ab?abc"), "ab"); ASSERT_EQ(extractPatternPrefix("1234[a-z]*134"), "1234"); ASSERT_EQ(extractPatternPrefix("?134"), ""); } TEST(EscapedPrefixExtractor, CrashCase) { EscapedPrefixExtractor ex1; ASSERT_TRUE(ex1.parse("my|##key")); ASSERT_EQ(ex1.getOriginalPrefix(), "my|"); ASSERT_EQ(ex1.getRawPrefix(), "my|"); ASSERT_EQ(ex1.getRawSuffix(), "key"); ASSERT_TRUE(ex1.parse("m|#y|##key")); ASSERT_EQ(ex1.getOriginalPrefix(), "m#y|"); ASSERT_EQ(ex1.getRawPrefix(), "m|#y|"); ASSERT_EQ(ex1.getRawSuffix(), "key"); ASSERT_TRUE(ex1.parse("my|#|##key")); ASSERT_EQ(ex1.getOriginalPrefix(), "my#|"); ASSERT_EQ(ex1.getRawPrefix(), "my|#|"); ASSERT_EQ(ex1.getRawSuffix(), "key"); ASSERT_TRUE(ex1.parse("my|#||###key")); ASSERT_EQ(ex1.getOriginalPrefix(), "my#|#"); ASSERT_EQ(ex1.getRawPrefix(), "my|#||#"); ASSERT_EQ(ex1.getRawSuffix(), "key"); ASSERT_TRUE(ex1.parse("my|#||####key")); ASSERT_EQ(ex1.getOriginalPrefix(), "my#|#"); ASSERT_EQ(ex1.getRawPrefix(), "my|#||#"); ASSERT_EQ(ex1.getRawSuffix(), "#key"); ASSERT_TRUE(ex1.parse("my|#||###|key")); ASSERT_EQ(ex1.getOriginalPrefix(), "my#|#"); ASSERT_EQ(ex1.getRawPrefix(), "my|#||#"); ASSERT_EQ(ex1.getRawSuffix(), "|key"); ASSERT_TRUE(ex1.parse("my|###key")); ASSERT_EQ(ex1.getOriginalPrefix(), "my#"); ASSERT_EQ(ex1.getRawPrefix(), "my|#"); ASSERT_EQ(ex1.getRawSuffix(), "key"); std::string strangeString("\000\000\000\000\001\261\066\210:|DeltaPhi|##28391063", 29); ASSERT_TRUE(ex1.parse(strangeString)); ASSERT_EQ(ex1.getOriginalPrefix(), std::string("\000\000\000\000\001\261\066\210:|DeltaPhi|", 19)); ASSERT_EQ(ex1.getRawPrefix(), std::string("\000\000\000\000\001\261\066\210:|DeltaPhi|", 19)); ASSERT_EQ(ex1.getRawSuffix(), "28391063"); } TEST(EscapedPrefixExtractor, BasicSanity) { using namespace std::literals; EscapedPrefixExtractor ex1; ASSERT_TRUE(ex1.parse("my##key")); ASSERT_EQ(ex1.getOriginalPrefix(), "my"); ASSERT_EQ(ex1.getRawPrefix(), "my"); ASSERT_EQ(ex1.getRawSuffix(), "key"); ASSERT_EQ(ex1.getBoundary(), 4u); ASSERT_TRUE(ex1.parse("aaaaaaaa##bbbb")); ASSERT_EQ(ex1.getOriginalPrefix(), "aaaaaaaa"); ASSERT_EQ(ex1.getRawPrefix(), "aaaaaaaa"); ASSERT_EQ(ex1.getRawSuffix(), "bbbb"); ASSERT_EQ(ex1.getBoundary(), 10u); ASSERT_TRUE(ex1.parse("adsfas|#aaaaa##bbbb")); ASSERT_EQ(ex1.getOriginalPrefix(), "adsfas#aaaaa"); ASSERT_EQ(ex1.getRawPrefix(), "adsfas|#aaaaa"); ASSERT_EQ(ex1.getRawSuffix(), "bbbb"); ASSERT_EQ(ex1.getBoundary(), 15u); ASSERT_TRUE(ex1.parse("##")); ASSERT_EQ(ex1.getOriginalPrefix(), ""); ASSERT_EQ(ex1.getRawPrefix(), ""); ASSERT_EQ(ex1.getRawSuffix(), ""); ASSERT_EQ(ex1.getBoundary(), 2u); ASSERT_TRUE(ex1.parse("q##")); ASSERT_EQ(ex1.getOriginalPrefix(), "q"); ASSERT_EQ(ex1.getRawPrefix(), "q"); ASSERT_EQ(ex1.getBoundary(), 3u); ASSERT_TRUE(ex1.parse("##qqqq")); ASSERT_EQ(ex1.getOriginalPrefix(), ""); ASSERT_EQ(ex1.getRawPrefix(), ""); ASSERT_EQ(ex1.getRawSuffix(), "qqqq"); ASSERT_EQ(ex1.getBoundary(), 2u); ASSERT_FALSE(ex1.parse("#")); ASSERT_FALSE(ex1.parse("asd")); ASSERT_TRUE(ex1.parse("###")); ASSERT_EQ(ex1.getOriginalPrefix(), ""); ASSERT_EQ(ex1.getRawPrefix(), ""); ASSERT_EQ(ex1.getRawSuffix(), "#"); ASSERT_EQ(ex1.getBoundary(), 2u); ASSERT_TRUE(ex1.parse("test|#bb##aa##bb")); ASSERT_EQ(ex1.getOriginalPrefix(), "test#bb"); ASSERT_EQ(ex1.getRawPrefix(), "test|#bb"); ASSERT_EQ(ex1.getRawSuffix(), "aa##bb"); ASSERT_EQ(ex1.getBoundary(), 10u); }