254 lines
6.6 KiB
C++
254 lines
6.6 KiB
C++
// Copyright 2021, Roman Gershman. All rights reserved.
|
|
// See LICENSE for licensing terms.
|
|
//
|
|
|
|
#include "server/string_family.h"
|
|
|
|
#include "base/gtest.h"
|
|
#include "base/logging.h"
|
|
#include "server/command_registry.h"
|
|
#include "server/conn_context.h"
|
|
#include "server/engine_shard_set.h"
|
|
#include "server/error.h"
|
|
#include "server/test_utils.h"
|
|
#include "server/transaction.h"
|
|
#include "util/uring/uring_pool.h"
|
|
|
|
using namespace testing;
|
|
using namespace std;
|
|
using namespace util;
|
|
using absl::StrCat;
|
|
|
|
namespace dfly {
|
|
|
|
class StringFamilyTest : public BaseFamilyTest {
|
|
protected:
|
|
};
|
|
|
|
TEST_F(StringFamilyTest, SetGet) {
|
|
auto resp = Run({"set", "key", "val"});
|
|
|
|
EXPECT_THAT(resp, RespEq("OK"));
|
|
EXPECT_THAT(Run({"get", "key"}), RespEq("val"));
|
|
EXPECT_THAT(Run({"set", "key1", "1"}), RespEq("OK"));
|
|
EXPECT_THAT(Run({"get", "key1"}), RespEq("1"));
|
|
EXPECT_THAT(Run({"set", "key", "2"}), RespEq("OK"));
|
|
EXPECT_THAT(Run({"get", "key"}), RespEq("2"));
|
|
}
|
|
|
|
TEST_F(StringFamilyTest, Incr) {
|
|
ASSERT_THAT(Run({"set", "key", "0"}), RespEq("OK"));
|
|
ASSERT_THAT(Run({"incr", "key"}), ElementsAre(IntArg(1)));
|
|
|
|
ASSERT_THAT(Run({"set", "key1", "123456789"}), RespEq("OK"));
|
|
ASSERT_THAT(Run({"incrby", "key1", "0"}), ElementsAre(IntArg(123456789)));
|
|
|
|
ASSERT_THAT(Run({"set", "key1", "-123456789"}), RespEq("OK"));
|
|
ASSERT_THAT(Run({"incrby", "key1", "0"}), ElementsAre(IntArg(-123456789)));
|
|
|
|
ASSERT_THAT(Run({"set", "key1", " -123 "}), RespEq("OK"));
|
|
ASSERT_THAT(Run({"incrby", "key1", "1"}), ElementsAre(ErrArg("ERR value is not an integer")));
|
|
|
|
ASSERT_THAT(Run({"incrby", "ne", "0"}), ElementsAre(IntArg(0)));
|
|
}
|
|
|
|
TEST_F(StringFamilyTest, Expire) {
|
|
constexpr uint64_t kNow = 232279092000;
|
|
|
|
UpdateTime(kNow);
|
|
ASSERT_THAT(Run({"set", "key", "val", "PX", "20"}), RespEq("OK"));
|
|
|
|
UpdateTime(kNow + 10);
|
|
EXPECT_THAT(Run({"get", "key"}), RespEq("val"));
|
|
|
|
UpdateTime(kNow + 20);
|
|
|
|
EXPECT_THAT(Run({"get", "key"}), ElementsAre(ArgType(RespExpr::NIL)));
|
|
|
|
ASSERT_THAT(Run({"set", "i", "1", "PX", "10"}), RespEq("OK"));
|
|
ASSERT_THAT(Run({"incr", "i"}), ElementsAre(IntArg(2)));
|
|
|
|
UpdateTime(kNow + 30);
|
|
ASSERT_THAT(Run({"incr", "i"}), ElementsAre(IntArg(1)));
|
|
}
|
|
|
|
TEST_F(StringFamilyTest, Set) {
|
|
auto resp = Run({"set", "foo", "bar", "XX"});
|
|
EXPECT_THAT(resp, ElementsAre(ArgType(RespExpr::NIL)));
|
|
|
|
resp = Run({"set", "foo", "bar", "NX"});
|
|
ASSERT_THAT(resp, RespEq("OK"));
|
|
resp = Run({"set", "foo", "bar", "NX"});
|
|
EXPECT_THAT(resp, ElementsAre(ArgType(RespExpr::NIL)));
|
|
|
|
resp = Run({"set", "foo", "bar", "xx"});
|
|
ASSERT_THAT(resp, RespEq("OK"));
|
|
|
|
resp = Run({"set", "foo", "bar", "ex", "abc"});
|
|
ASSERT_THAT(resp, ElementsAre(ErrArg(kInvalidIntErr)));
|
|
|
|
resp = Run({"set", "foo", "bar", "ex", "-1"});
|
|
ASSERT_THAT(resp, ElementsAre(ErrArg("invalid expire time in set")));
|
|
|
|
resp = Run({"set", "foo", "bar", "ex", "1"});
|
|
ASSERT_THAT(resp, RespEq("OK"));
|
|
}
|
|
|
|
TEST_F(StringFamilyTest, MGetSet) {
|
|
Run({"mset", "x", "0", "b", "0"});
|
|
|
|
ASSERT_EQ(2, GetDebugInfo("IO0").shards_count);
|
|
|
|
auto mget_fb = pp_->at(0)->LaunchFiber([&] {
|
|
for (size_t i = 0; i < 1000; ++i) {
|
|
auto resp = Run({"mget", "b", "x"});
|
|
ASSERT_EQ(2, resp.size());
|
|
auto ivec = ToIntArr(resp);
|
|
|
|
ASSERT_GE(ivec[1], ivec[0]);
|
|
}
|
|
});
|
|
|
|
auto set_fb = pp_->at(1)->LaunchFiber([&] {
|
|
for (size_t i = 1; i < 2000; ++i) {
|
|
Run({"set", "x", StrCat(i)});
|
|
Run({"set", "b", StrCat(i)});
|
|
}
|
|
});
|
|
|
|
mget_fb.join();
|
|
set_fb.join();
|
|
}
|
|
|
|
TEST_F(StringFamilyTest, MSetGet) {
|
|
Run({"mset", "x", "0", "y", "0", "a", "0", "b", "0"});
|
|
ASSERT_EQ(2, GetDebugInfo().shards_count);
|
|
|
|
Run({"mset", "x", "0", "y", "0"});
|
|
ASSERT_EQ(1, GetDebugInfo().shards_count);
|
|
|
|
Run({"mset", "x", "1", "b", "5", "x", "0"});
|
|
ASSERT_EQ(2, GetDebugInfo().shards_count);
|
|
|
|
int64_t val = CheckedInt({"get", "x"});
|
|
EXPECT_EQ(0, val);
|
|
|
|
val = CheckedInt({"get", "b"});
|
|
EXPECT_EQ(5, val);
|
|
|
|
auto mset_fb = pp_->at(0)->LaunchFiber([&] {
|
|
for (size_t i = 0; i < 1000; ++i) {
|
|
RespVec resp = Run({"mset", "x", StrCat(i), "b", StrCat(i)});
|
|
ASSERT_THAT(resp, RespEq("OK")) << i;
|
|
}
|
|
});
|
|
|
|
// A problematic order when mset is not atomic: set x, get x, get b (old), set b
|
|
auto get_fb = pp_->at(2)->LaunchFiber([&] {
|
|
for (size_t i = 0; i < 1000; ++i) {
|
|
int64_t x = CheckedInt({"get", "x"});
|
|
int64_t z = CheckedInt({"get", "b"});
|
|
|
|
ASSERT_LE(x, z) << "Inconsistency at " << i;
|
|
}
|
|
});
|
|
|
|
mset_fb.join();
|
|
get_fb.join();
|
|
}
|
|
|
|
|
|
TEST_F(StringFamilyTest, MSetDel) {
|
|
auto mset_fb = pp_->at(0)->LaunchFiber([&] {
|
|
for (size_t i = 0; i < 1000; ++i) {
|
|
Run({"mset", "x", "0", "z", "0"});
|
|
}
|
|
});
|
|
|
|
auto del_fb = pp_->at(2)->LaunchFiber([&] {
|
|
for (size_t i = 0; i < 1000; ++i) {
|
|
CheckedInt({"del", "x", "z"});
|
|
}
|
|
});
|
|
|
|
mset_fb.join();
|
|
del_fb.join();
|
|
}
|
|
|
|
TEST_F(StringFamilyTest, IntKey) {
|
|
Run({"mset", "1", "1", "-1000", "-1000"});
|
|
auto resp = Run({"get", "1"});
|
|
ASSERT_THAT(resp, RespEq("1"));
|
|
}
|
|
|
|
TEST_F(StringFamilyTest, SingleShard) {
|
|
Run({"mset", "x", "1", "y", "1"});
|
|
ASSERT_EQ(1, GetDebugInfo("IO0").shards_count);
|
|
|
|
Run({"mget", "x", "y", "b"});
|
|
ASSERT_EQ(2, GetDebugInfo("IO0").shards_count);
|
|
|
|
auto resp = Run({"mget", "x", "y"});
|
|
ASSERT_EQ(1, GetDebugInfo("IO0").shards_count);
|
|
ASSERT_THAT(ToIntArr(resp), ElementsAre(1, 1));
|
|
|
|
auto mset_fb = pp_->at(0)->LaunchFiber([&] {
|
|
for (size_t i = 0; i < 100; ++i) {
|
|
Run({"mset", "x", "0", "y", "0"});
|
|
}
|
|
});
|
|
|
|
// Specially multiple shards to avoid fast-path.
|
|
auto mget_fb = pp_->at(1)->LaunchFiber([&] {
|
|
for (size_t i = 0; i < 100; ++i) {
|
|
Run({"mget", "x", "b", "y"});
|
|
}
|
|
});
|
|
mset_fb.join();
|
|
mget_fb.join();
|
|
}
|
|
|
|
TEST_F(StringFamilyTest, MSetIncr) {
|
|
/* serialzable orders
|
|
init: x=z=0
|
|
|
|
mset x=z=1
|
|
mset, incr x, incr z = 2, 2
|
|
incr x, mset, incr z = 1, 2
|
|
incr x, incr z, mset = 1, 1
|
|
*/
|
|
|
|
/* unserializable scenario when mset is not atomic with respect to incr x
|
|
set x, incr x, incr z, set z = 2, 1
|
|
*/
|
|
|
|
Run({"mset", "a", "0", "b", "0", "c", "0"});
|
|
ASSERT_EQ(2, GetDebugInfo("IO0").shards_count);
|
|
|
|
auto mset_fb = pp_->at(0)->LaunchFiber([&] {
|
|
for (size_t i = 1; i < 1000; ++i) {
|
|
string base = StrCat(i * 900);
|
|
auto resp = Run({"mset", "b", base, "a", base, "c", base});
|
|
ASSERT_THAT(resp, RespEq("OK"));
|
|
}
|
|
});
|
|
|
|
auto get_fb = pp_->at(1)->LaunchFiber([&] {
|
|
for (unsigned j = 0; j < 900; ++j) {
|
|
int64_t a = CheckedInt({"incr", "a"});
|
|
int64_t b = CheckedInt({"incr", "b"});
|
|
ASSERT_LE(a, b);
|
|
|
|
int64_t c = CheckedInt({"incr", "c"});
|
|
if (a > c) {
|
|
LOG(ERROR) << "Consistency error ";
|
|
}
|
|
ASSERT_LE(a, c);
|
|
}
|
|
});
|
|
mset_fb.join();
|
|
get_fb.join();
|
|
}
|
|
|
|
} // namespace dfly
|