// ---------------------------------------------------------------------- // File: utils.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 #include "raft/RaftCommon.hh" #include "utils/Statistics.hh" #include "utils/IntToBinaryString.hh" #include "utils/ParseUtils.hh" #include "utils/StringUtils.hh" #include "utils/FileUtils.hh" #include "utils/Resilvering.hh" #include "utils/SmartBuffer.hh" #include "utils/CommandParsing.hh" #include "utils/TimeFormatting.hh" #include "utils/Random.hh" #include "utils/AssistedThread.hh" #include "utils/CoreLocalArray.hh" #include "utils/Synchronized.hh" #include "redis/Transaction.hh" #include "redis/Authenticator.hh" #include "redis/LeaseFilter.hh" #include "redis/InternalFilter.hh" #include "redis/RedisEncodedResponse.hh" #include "storage/Randomization.hh" #include "storage/ParanoidManifestChecker.hh" #include "storage/ExpirationEventCache.hh" #include "pubsub/SimplePatternMatcher.hh" #include "pubsub/ThreadSafeMultiMap.hh" #include "pubsub/SubscriptionTracker.hh" #include "memory/RingAllocator.hh" #include "Utils.hh" #include "Formatter.hh" #include "qclient/ResponseBuilder.hh" #include "qclient/QClient.hh" using namespace quarkdb; TEST(Utils, binary_string_int_conversion) { EXPECT_EQ(intToBinaryString(1), std::string("\x00\x00\x00\x00\x00\x00\x00\x01", 8)); EXPECT_EQ(binaryStringToInt("\x00\x00\x00\x00\x00\x00\x00\x01"), 1); EXPECT_EQ(binaryStringToInt(intToBinaryString(1).data()), 1); EXPECT_EQ(binaryStringToInt(intToBinaryString(2).c_str()), 2); EXPECT_EQ(binaryStringToInt(intToBinaryString(123415).c_str()), 123415); EXPECT_EQ(binaryStringToInt(intToBinaryString(17465798).c_str()), 17465798); EXPECT_EQ(binaryStringToInt(intToBinaryString(16583415634).c_str()), 16583415634); EXPECT_EQ(binaryStringToInt(intToBinaryString(-1234169761).c_str()), -1234169761); } TEST(Utils, binary_string_unsigned_int_conversion) { EXPECT_EQ(unsignedIntToBinaryString(1u), std::string("\x00\x00\x00\x00\x00\x00\x00\x01", 8)); EXPECT_EQ(binaryStringToUnsignedInt("\x00\x00\x00\x00\x00\x00\x00\x01"), 1u); EXPECT_EQ(binaryStringToUnsignedInt(unsignedIntToBinaryString(1u).data()), 1u); EXPECT_EQ(binaryStringToUnsignedInt(unsignedIntToBinaryString(2u).c_str()), 2u); EXPECT_EQ(binaryStringToUnsignedInt(unsignedIntToBinaryString(123415u).c_str()), 123415u); EXPECT_EQ(binaryStringToUnsignedInt(unsignedIntToBinaryString(17465798u).c_str()), 17465798u); EXPECT_EQ(binaryStringToUnsignedInt(unsignedIntToBinaryString(16583415634u).c_str()), 16583415634u); EXPECT_EQ(binaryStringToUnsignedInt(unsignedIntToBinaryString(18446744073709551613u).c_str()), 18446744073709551613u); uint64_t big_number = std::numeric_limits::max() / 2; EXPECT_EQ(binaryStringToUnsignedInt(unsignedIntToBinaryString(big_number).c_str()), big_number); } TEST(Utils, pathJoin) { ASSERT_EQ(pathJoin("/home/", "test"), "/home/test"); ASSERT_EQ(pathJoin("/home", "test"), "/home/test"); ASSERT_EQ(pathJoin("", "home"), "/home"); ASSERT_EQ(pathJoin("/home", ""), "/home"); } TEST(Utils, resilvering_event_parsing) { ResilveringEvent event1("f493280d-009e-4388-a7ec-77ce66b77ce9", 123), event2; ASSERT_TRUE(ResilveringEvent::deserialize(event1.serialize(), event2)); ASSERT_EQ(event1, event2); ASSERT_EQ(event1.getID(), event2.getID()); ASSERT_EQ(event1.getStartTime(), event2.getStartTime()); ResilveringEvent event3("a94a3955-be85-4e70-9fea-0f68eb01de89", 456); ASSERT_FALSE(event1 == event3); } TEST(Utils, resilvering_history_parsing) { ResilveringHistory history; history.append(ResilveringEvent("f493280d-009e-4388-a7ec-77ce66b77ce9", 123)); history.append(ResilveringEvent("a94a3955-be85-4e70-9fea-0f68eb01de89", 456)); history.append(ResilveringEvent("56f3dcec-2aa6-4487-b708-e867225d849c", 789)); ResilveringHistory history2; ASSERT_TRUE(ResilveringHistory::deserialize(history.serialize(), history2)); ASSERT_EQ(history, history2); for(size_t i = 0; i < history.size(); i++) { ASSERT_EQ(history.at(i), history2.at(i)); } history2.append(ResilveringEvent("711e8894-ec4e-4f57-9c2c-eb9e260401ff", 890)); ASSERT_FALSE(history == history2); ResilveringHistory history3, history4; ASSERT_TRUE(history3 == history4); ASSERT_FALSE(history == history3); ASSERT_FALSE(history3 == history); } TEST(Utils, ReplicaStatus) { ReplicaStatus replica { RaftServer("localhost", 123), false, -1 }; ASSERT_FALSE(replica.upToDate(1)); ASSERT_FALSE(replica.upToDate(100)); ASSERT_FALSE(replica.upToDate(1000)); ASSERT_FALSE(replica.upToDate(10000)); ASSERT_FALSE(replica.upToDate(100000)); ASSERT_EQ(replica.toString(123), "localhost:123 | OFFLINE"); replica = ReplicaStatus { RaftServer("localhost", 123), true, -1, "9.9.9" }; ASSERT_FALSE(replica.upToDate(1)); ASSERT_FALSE(replica.upToDate(100)); ASSERT_FALSE(replica.upToDate(1000)); ASSERT_FALSE(replica.upToDate(10000)); ASSERT_FALSE(replica.upToDate(100000)); ASSERT_EQ(replica.toString(123), "localhost:123 | ONLINE | LAGGING | LOG-SIZE N/A | VERSION 9.9.9"); replica = ReplicaStatus { RaftServer("localhost", 123), true, 10 }; ASSERT_TRUE(replica.upToDate(100)); ASSERT_TRUE(replica.upToDate(1000)); ASSERT_TRUE(replica.upToDate(10000)); ASSERT_FALSE(replica.upToDate(100000)); ASSERT_EQ(replica.toString(123), "localhost:123 | ONLINE | UP-TO-DATE | LOG-SIZE 10 | VERSION N/A"); ASSERT_EQ(replica.toString(30000), "localhost:123 | ONLINE | UP-TO-DATE | LOG-SIZE 10 | VERSION N/A"); ASSERT_EQ(replica.toString(30009), "localhost:123 | ONLINE | UP-TO-DATE | LOG-SIZE 10 | VERSION N/A"); ASSERT_EQ(replica.toString(30010), "localhost:123 | ONLINE | LAGGING | LOG-SIZE 10 | VERSION N/A"); replica = ReplicaStatus { RaftServer("localhost", 123), true, 10, "9.9.9", "5/15" }; ASSERT_EQ(replica.toString(123), "localhost:123 | ONLINE | RESILVERING-PROGRESS 5/15 | LOG-SIZE 10 | VERSION 9.9.9"); } TEST(Utils, replication_status) { ReplicationStatus status; ReplicaStatus replica { RaftServer("localhost", 123), true, 10000 }; status.addReplica(replica); ASSERT_THROW(status.addReplica(replica), FatalException); replica.target = RaftServer("localhost", 456); replica.logSize = 20000; status.addReplica(replica); replica.target = RaftServer("localhost", 567); replica.online = false; status.addReplica(replica); ASSERT_EQ(status.replicasOnline(), 2u); ASSERT_EQ(status.replicasUpToDate(30000), 2u); ASSERT_EQ(status.replicasUpToDate(40001), 1u); ASSERT_EQ(status.replicasUpToDate(50001), 0u); ASSERT_THROW(status.removeReplica(RaftServer("localhost", 789)), FatalException); status.removeReplica(RaftServer("localhost", 456)); ASSERT_EQ(status.replicasOnline(), 1u); ASSERT_EQ(status.replicasUpToDate(30000), 1u); ASSERT_EQ(status.getReplicaStatus(RaftServer("localhost", 123)).target, RaftServer("localhost", 123)); #pragma GCC diagnostic ignored "-Wunused-value" ASSERT_THROW(status.getReplicaStatus(RaftServer("localhost", 456)).target, FatalException); } TEST(Utils, parseIntegerList) { std::vector res, tmp; ASSERT_TRUE(ParseUtils::parseIntegerList("1,4,7", ",", res)); tmp = {1, 4, 7}; ASSERT_EQ(res, tmp); ASSERT_FALSE(ParseUtils::parseIntegerList("14 - 7", ",", res)); ASSERT_TRUE(ParseUtils::parseIntegerList("147", ",", res)); tmp = {147}; ASSERT_EQ(res, tmp); } template class Smart_Buffer : public testing::Test { protected: T buff; }; typedef ::testing::Types< SmartBuffer<1>, SmartBuffer<2>, SmartBuffer<3>, SmartBuffer<4>, SmartBuffer<5>, SmartBuffer<6>, SmartBuffer<7>, SmartBuffer<8>, SmartBuffer<9>, SmartBuffer<10>, SmartBuffer<11>, SmartBuffer<13>, SmartBuffer<16>, SmartBuffer<20>, SmartBuffer<32>, SmartBuffer<100>, SmartBuffer<128>, SmartBuffer<200>, SmartBuffer<333>> Implementations; TYPED_TEST_SUITE(Smart_Buffer, Implementations); TYPED_TEST(Smart_Buffer, BasicSanity) { std::vector strings; strings.emplace_back("1234"); strings.emplace_back("adfafasfad2y45uahfdgakh"); strings.emplace_back("The quick brown fox jumps over the lazy dog"); strings.emplace_back("1"); strings.emplace_back(256, 'z'); strings.emplace_back("3"); strings.emplace_back(1337, 'y'); strings.emplace_back(3, 'k'); strings.emplace_back("what am i doing"); strings.emplace_back(13, 'f'); for(size_t i = 0; i < strings.size(); i++) { this->buff.resize(strings[i].size()); memcpy(this->buff.data(), strings[i].c_str(), strings[i].size()); ASSERT_EQ(this->buff.toString(), strings[i]); } } TYPED_TEST(Smart_Buffer, Expansion) { std::string contents = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris porttitor urna in diam ultricies semper. Vivamus gravida purus eu erat condimentum, ullamcorper aliquam dui commodo. Fusce id nunc euismod mauris venenatis cursus non vel odio. Aliquam porttitor urna eget nibh cursus, eget ultricies quam sagittis. Donec pulvinar fermentum nunc, id rhoncus justo convallis sed. Donec suscipit quis lectus eget maximus. Etiam ut pharetra odio. Morbi ac nulla rhoncus, placerat quam varius, ultrices justo."; this->buff.resize(1); this->buff[0] = 'L'; size_t prevSize = 1; for(size_t i = 5; i < contents.size(); i++) { ASSERT_EQ(prevSize, this->buff.size()); this->buff.expand(i); // ensure old contents are still there! ASSERT_EQ(memcmp(this->buff.data(), contents.data(), prevSize), 0); // copy over new contents memcpy(this->buff.data(), contents.data(), i); prevSize = i; i += (rand() % 10) + 1; } this->buff.shrink(2); ASSERT_EQ(this->buff.size(), 2u); } TEST(StringUtils, CountOccurences) { ASSERT_EQ(StringUtils::countOccurences("abc", 'a'), 1u); ASSERT_EQ(StringUtils::countOccurences("adfas#abc", '#'), 1u); ASSERT_EQ(StringUtils::countOccurences("adfasabc", '#'), 0u); ASSERT_EQ(StringUtils::countOccurences("#adfa#sabc#", '#'), 3u); } TEST(StringUtils, isPrefix) { std::string target = "1234adfas"; ASSERT_TRUE(StringUtils::isPrefix("1234", target)); ASSERT_TRUE(StringUtils::isPrefix("1", target)); ASSERT_TRUE(StringUtils::isPrefix("", target)); ASSERT_FALSE(StringUtils::isPrefix("2", target)); ASSERT_FALSE(StringUtils::isPrefix("1234adfasAAA", target)); ASSERT_FALSE(StringUtils::isPrefix("ldgfkahgfkadgfaksgfkajg", target)); ASSERT_TRUE(StringUtils::isPrefix("1234adfas", target)); } TEST(StringUtils, EscapeNonPrintable) { ASSERT_TRUE(StringUtils::isPrintable("abc")); ASSERT_FALSE(StringUtils::isPrintable("abc\r\n")); ASSERT_EQ(StringUtils::escapeNonPrintable("abc" "\xab" "abc"), "abc" "\\xAB" "abc"); ASSERT_EQ(StringUtils::escapeNonPrintable("abc"), "abc"); std::string binstr = "abc123"; binstr.push_back('\0'); binstr.push_back(0xff); binstr += "aaa"; ASSERT_EQ(StringUtils::escapeNonPrintable(binstr), "abc123\\x00\\xFFaaa"); } TEST(StringUtils, Base16Encode) { ASSERT_EQ(StringUtils::base16Encode("some-text"), "736f6d652d74657874"); ASSERT_EQ(StringUtils::base16Encode("asdgflhsdfkljh!#$@@$@^SDFA^_^===== ಠ_ಠ"), "61736467666c687364666b6c6a68212324404024405e534446415e5f5e3d3d3d3d3d20e0b2a05fe0b2a0"); ASSERT_EQ(StringUtils::base16Encode("@!!#$SDFGJSFXBV>? region = MemoryRegion::Construct(128); ASSERT_EQ(region->refcount(), 1); ASSERT_EQ(region->size(), 128u); ASSERT_EQ(region->bytesFree(), 128u); ASSERT_EQ(region->bytesConsumed(), 0u); PinnedBuffer ptr1 = region->allocate(8).value(); ASSERT_EQ(region->refcount(), 2); PinnedBuffer ptr2 = region->allocate(16).value(); ASSERT_EQ(region->refcount(), 3); PinnedBuffer ptr3 = region->allocate(3).value(); ASSERT_EQ(region->refcount(), 4); ASSERT_EQ(ptr1.data()+8, ptr2.data()); ASSERT_EQ(ptr2.data()+16, ptr3.data()); ASSERT_EQ(region->bytesConsumed(), 27u); ASSERT_EQ(region->bytesFree(), 101u); region->resetAllocations(); PinnedBuffer ptr4 = region->allocate(4).value(); ASSERT_EQ(ptr4.data(), ptr1.data()); ASSERT_EQ(region->refcount(), 5); ASSERT_EQ(region->bytesConsumed(), 4u); ASSERT_EQ(region->bytesFree(), 124u); ASSERT_FALSE(region->allocate(125u).has_value()); PinnedBuffer ptr5 = region->allocate(124u).value(); ASSERT_EQ(ptr4.data() + 4, ptr5.data()); ASSERT_FALSE(region->allocate(1u).has_value()); ASSERT_EQ(region->refcount(), 6); ASSERT_EQ(region->bytesFree(), 0u); ASSERT_EQ(region->bytesConsumed(), 128u); } TEST(PinnedBuffer, substr) { std::shared_ptr region = MemoryRegion::Construct(128); ASSERT_EQ(region->refcount(), 1u); PinnedBuffer buf1 = region->allocate(10).value(); buf1[0] = 'a'; buf1[9] = 'b'; for(size_t i = 1; i < 9; i++) { buf1[i] = 'c'; } ASSERT_EQ(buf1, "accccccccb"); ASSERT_EQ(region->refcount(), 2u); PinnedBuffer buf2 = buf1.substr(0, 3); ASSERT_EQ(region->refcount(), 3u); ASSERT_EQ(buf2, "acc"); PinnedBuffer buf3 = buf1.substr(1, 9); ASSERT_EQ(region->refcount(), 4u); ASSERT_EQ(buf3, "ccccccccb"); PinnedBuffer buf4(std::string("qwerty")); ASSERT_EQ(buf4, "qwerty"); PinnedBuffer buf5 = buf4.substr(1, 3); ASSERT_EQ(buf5, "wer"); ASSERT_TRUE(buf5.usingInternalBuffer()); } TEST(SimplePatternMatcher, BasicSanity) { SimplePatternMatcher matcher; auto it = matcher.find("aaa"); ASSERT_FALSE(it.valid()); ASSERT_EQ(matcher.size(), 0u); ASSERT_TRUE(matcher.insert("*", 999)); ASSERT_EQ(matcher.size(), 1u); ASSERT_FALSE(matcher.insert("*", 999)); ASSERT_EQ(matcher.size(), 1u); ASSERT_TRUE(matcher.insert("abc", 111)); ASSERT_EQ(matcher.size(), 2u); ASSERT_TRUE(matcher.insert("bbb", 123)); ASSERT_EQ(matcher.size(), 3u); it = matcher.find("aaa"); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "*"); ASSERT_EQ(it.getValue(), 999); it.next(); ASSERT_FALSE(it.valid()); it = matcher.find("abc"); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "*"); ASSERT_EQ(it.getValue(), 999); it.next(); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "abc"); ASSERT_EQ(it.getValue(), 111); it.next(); ASSERT_FALSE(it.valid()); ASSERT_TRUE(matcher.insert("[ab]bc", 222)); ASSERT_EQ(matcher.size(), 4u); it = matcher.find("abc"); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "*"); ASSERT_EQ(it.getValue(), 999); it.next(); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "[ab]bc"); ASSERT_EQ(it.getValue(), 222); it.next(); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "abc"); ASSERT_EQ(it.getValue(), 111); it.next(); ASSERT_FALSE(it.valid()); ASSERT_TRUE(matcher.insert("bbb", 777)); ASSERT_EQ(matcher.size(), 5u); it = matcher.find("bbb"); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "*"); ASSERT_EQ(it.getValue(), 999); it.next(); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "bbb"); ASSERT_EQ(it.getValue(), 123); it.next(); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "bbb"); ASSERT_EQ(it.getValue(), 777); it.next(); ASSERT_FALSE(it.valid()); ASSERT_FALSE(matcher.erase("*", 888)); ASSERT_EQ(matcher.size(), 5u); ASSERT_TRUE(matcher.erase("*", 999)); ASSERT_EQ(matcher.size(), 4u); ASSERT_TRUE(matcher.insert("bb*", 333)); ASSERT_EQ(matcher.size(), 5u); it = matcher.find("bbb"); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "bb*"); ASSERT_EQ(it.getValue(), 333); it.next(); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "bbb"); ASSERT_EQ(it.getValue(), 123); it.next(); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "bbb"); ASSERT_EQ(it.getValue(), 777); it.next(); ASSERT_FALSE(it.valid()); ASSERT_FALSE(matcher.erase("bb*", 222)); ASSERT_EQ(matcher.size(), 5u); ASSERT_TRUE(matcher.erase("bb*", 333)); ASSERT_EQ(matcher.size(), 4u); it = matcher.find("bbb"); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "bbb"); ASSERT_EQ(it.getValue(), 123); ASSERT_TRUE(it.erase()); ASSERT_FALSE(it.erase()); it.next(); ASSERT_EQ(matcher.size(), 3u); ASSERT_TRUE(it.valid()); ASSERT_EQ(it.getPattern(), "bbb"); ASSERT_EQ(it.getValue(), 777); ASSERT_FALSE(matcher.erase("bbb", 123)); ASSERT_EQ(matcher.size(), 3u); ASSERT_TRUE(matcher.erase("bbb", 777)); ASSERT_EQ(matcher.size(), 2u); ASSERT_TRUE(matcher.erase("[ab]bc", 222)); ASSERT_EQ(matcher.size(), 1u); ASSERT_TRUE(matcher.erase("abc", 111)); ASSERT_EQ(matcher.size(), 0u); } TEST(ThreadSafeMultiMap, BasicSanity) { std::vector stageSizesToTest = {1, 2, 3, 4, 5, 6, 7, 10, 20, 100, 1000, 2000 }; ThreadSafeMultiMap mm; auto keyIter = mm.getKeyIterator(); ASSERT_FALSE(keyIter.valid()); ASSERT_EQ(mm.size(), 0u); ASSERT_TRUE(mm.insert("test", 123)); ASSERT_EQ(mm.size(), 1u); ASSERT_TRUE(mm.insert("test", 234)); ASSERT_EQ(mm.size(), 2u); ASSERT_TRUE(mm.insert("test", 333)); ASSERT_EQ(mm.size(), 3u); for(size_t stageSize : stageSizesToTest) { keyIter = mm.getKeyIterator(stageSize); ASSERT_TRUE(keyIter.valid()); ASSERT_EQ(keyIter.getKey(), "test"); keyIter.next(); ASSERT_FALSE(keyIter.valid()); } ASSERT_TRUE(mm.insert("test-2", 111)); ASSERT_EQ(mm.size(), 4u); ASSERT_TRUE(mm.insert("test-3", 999)); ASSERT_EQ(mm.size(), 5u); ASSERT_TRUE(mm.insert("test-4", 888)); ASSERT_EQ(mm.size(), 6u); ASSERT_TRUE(mm.insert("test-4", 777)); ASSERT_EQ(mm.size(), 7u); for(size_t stageSize : stageSizesToTest) { keyIter = mm.getKeyIterator(stageSize); ASSERT_TRUE(keyIter.valid()); ASSERT_EQ(keyIter.getKey(), "test"); keyIter.next(); ASSERT_TRUE(keyIter.valid()); ASSERT_EQ(keyIter.getKey(), "test-2"); keyIter.next(); ASSERT_TRUE(keyIter.valid()); ASSERT_EQ(keyIter.getKey(), "test-3"); keyIter.next(); ASSERT_TRUE(keyIter.valid()); ASSERT_EQ(keyIter.getKey(), "test-4"); keyIter.next(); ASSERT_FALSE(keyIter.valid()); } ASSERT_FALSE(mm.insert("test-2", 111)); ASSERT_EQ(mm.size(), 7u); for(size_t stageSize : stageSizesToTest) { auto matchIter = mm.findMatching("test", stageSize); ASSERT_TRUE(matchIter.valid()); ASSERT_EQ(matchIter.getValue(), 123); matchIter.next(); ASSERT_TRUE(matchIter.valid()); ASSERT_EQ(matchIter.getValue(), 234); matchIter.next(); ASSERT_TRUE(matchIter.valid()); ASSERT_EQ(matchIter.getValue(), 333); matchIter.next(); ASSERT_FALSE(matchIter.valid()); matchIter = mm.findMatching("test-3", stageSize); ASSERT_TRUE(matchIter.valid()); ASSERT_EQ(matchIter.getValue(), 999); matchIter.next(); ASSERT_FALSE(matchIter.valid()); matchIter = mm.findMatching("not-existing", stageSize); ASSERT_FALSE(matchIter.valid()); } auto matchIter1 = mm.findMatching("test-2", 1); auto matchIter2 = mm.findMatching("test-2", 1); ASSERT_TRUE(matchIter1.valid()); ASSERT_TRUE(matchIter1.valid()); ASSERT_EQ(matchIter1.getValue(), 111); ASSERT_EQ(matchIter2.getValue(), 111); ASSERT_TRUE(matchIter1.erase()); ASSERT_FALSE(matchIter1.erase()); auto matchIter3 = mm.findMatching("test-2", 1); ASSERT_FALSE(matchIter3.valid()); ASSERT_EQ(mm.size(), 6u); } TEST(ThreadSafeMultiMap, FullIteration) { std::vector stageSizesToTest = {1, 2, 3, 4, 5, 6, 7, 10, 20, 100, 1000, 2000 }; ThreadSafeMultiMap mm; auto fullIter = mm.getFullIterator(); ASSERT_FALSE(fullIter.valid()); mm.insert("aaa", 123); mm.insert("aaa", 444); mm.insert("aaa", 555); mm.insert("bbb", 111); mm.insert("bbb", 222); mm.insert("ccc", 999); mm.insert("ccc", 888); mm.insert("ddd", 111); fullIter = mm.getFullIterator(); ASSERT_TRUE(fullIter.valid()); ASSERT_EQ(fullIter.getKey(), "aaa"); ASSERT_EQ(fullIter.getValue(), 123); ASSERT_TRUE(mm.erase("aaa", 444)); ASSERT_TRUE(mm.erase("bbb", 111)); fullIter.next(); ASSERT_TRUE(fullIter.valid()); ASSERT_EQ(fullIter.getKey(), "aaa"); ASSERT_EQ(fullIter.getValue(), 444); fullIter.next(); ASSERT_TRUE(fullIter.valid()); ASSERT_EQ(fullIter.getKey(), "aaa"); ASSERT_EQ(fullIter.getValue(), 555); fullIter.next(); ASSERT_TRUE(fullIter.valid()); ASSERT_EQ(fullIter.getKey(), "bbb"); ASSERT_EQ(fullIter.getValue(), 222); fullIter.next(); ASSERT_TRUE(fullIter.valid()); ASSERT_EQ(fullIter.getKey(), "ccc"); ASSERT_EQ(fullIter.getValue(), 888); fullIter.skipKey(); ASSERT_TRUE(fullIter.valid()); ASSERT_EQ(fullIter.getKey(), "ddd"); ASSERT_EQ(fullIter.getValue(), 111); fullIter.next(); ASSERT_FALSE(fullIter.valid()); } TEST(SubscriptionTracker, BasicSanity) { SubscriptionTracker tracker; ASSERT_TRUE(tracker.addChannel("test-1")); ASSERT_FALSE(tracker.addChannel("test-1")); ASSERT_TRUE(tracker.addPattern("test-*")); ASSERT_TRUE(tracker.addPattern("test*")); ASSERT_TRUE(tracker.hasChannel("test-1")); ASSERT_FALSE(tracker.hasChannel("test-2")); ASSERT_TRUE(tracker.hasPattern("test-*")); ASSERT_FALSE(tracker.hasPattern("test-*1")); ASSERT_TRUE(tracker.hasPattern("test*")); ASSERT_TRUE(tracker.removeChannel("test-1")); ASSERT_FALSE(tracker.removeChannel("test-1")); ASSERT_FALSE(tracker.removeChannel("test-2")); ASSERT_TRUE(tracker.removePattern("test-*")); ASSERT_FALSE(tracker.removePattern("test-*")); ASSERT_TRUE(tracker.removePattern("test*")); ASSERT_FALSE(tracker.removePattern("test***")); } struct alignas(CoreLocal::kCacheLine) AlignedStruct { int a; }; TEST(CoreLocalArray, BasicSanity) { CoreLocalArray test; std::cout << "CoreLocalArray size: " << test.size() << std::endl; std::pair local = test.access(); std::cout << "Executing on core #" << local.second << std::endl; local.first->a = 5; ASSERT_EQ(test.accessAtCore(local.second)->a, 5); // Ensure every single element is aligned for(size_t i = 0; i < test.size(); i++) { ASSERT_EQ( ((uintptr_t)test.accessAtCore(i) % CoreLocal::kCacheLine), 0u); } } TEST(Statistics, BasicSanity) { Statistics stats; stats.reads = 10; stats.writes = 20; stats.txreadwrite = 11; Statistics stats2; stats += stats2; ASSERT_EQ(stats.reads, 10); ASSERT_EQ(stats.writes, 20); ASSERT_EQ(stats.txreadwrite, 11); stats2.reads = 1; stats2.writes = 2; stats2.txreadwrite = 3; stats += stats2; ASSERT_EQ(stats.reads, 11); ASSERT_EQ(stats.writes, 22); ASSERT_EQ(stats.txreadwrite, 14); } TEST(StatAggregator, BasicSanity) { StatAggregator aggr; Statistics* stats = aggr.getStats(); ASSERT_EQ(stats->reads, 0); ASSERT_EQ(stats->writes, 0); ASSERT_EQ(stats->txreadwrite, 0); stats->reads += 10; stats->writes += 10; stats->txreadwrite += 10; Statistics overall = aggr.getOverallStats(); ASSERT_EQ(overall.reads, 10); ASSERT_EQ(overall.writes, 10); ASSERT_EQ(overall.txreadwrite, 10); Statistics sinceLast = aggr.getOverallStatsSinceLastTime(); ASSERT_EQ(sinceLast.reads, 10); ASSERT_EQ(sinceLast.writes, 10); ASSERT_EQ(sinceLast.txreadwrite, 10); stats->reads += 30; stats->writes += 90; stats->txreadwrite += 3; sinceLast = aggr.getOverallStatsSinceLastTime(); ASSERT_EQ(sinceLast.reads, 30); ASSERT_EQ(sinceLast.writes, 90); ASSERT_EQ(sinceLast.txreadwrite, 3); } TEST(HistoricalStatistics, BasicSanity) { HistoricalStatistics history(2); std::chrono::system_clock::time_point timepoint; Statistics stats; stats.reads = 90; stats.writes = 80; timepoint += std::chrono::seconds(100); history.push(stats, timepoint); stats.reads = 100; stats.writes = 50; timepoint += std::chrono::seconds(99); history.push(stats, timepoint); stats.reads = 300; stats.writes = 1; timepoint += std::chrono::seconds(300); history.push(stats, timepoint); std::vector headers; std::vector> data; history.serialize(headers, data); qclient::redisReplyPtr ans = qclient::ResponseBuilder::parseRedisEncodedString(Formatter::vectorsWithHeaders(headers, data).val); ASSERT_EQ(qclient::describeRedisReply(ans), "1) 1) TIMESTAMP 499\n" " 2) 1) READS 300\n" " 2) WRITES 1\n" " 3) TXREAD 0\n" " 4) TXREADWRITE 0\n" "2) 1) TIMESTAMP 199\n" " 2) 1) READS 100\n" " 2) WRITES 50\n" " 3) TXREAD 0\n" " 4) TXREADWRITE 0\n" ); } TEST(Synchronized, String) { Synchronized syncstr; ASSERT_EQ(syncstr.get(), ""); syncstr.set("test"); ASSERT_EQ(syncstr.get(), "test"); } TEST(StringUtils, endsWith) { ASSERT_TRUE(StringUtils::endsWith("some-string-123", "3")); ASSERT_TRUE(StringUtils::endsWith("some-string-123", "123")); ASSERT_TRUE(StringUtils::endsWith("some-string-123", "-123")); ASSERT_TRUE(StringUtils::endsWith("some-string-123", "string-123")); ASSERT_FALSE(StringUtils::endsWith("some-string-123", "4")); ASSERT_FALSE(StringUtils::endsWith("some-string-123", "124")); ASSERT_FALSE(StringUtils::endsWith("some-string-123", "-124")); ASSERT_FALSE(StringUtils::endsWith("some-string-123", "strin4-123")); } TEST(FileUtils, RecursiveFileCount) { ASSERT_EQ(system("rm -rf /tmp/qdb-test-filecount/"), 0); ASSERT_EQ(system("mkdir /tmp/qdb-test-filecount/"), 0); ASSERT_EQ(system("touch /tmp/qdb-test-filecount/1"), 0); std::string error; size_t nitems; ASSERT_FALSE(countFilesInDirectoryRecursively("/tmp/qdb-test-filecount/dir1/", error, nitems)); ASSERT_EQ(error, "countFilesInDirectoryRecursively failed, unable to iterate directory: Unable to opendir: /tmp/qdb-test-filecount/dir1/"); ASSERT_TRUE(countFilesInDirectoryRecursively("/tmp/qdb-test-filecount/", error, nitems)); ASSERT_EQ(nitems, 1u); ASSERT_EQ(system("touch /tmp/qdb-test-filecount/2"), 0); ASSERT_TRUE(countFilesInDirectoryRecursively("/tmp/qdb-test-filecount/", error, nitems)); ASSERT_EQ(nitems, 2u); ASSERT_EQ(system("mkdir /tmp/qdb-test-filecount/dir1/"), 0); ASSERT_TRUE(countFilesInDirectoryRecursively("/tmp/qdb-test-filecount/", error, nitems)); ASSERT_EQ(nitems, 2u); ASSERT_EQ(system("touch /tmp/qdb-test-filecount/dir1/3"), 0); ASSERT_TRUE(countFilesInDirectoryRecursively("/tmp/qdb-test-filecount/", error, nitems)); ASSERT_EQ(nitems, 3u); ASSERT_EQ(system("mkdir -p /tmp/qdb-test-filecount/dir1/dir2/dir3/dir4/dir5"), 0); ASSERT_TRUE(countFilesInDirectoryRecursively("/tmp/qdb-test-filecount/", error, nitems)); ASSERT_EQ(nitems, 3u); ASSERT_EQ(system("touch /tmp/qdb-test-filecount/dir1/dir2/dir3/dir4/dir5/4"), 0); ASSERT_TRUE(countFilesInDirectoryRecursively("/tmp/qdb-test-filecount/", error, nitems)); ASSERT_EQ(nitems, 4u); } TEST(ParanoidManifestChecker, BasicSanity) { struct timespec manifest, newestSst; manifest.tv_sec = 10; manifest.tv_nsec = 0; // SST file newer than manifest by more than 1 hour, something is wrong newestSst.tv_sec = 4000; newestSst.tv_nsec = 0; Status st = ParanoidManifestChecker::compareMTimes(manifest, newestSst); ASSERT_FALSE(st.ok()); ASSERT_EQ(st.getMsg(), "3990 sec, sst:4000.0 vs m:10.0"); newestSst.tv_sec = 20; st = ParanoidManifestChecker::compareMTimes(manifest, newestSst); ASSERT_TRUE(st.ok()); ASSERT_EQ(st.getMsg(), "10 sec, sst:20.0 vs m:10.0"); manifest.tv_sec = 30; newestSst.tv_sec = 20; st = ParanoidManifestChecker::compareMTimes(manifest, newestSst); ASSERT_TRUE(st.ok()); ASSERT_EQ(st.getMsg(), "-10 sec, sst:20.0 vs m:30.0"); } TEST(ExpirationEventCache, BasicSanity) { ExpirationEventCache cache; ASSERT_EQ(cache.size(), 0u); cache.insert(1, "my-lease-1"); ASSERT_EQ(cache.size(), 1u); ASSERT_THROW(cache.insert(1, "my-lease-1"), FatalException); ASSERT_THROW(cache.insert(2, "my-lease-1"), FatalException); ASSERT_THROW(cache.insert(0, "my-lease-1"), FatalException); ASSERT_THROW(cache.remove(1, "my-lease-2"), FatalException); ASSERT_EQ(cache.size(), 1u); ASSERT_EQ(cache.getFrontDeadline(), 1u); ASSERT_EQ(cache.getFrontLease(), "my-lease-1"); cache.pop_front(); ASSERT_THROW(cache.pop_front(), FatalException); ASSERT_TRUE(cache.empty()); } TEST(ExpirationEventCache, ThreeLeases) { ExpirationEventCache cache; ASSERT_EQ(cache.size(), 0u); cache.insert(1, "my-lease-1"); cache.insert(1, "my-lease-2"); cache.insert(2, "my-lease-3"); ASSERT_EQ(cache.size(), 3u); ASSERT_EQ(cache.getFrontDeadline(), 1u); ASSERT_EQ(cache.getFrontLease(), "my-lease-1"); cache.pop_front(); ASSERT_EQ(cache.getFrontDeadline(), 1u); ASSERT_EQ(cache.getFrontLease(), "my-lease-2"); cache.pop_front(); ASSERT_EQ(cache.getFrontDeadline(), 2u); ASSERT_EQ(cache.getFrontLease(), "my-lease-3"); cache.pop_front(); ASSERT_TRUE(cache.empty()); } TEST(ExpirationEventCache, ThreeLeasesWithUpdates) { ExpirationEventCache cache; ASSERT_EQ(cache.size(), 0u); cache.insert(1, "my-lease-1"); cache.insert(1, "my-lease-2"); cache.insert(2, "my-lease-3"); ASSERT_EQ(cache.getFrontDeadline(), 1u); ASSERT_EQ(cache.getFrontLease(), "my-lease-1"); cache.remove(1, "my-lease-2"); ASSERT_THROW(cache.remove(1, "my-lease-2"), FatalException); cache.insert(10, "my-lease-2"); ASSERT_EQ(cache.getFrontDeadline(), 1u); ASSERT_EQ(cache.getFrontLease(), "my-lease-1"); cache.pop_front(); ASSERT_EQ(cache.getFrontDeadline(), 2u); ASSERT_EQ(cache.getFrontLease(), "my-lease-3"); cache.pop_front(); ASSERT_EQ(cache.getFrontDeadline(), 10u); ASSERT_EQ(cache.getFrontLease(), "my-lease-2"); cache.pop_front(); ASSERT_TRUE(cache.empty()); } TEST(ExpirationEventCache, OneConstantlyUpdating) { ExpirationEventCache cache; ASSERT_EQ(cache.size(), 0u); std::string leaseName = "my-lease"; cache.insert(0, leaseName); cache.insert(200, "another-lease"); for(size_t i = 1; i < 100; i++) { ASSERT_EQ(cache.size(), 2u); ASSERT_EQ(cache.getFrontDeadline(), i-1); ASSERT_EQ(cache.getFrontLease(), leaseName); cache.remove(i-1, leaseName); cache.insert(i, leaseName); ASSERT_EQ(cache.size(), 2u); ASSERT_EQ(cache.getFrontDeadline(), i); ASSERT_EQ(cache.getFrontLease(), leaseName); } }