// Copyright 2022, DragonflyDB authors. All rights reserved. // See LICENSE for licensing terms. // #include "server/bitops_family.h" #include #include #include #include #include #include #include "absl/strings/str_cat.h" #include "base/gtest.h" #include "base/logging.h" #include "facade/facade_test.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" using namespace testing; using namespace std; using namespace util; using absl::StrCat; namespace dfly { class Bytes { using char_t = std::uint8_t; using string_type = std::basic_string; public: enum State { GOOD, ERROR, NIL }; Bytes(std::initializer_list bytes) : data_(bytes.size(), 0) { // note - we want this to be like its would be used in redis where most significate bit is to // the "left" std::copy(rbegin(bytes), rend(bytes), data_.begin()); } explicit Bytes(unsigned long long n) : data_(sizeof(n), 0) { FromNumber(n); } static Bytes From(unsigned long long x) { return Bytes(x); } explicit Bytes(State state) : state_{state} { } Bytes(const char_t* ch, std::size_t len) : data_(ch, len) { } Bytes(const char* ch, std::size_t len) : Bytes(reinterpret_cast(ch), len) { } explicit Bytes(std::string_view from) : Bytes(from.data(), from.size()) { } static Bytes From(RespExpr&& r); std::size_t Size() const { return data_.size(); } operator std::string_view() const { return std::string_view(reinterpret_cast(data_.data()), Size()); } std::ostream& Print(std::ostream& os) const; std::ostream& PrintHex(std::ostream& os) const; private: template void FromNumber(T num) { // note - we want this to be like its would be used in redis where most significate bit is to // the "left" std::size_t i = 0; for (const char_t* s = reinterpret_cast(&num); i < sizeof(T); s++, i++) { data_[i] = *s; } } string_type data_; State state_ = GOOD; }; Bytes Bytes::From(RespExpr&& r) { if (r.type == RespExpr::STRING) { return Bytes(ToSV(r.GetBuf())); } else { if (r.type == RespExpr::NIL || r.type == RespExpr::NIL_ARRAY) { return Bytes{Bytes::NIL}; } else { return Bytes(Bytes::ERROR); } } } std::ostream& Bytes::Print(std::ostream& os) const { if (state_ == GOOD) { for (auto c : data_) { std::bitset<8> b{c}; os << b << ":"; } } else { if (state_ == NIL) { os << "nil"; } else { os << "error"; } } return os; } std::ostream& Bytes::PrintHex(std::ostream& os) const { if (state_ == GOOD) { for (auto c : data_) { os << std::hex << std::setfill('0') << std::setw(2) << (std::uint16_t)c << ":"; } } else { if (state_ == NIL) { os << "nil"; } else { os << "error"; } } return os; } inline bool operator==(const Bytes& left, const Bytes& right) { return static_cast(left) == static_cast(right); } inline bool operator!=(const Bytes& left, const Bytes& right) { return !(left == right); } inline Bytes operator"" _b(unsigned long long x) { return Bytes::From(x); } inline Bytes operator"" _b(const char* x, std::size_t s) { return Bytes{x, s}; } inline Bytes operator"" _b(const char* x) { return Bytes{x, std::strlen(x)}; } inline std::ostream& operator<<(std::ostream& os, const Bytes& bs) { return bs.PrintHex(os); } class BitOpsFamilyTest : public BaseFamilyTest { protected: // only for bitop XOR, OR, AND tests void BitOpSetKeys(); }; // for the bitop tests we need to test with multiple keys as the issue // is that we need to make sure that accessing multiple shards creates // the correct result // Since this is bit operations, we are using the bytes data type // that makes the verification more ergonomics. const std::pair KEY_VALUES_BIT_OP[] = { {"first_key", 0xFFAACC01_b}, {"key_second", {0x1, 0xBB}}, {"_this_is_the_third_key", {0x01, 0x05, 0x15, 0x20, 0xAA, 0xCC}}, {"the_last_key_we_have", 0xAACC_b}}; // For the bitop XOR OR and AND we are setting these keys/value pairs void BitOpsFamilyTest::BitOpSetKeys() { auto resp = Run({"set", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[0].second}); EXPECT_EQ(resp, "OK"); resp = Run({"set", KEY_VALUES_BIT_OP[1].first, KEY_VALUES_BIT_OP[1].second}); EXPECT_EQ(resp, "OK"); resp = Run({"set", KEY_VALUES_BIT_OP[2].first, KEY_VALUES_BIT_OP[2].second}); EXPECT_EQ(resp, "OK"); resp = Run({"set", KEY_VALUES_BIT_OP[3].first, KEY_VALUES_BIT_OP[3].second}); EXPECT_EQ(resp, "OK"); } const long EXPECTED_VALUE_SETBIT[] = {0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0}; // taken from running this on redis const int32_t ITERATIONS = sizeof(EXPECTED_VALUE_SETBIT) / sizeof(EXPECTED_VALUE_SETBIT[0]); TEST_F(BitOpsFamilyTest, GetBit) { auto resp = Run({"set", "foo", "abc"}); EXPECT_EQ(resp, "OK"); for (int32_t i = 0; i < ITERATIONS; i++) { EXPECT_EQ(EXPECTED_VALUE_SETBIT[i], CheckedInt({"getbit", "foo", std::to_string(i)})); } // make sure that when accessing bit that is not in the range its working and we are // getting 0 EXPECT_EQ(0, CheckedInt({"getbit", "foo", std::to_string(strlen("abc") + 5)})); } TEST_F(BitOpsFamilyTest, SetBitExistingKey) { // this test would test when we have the value in place and // we are overriding and existing key // so there are no allocations of keys auto resp = Run({"set", "foo", "abc"}); EXPECT_EQ(resp, "OK"); // we are setting all to 1s first, we are expecting to get the old values for (int32_t i = 0; i < ITERATIONS; i++) { EXPECT_EQ(EXPECTED_VALUE_SETBIT[i], CheckedInt({"setbit", "foo", std::to_string(i), "1"})); } for (int32_t i = 0; i < ITERATIONS; i++) { EXPECT_EQ(1, CheckedInt({"getbit", "foo", std::to_string(i)})); } } TEST_F(BitOpsFamilyTest, SetBitMissingKey) { // This test would run without pre-allocated existing key // so we need to allocate the key as part of setting the values for (int32_t i = 0; i < ITERATIONS; i++) { // we are setting all to 1s first, we are expecting // get 0s since we didn't have this key before EXPECT_EQ(0, CheckedInt({"setbit", "foo", std::to_string(i), "1"})); } // now all that we set are at 1s for (int32_t i = 0; i < ITERATIONS; i++) { EXPECT_EQ(1, CheckedInt({"getbit", "foo", std::to_string(i)})); } } TEST_F(BitOpsFamilyTest, SetBitIncorrectValues) { EXPECT_EQ(0, CheckedInt({"setbit", "foo", "0", "1"})); EXPECT_THAT(Run({"setbit", "foo", "1", "-1"}), ErrArg("ERR value is not an integer or out of range")); EXPECT_THAT(Run({"setbit", "foo", "2", "11"}), ErrArg("ERR value is not an integer or out of range")); EXPECT_THAT(Run({"setbit", "foo", "3", "a"}), ErrArg("ERR value is not an integer or out of range")); EXPECT_THAT(Run({"setbit", "foo", "4", "O"}), ErrArg("ERR value is not an integer or out of range")); EXPECT_EQ(1, CheckedInt({"getbit", "foo", "0"})); EXPECT_EQ(0, CheckedInt({"getbit", "foo", "1"})); EXPECT_EQ(0, CheckedInt({"getbit", "foo", "2"})); EXPECT_EQ(0, CheckedInt({"getbit", "foo", "3"})); EXPECT_EQ(0, CheckedInt({"getbit", "foo", "4"})); } const int32_t EXPECTED_VALUES_BYTES_BIT_COUNT[] = { // got this from redis 0 as start index 4, 7, 11, 14, 17, 21, 21, 21, 21}; const int32_t BYTES_EXPECTED_VALUE_LEN = sizeof(EXPECTED_VALUES_BYTES_BIT_COUNT) / sizeof(EXPECTED_VALUES_BYTES_BIT_COUNT[0]); TEST_F(BitOpsFamilyTest, BitCountByte) { // This would run without the bit flag - meaning it count on bytes boundaries auto resp = Run({"set", "foo", "farbar"}); EXPECT_EQ(resp, "OK"); EXPECT_EQ(0, CheckedInt({"bitcount", "foo2"})); // on none existing key we are expecting 0 for (int32_t i = 0; i < BYTES_EXPECTED_VALUE_LEN; i++) { EXPECT_EQ(EXPECTED_VALUES_BYTES_BIT_COUNT[i], CheckedInt({"bitcount", "foo", "0", std::to_string(i)})); } EXPECT_EQ(21, CheckedInt({"bitcount", "foo"})); // the total number of bits in this value } TEST_F(BitOpsFamilyTest, BitCountByteSubRange) { // This test test using some sub ranges of bit count on bytes auto resp = Run({"set", "foo", "farbar"}); EXPECT_EQ(resp, "OK"); EXPECT_EQ(3, CheckedInt({"bitcount", "foo", "1", "1"})); EXPECT_EQ(7, CheckedInt({"bitcount", "foo", "1", "2"})); EXPECT_EQ(4, CheckedInt({"bitcount", "foo", "2", "2"})); EXPECT_EQ(0, CheckedInt({"bitcount", "foo", "3", "2"})); // illegal range EXPECT_EQ(10, CheckedInt({"bitcount", "foo", "-3", "-1"})); EXPECT_EQ(13, CheckedInt({"bitcount", "foo", "-5", "-2"})); EXPECT_EQ(0, CheckedInt({"bitcount", "foo", "-1", "-2"})); // illegal range EXPECT_EQ(0, CheckedInt({"bitcount", "foo", "1", "0"})); // illegal range } TEST_F(BitOpsFamilyTest, BitCountByteBitSubRange) { // This test test using some sub ranges of bit count on bytes auto resp = Run({"set", "foo", "abcdef"}); EXPECT_EQ(resp, "OK"); resp = Run({"bitcount", "foo", "bar", "BIT"}); ASSERT_THAT(resp, ErrArg("value is not an integer or out of range")); EXPECT_EQ(1, CheckedInt({"bitcount", "foo", "1", "1", "BIT"})); EXPECT_EQ(2, CheckedInt({"bitcount", "foo", "1", "2", "BIT"})); EXPECT_EQ(1, CheckedInt({"bitcount", "foo", "2", "2", "BIT"})); EXPECT_EQ(0, CheckedInt({"bitcount", "foo", "3", "2", "bit"})); // illegal range EXPECT_EQ(2, CheckedInt({"bitcount", "foo", "-3", "-1", "bit"})); EXPECT_EQ(2, CheckedInt({"bitcount", "foo", "-5", "-2", "bit"})); EXPECT_EQ(4, CheckedInt({"bitcount", "foo", "1", "9", "bit"})); EXPECT_EQ(7, CheckedInt({"bitcount", "foo", "2", "19", "bit"})); EXPECT_EQ(0, CheckedInt({"bitcount", "foo", "-1", "-2", "bit"})); // illegal range } // ------------------------- BITOP tests const auto EXPECTED_LEN_BITOP = std::max(KEY_VALUES_BIT_OP[0].second.Size(), KEY_VALUES_BIT_OP[1].second.Size()); const auto EXPECTED_LEN_BITOP2 = std::max(EXPECTED_LEN_BITOP, KEY_VALUES_BIT_OP[2].second.Size()); const auto EXPECTED_LEN_BITOP3 = std::max(EXPECTED_LEN_BITOP2, KEY_VALUES_BIT_OP[3].second.Size()); TEST_F(BitOpsFamilyTest, BitOpsAnd) { BitOpSetKeys(); auto resp = Run({"bitop", "foo", "bar", "abc"}); // should failed this is illegal operation ASSERT_THAT(resp, ErrArg("syntax error")); // run with none existing keys, should return 0 EXPECT_EQ(0, CheckedInt({"bitop", "and", "dest_key", "1", "2", "3"})); // bitop AND single key EXPECT_EQ(KEY_VALUES_BIT_OP[0].second.Size(), CheckedInt({"bitop", "and", "foo_out", KEY_VALUES_BIT_OP[0].first})); auto res = Bytes::From(Run({"get", "foo_out"})); EXPECT_EQ(res, KEY_VALUES_BIT_OP[0].second); // this will 0 all values other than one bit it would end with result with length == // FOO_KEY_VALUE && value == BAR_KEY_VALUE EXPECT_EQ(EXPECTED_LEN_BITOP, CheckedInt({"bitop", "and", "foo-out", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[1].first})); const auto EXPECTED_RESULT = Bytes((0xffaacc01 & 0x1BB)); // first and second values res = Bytes::From(Run({"get", "foo-out"})); EXPECT_EQ(res, EXPECTED_RESULT); // test bitop AND with 3 keys EXPECT_EQ(EXPECTED_LEN_BITOP2, CheckedInt({"bitop", "and", "foo-out", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[1].first, KEY_VALUES_BIT_OP[2].first})); const auto EXPECTED_RES2 = Bytes((0xffaacc01 & 0x1BB & 0x01051520AACC)); res = Bytes::From(Run({"get", "foo-out"})); EXPECT_EQ(EXPECTED_RES2, res); // test bitop AND with 4 parameters const auto EXPECTED_RES3 = Bytes((0xffaacc01 & 0x1BB & 0x01051520AACC & 0xAACC)); EXPECT_EQ(EXPECTED_LEN_BITOP3, CheckedInt({"bitop", "and", "foo-out", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[1].first, KEY_VALUES_BIT_OP[2].first, KEY_VALUES_BIT_OP[3].first})); res = Bytes::From(Run({"get", "foo-out"})); EXPECT_EQ(EXPECTED_RES3, res); } TEST_F(BitOpsFamilyTest, BitOpsOr) { BitOpSetKeys(); EXPECT_EQ(0, CheckedInt({"bitop", "or", "dest_key", "1", "2", "3"})); // bitop or single key EXPECT_EQ(KEY_VALUES_BIT_OP[0].second.Size(), CheckedInt({"bitop", "or", "foo_out", KEY_VALUES_BIT_OP[0].first})); auto res = Bytes::From(Run({"get", "foo_out"})); EXPECT_EQ(res, KEY_VALUES_BIT_OP[0].second); // bitop OR 2 keys EXPECT_EQ(EXPECTED_LEN_BITOP, CheckedInt({"bitop", "or", "foo-out", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[1].first})); const auto EXPECTED_RESULT = Bytes((0xffaacc01 | 0x1BB)); // first or second values res = Bytes::From(Run({"get", "foo-out"})); EXPECT_EQ(res, EXPECTED_RESULT); // bitop OR with 3 keys EXPECT_EQ(EXPECTED_LEN_BITOP2, CheckedInt({"bitop", "or", "foo-out", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[1].first, KEY_VALUES_BIT_OP[2].first})); const auto EXPECTED_RES2 = Bytes((0xffaacc01 | 0x1BB | 0x01051520AACC)); res = Bytes::From(Run({"get", "foo-out"})); EXPECT_EQ(EXPECTED_RES2, res); // bitop OR with 4 keys const auto EXPECTED_RES3 = Bytes((0xffaacc01 | 0x1BB | 0x01051520AACC | 0xAACC)); EXPECT_EQ(EXPECTED_LEN_BITOP3, CheckedInt({"bitop", "or", "foo-out", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[1].first, KEY_VALUES_BIT_OP[2].first, KEY_VALUES_BIT_OP[3].first})); res = Bytes::From(Run({"get", "foo-out"})); EXPECT_EQ(EXPECTED_RES3, res); } TEST_F(BitOpsFamilyTest, BitOpsXor) { BitOpSetKeys(); EXPECT_EQ(0, CheckedInt({"bitop", "or", "dest_key", "1", "2", "3"})); // bitop XOR on single key EXPECT_EQ(KEY_VALUES_BIT_OP[0].second.Size(), CheckedInt({"bitop", "xor", "foo_out", KEY_VALUES_BIT_OP[0].first})); auto res = Bytes::From(Run({"get", "foo_out"})); EXPECT_EQ(res, KEY_VALUES_BIT_OP[0].second); // bitop on XOR with two keys EXPECT_EQ(EXPECTED_LEN_BITOP, CheckedInt({"bitop", "xor", "foo-out", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[1].first})); const auto EXPECTED_RESULT = Bytes((0xffaacc01 ^ 0x1BB)); // first xor second values res = Bytes::From(Run({"get", "foo-out"})); EXPECT_EQ(res, EXPECTED_RESULT); // bitop XOR with 3 keys EXPECT_EQ(EXPECTED_LEN_BITOP2, CheckedInt({"bitop", "xor", "foo-out", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[1].first, KEY_VALUES_BIT_OP[2].first})); const auto EXPECTED_RES2 = Bytes((0xffaacc01 ^ 0x1BB ^ 0x01051520AACC)); res = Bytes::From(Run({"get", "foo-out"})); EXPECT_EQ(EXPECTED_RES2, res); // bitop XOR with 4 keys const auto EXPECTED_RES3 = Bytes((0xffaacc01 ^ 0x1BB ^ 0x01051520AACC ^ 0xAACC)); EXPECT_EQ(EXPECTED_LEN_BITOP3, CheckedInt({"bitop", "xor", "foo-out", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[1].first, KEY_VALUES_BIT_OP[2].first, KEY_VALUES_BIT_OP[3].first})); res = Bytes::From(Run({"get", "foo-out"})); EXPECT_EQ(EXPECTED_RES3, res); } TEST_F(BitOpsFamilyTest, BitOpsNot) { // should failed this is illegal number of args auto resp = Run({"bitop", "not", "bar", "abc", "efg"}); ASSERT_THAT(resp, ErrArg("syntax error")); // Make sure that this works with none existing key as well EXPECT_EQ(0, CheckedInt({"bitop", "NOT", "bit-op-not-none-existing-key-results", "this-key-do-not-exists"})); ASSERT_THAT(Run({"get", "bit-op-not-none-existing-key-results"}), ArgType(RespExpr::Type::NIL)); EXPECT_EQ(Run({"set", "foo", "bar"}), "OK"); EXPECT_EQ(0, CheckedInt({"bitop", "NOT", "foo", "this-key-do-not-exists"})); ASSERT_THAT(Run({"get", "foo"}), ArgType(RespExpr::Type::NIL)); // Change the type of foo. Bitops is similar to set command. It's a blind update. ASSERT_THAT(Run({"hset", "foo", "bar", "val"}), IntArg(1)); EXPECT_EQ(0, CheckedInt({"bitop", "NOT", "foo", "this-key-do-not-exists"})); ASSERT_THAT(Run({"get", "foo"}), ArgType(RespExpr::Type::NIL)); // test bitop not resp = Run({"set", KEY_VALUES_BIT_OP[0].first, KEY_VALUES_BIT_OP[0].second}); EXPECT_EQ(KEY_VALUES_BIT_OP[0].second.Size(), CheckedInt({"bitop", "not", "foo_out", KEY_VALUES_BIT_OP[0].first})); auto res = Bytes::From(Run({"get", "foo_out"})); const auto NOT_RESULTS = Bytes(~0xFFAACC01ull); EXPECT_EQ(res, NOT_RESULTS); } TEST_F(BitOpsFamilyTest, BitPos) { ASSERT_EQ(Run({"set", "a", "\x00\x00\x06\xff\xf0"_b}), "OK"); // Find clear bits EXPECT_EQ(0, CheckedInt({"bitpos", "a", "0"})); EXPECT_EQ(8, CheckedInt({"bitpos", "a", "0", "1"})); EXPECT_EQ(16, CheckedInt({"bitpos", "a", "0", "2"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "0", "100"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "0", "100", "103"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "0", "100", "0"})); EXPECT_EQ(0, CheckedInt({"bitpos", "a", "0", "0", "100"})); EXPECT_EQ(8, CheckedInt({"bitpos", "a", "0", "1", "100"})); EXPECT_EQ(0, CheckedInt({"bitpos", "a", "0", "0", "-3"})); EXPECT_EQ(8, CheckedInt({"bitpos", "a", "0", "1", "-2"})); EXPECT_EQ(36, CheckedInt({"bitpos", "a", "0", "3"})); EXPECT_EQ(36, CheckedInt({"bitpos", "a", "0", "4"})); EXPECT_EQ(36, CheckedInt({"bitpos", "a", "0", "-2"})); EXPECT_EQ(36, CheckedInt({"bitpos", "a", "0", "-2", "-1"})); EXPECT_EQ(36, CheckedInt({"bitpos", "a", "0", "-1"})); EXPECT_EQ(0, CheckedInt({"bitpos", "a", "0", "-100"})); // Find clear bits, explicitly mention "BYTE" EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "0", "100", "103", "BYTE"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "0", "100", "0", "BYTE"})); EXPECT_EQ(0, CheckedInt({"bitpos", "a", "0", "0", "100", "BYTE"})); EXPECT_EQ(8, CheckedInt({"bitpos", "a", "0", "1", "100", "BYTE"})); EXPECT_EQ(0, CheckedInt({"bitpos", "a", "0", "0", "-3", "BYTE"})); EXPECT_EQ(8, CheckedInt({"bitpos", "a", "0", "1", "-2", "BYTE"})); EXPECT_EQ(36, CheckedInt({"bitpos", "a", "0", "-2", "-1", "BYTE"})); // Find clear bits using "BIT" EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "0", "100", "103", "BIT"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "0", "100", "0", "BIT"})); EXPECT_EQ(0, CheckedInt({"bitpos", "a", "0", "0", "100", "BIT"})); EXPECT_EQ(1, CheckedInt({"bitpos", "a", "0", "1", "100", "BIT"})); EXPECT_EQ(2, CheckedInt({"bitpos", "a", "0", "2", "100", "BIT"})); EXPECT_EQ(16, CheckedInt({"bitpos", "a", "0", "16", "100", "BIT"})); EXPECT_EQ(23, CheckedInt({"bitpos", "a", "0", "21", "100", "BIT"})); EXPECT_EQ(36, CheckedInt({"bitpos", "a", "0", "24", "100", "BIT"})); EXPECT_EQ(0, CheckedInt({"bitpos", "a", "0", "0", "-3", "BIT"})); EXPECT_EQ(1, CheckedInt({"bitpos", "a", "0", "1", "-2", "BIT"})); EXPECT_EQ(38, CheckedInt({"bitpos", "a", "0", "-2", "-1", "BIT"})); // Find set bits EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "0"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "1"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "2"})); EXPECT_EQ(24, CheckedInt({"bitpos", "a", "1", "3"})); EXPECT_EQ(32, CheckedInt({"bitpos", "a", "1", "4"})); EXPECT_EQ(32, CheckedInt({"bitpos", "a", "1", "-1"})); EXPECT_EQ(24, CheckedInt({"bitpos", "a", "1", "-2"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "-3"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "-4"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "-5"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "-6"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "-100"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "0", "0"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "0", "1"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "0", "3"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "0", "100"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "2", "2"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "2", "3"})); EXPECT_EQ(32, CheckedInt({"bitpos", "a", "1", "-1", "-1"})); EXPECT_EQ(24, CheckedInt({"bitpos", "a", "1", "-2", "-1"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "-1", "-2"})); // Find set bits, explicitly mention "BYTE" EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "0", "0", "BYTE"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "0", "1", "BYTE"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "0", "3", "BYTE"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "0", "100", "BYTE"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "2", "2", "BYTE"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "2", "3", "BYTE"})); EXPECT_EQ(32, CheckedInt({"bitpos", "a", "1", "-1", "-1", "BYTE"})); EXPECT_EQ(24, CheckedInt({"bitpos", "a", "1", "-2", "-1", "BYTE"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "-1", "-2", "BYTE"})); // Find set bits using "BIT" EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "0", "0", "BIT"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "0", "1", "BIT"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "0", "21", "BIT"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "21", "21", "BIT"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "21", "100", "BIT"})); EXPECT_EQ(21, CheckedInt({"bitpos", "a", "1", "0", "100", "BIT"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "-1", "-1", "BIT"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "a", "1", "-4", "-1", "BIT"})); EXPECT_EQ(35, CheckedInt({"bitpos", "a", "1", "-5", "-1", "BIT"})); EXPECT_EQ(34, CheckedInt({"bitpos", "a", "1", "-6", "-1", "BIT"})); // Make sure we behave like Redis does when looking for clear bits in an all-set string. ASSERT_EQ(Run({"set", "b", "\xff\xff\xff"_b}), "OK"); EXPECT_EQ(24, CheckedInt({"bitpos", "b", "0"})); EXPECT_EQ(24, CheckedInt({"bitpos", "b", "0", "0"})); EXPECT_EQ(24, CheckedInt({"bitpos", "b", "0", "1"})); EXPECT_EQ(24, CheckedInt({"bitpos", "b", "0", "2"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "b", "0", "3"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "b", "0", "0", "1"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "b", "0", "0", "1", "BYTE"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "b", "0", "0", "3"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "b", "0", "0", "3", "BYTE"})); ASSERT_EQ(Run({"set", "empty", ""_b}), "OK"); EXPECT_EQ(-1, CheckedInt({"bitpos", "empty", "0"})); EXPECT_EQ(-1, CheckedInt({"bitpos", "empty", "0", "1"})); // Non-existent key should be treated like padded with zeros string. EXPECT_EQ(-1, CheckedInt({"bitpos", "d", "1"})); EXPECT_EQ(0, CheckedInt({"bitpos", "d", "0"})); // Make sure we accept only 0 and 1 for the bit mode arguement. const auto argument_must_be_0_or_1_error = ErrArg("ERR The bit argument must be 1 or 0"); ASSERT_THAT(Run({"bitpos", "d", "2"}), argument_must_be_0_or_1_error); ASSERT_THAT(Run({"bitpos", "d", "42"}), argument_must_be_0_or_1_error); ASSERT_THAT(Run({"bitpos", "d", "-1"}), argument_must_be_0_or_1_error); } TEST_F(BitOpsFamilyTest, BitFieldParsing) { const auto syntax_error = ErrArg("ERR syntax error"); // Parsing Errors ASSERT_THAT(Run({"bitfield", "foo", "set", "u1"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "set", "u1", "0"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "set", "u1", "0", "0", "55"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "set", "u1", "0", "0", "get", "u1"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "u1"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "u1", "0"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "0", "15"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "get"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "set", "u1", "0", "0", "set"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "overflow"}), syntax_error); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "nonsense"}), syntax_error); // Range errors auto expected_error = ErrArg( "ERR invalid bitfield type. use something like i16 u8. note that u64 is not supported but " "i64 is."); ASSERT_THAT(Run({"bitfield", "foo", "set", "u0", "0", "0"}), expected_error); ASSERT_THAT(Run({"bitfield", "foo", "set", "u0", "0", "0"}), expected_error); ASSERT_THAT(Run({"bitfield", "foo", "set", "u64", "0", "0"}), expected_error); ASSERT_THAT(Run({"bitfield", "foo", "set", "u65", "0", "0"}), expected_error); ASSERT_THAT(Run({"bitfield", "foo", "set", "i65", "0", "0"}), expected_error); expected_error = ErrArg("BITFIELD_RO only supports the GET subcommand"); ASSERT_THAT(Run({"bitfield_ro", "foo", "set", "u1", "0", "0"}), expected_error); ASSERT_THAT(Run({"bitfield_ro", "foo", "incrby", "i64", "0", "15"}), expected_error); } TEST_F(BitOpsFamilyTest, BitFieldCreate) { // check that SET, INCR create the key when it does not exist ASSERT_THAT(Run({"bitfield", "foo", "set", "u1", "0", "1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "0"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "u1", "1", "1"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "1"}), IntArg(1)); } TEST_F(BitOpsFamilyTest, BitFieldOverflowUnderflow) { Run({"bitfield", "foo", "set", "u2", "0", "2"}); // unsigned 1bit ASSERT_THAT(Run({"bitfield", "foo", "set", "u1", "0", "2"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "0"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "u1", "1", "2"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "1"}), IntArg(0)); // unsigned 63bit int64_t max = std::numeric_limits::max(); Run({"bitfield", "foo", "set", "i64", "0", absl::StrCat(max)}); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "i64", "0", "1"}), IntArg(-max - 1)); // signed 1 bit Run({"bitfield", "foo", "set", "i1", "0", "-2"}); ASSERT_THAT(Run({"bitfield", "foo", "get", "i1", "0"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "i1", "0", "-1"}), IntArg(-1)); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "i1", "0", "-1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "i1", "0", "-3"}), IntArg(-1)); int64_t min = std::numeric_limits::min(); Run({"bitfield", "foo", "set", "i8", "0", absl::StrCat(min)}); ASSERT_THAT(Run({"bitfield", "foo", "get", "i8", "0"}), IntArg(0)); // signed 64 bit Run({"bitfield", "foo", "set", "i64", "0", absl::StrCat(min)}); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "i64", "0", "-1"}), IntArg(max)); // overflow sat // unsigned 8 bit Run({"bitfield", "foo", "set", "u1", "0", "0"}); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "incrby", "u8", "0", "300"}), IntArg(255)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "incrby", "u8", "0", "10"}), IntArg(255)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "0"}), IntArg(255)); // unsigned 63 bit Run({"bitfield", "foo", "set", "u63", "0", "0"}); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "set", "u63", "0", absl::StrCat(max)}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "incrby", "u63", "0", "10"}), IntArg(max)); // signed 8 bit Run({"bitfield", "foo", "set", "u8", "0", "0"}); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "set", "i8", "0", "300"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "incrby", "i8", "0", "-127"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "incrby", "i8", "0", "-255"}), IntArg(-128)); // signed 64 bit Run({"bitfield", "foo", "set", "i64", "0", "0"}); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "set", "i64", "0", absl::StrCat(max)}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "incrby", "i64", "0", "100"}), IntArg(max)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i64", "0"}), IntArg(max)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "set", "i64", "0", absl::StrCat(min)}), IntArg(max)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "sat", "incrby", "i64", "0", "-100"}), IntArg(min)); // overflow fail // unsigned ASSERT_THAT(Run({"bitfield", "foo", "overflow", "fail", "set", "u8", "0", "300"}), ArgType(RespExpr::Type::NIL)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "fail", "incrby", "u1", "0", "10"}), ArgType(RespExpr::Type::NIL)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "fail", "incrby", "u1", "0", "-10"}), ArgType(RespExpr::Type::NIL)); // signed ASSERT_THAT(Run({"bitfield", "foo", "overflow", "fail", "incrby", "i8", "0", "300"}), ArgType(RespExpr::Type::NIL)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "fail", "incrby", "i1", "0", "10"}), ArgType(RespExpr::Type::NIL)); ASSERT_THAT(Run({"bitfield", "foo", "overflow", "fail", "incrby", "i1", "0", "-10"}), ArgType(RespExpr::Type::NIL)); // stickiness of overflow among operations in a chain ASSERT_THAT(Run({"bitfield", "foo", "overflow", "fail", "set", "u8", "0", "300", "set", "u1", "0", "400"}), RespArray(ElementsAre(ArgType(RespExpr::NIL), ArgType(RespExpr::NIL)))); } TEST_F(BitOpsFamilyTest, BitFieldOperations) { // alligned offset reads/writes unsigned Run({"bitfield", "foo", "set", "u32", "0", "0"}); // Set the bit battern 01111000 00000001 00000001 00001010 ASSERT_THAT(Run({"bitfield", "foo", "set", "u8", "0", "120"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "0"}), IntArg(120)); ASSERT_THAT(Run({"bitfield", "foo", "set", "u8", "8", "1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "8"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "set", "u8", "16", "1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "16"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "set", "u8", "24", "10"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "24"}), IntArg(10)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u32", "0"}), IntArg(2013331722)); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "u8", "0", "120"}), IntArg(240)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "0"}), IntArg(240)); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "u16", "0", "120"}), IntArg(61561)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u16", "0"}), IntArg(61561)); // alligned offset reads/writes signed Run({"bitfield", "foo", "set", "u32", "0", "0"}); // Set the bit battern 10001000 11111111 11111111 11110110 ASSERT_THAT(Run({"bitfield", "foo", "set", "i8", "0", "-120"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i8", "0"}), IntArg(-120)); ASSERT_THAT(Run({"bitfield", "foo", "set", "i8", "8", "-1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i8", "8"}), IntArg(-1)); ASSERT_THAT(Run({"bitfield", "foo", "set", "i8", "16", "-1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i8", "16"}), IntArg(-1)); ASSERT_THAT(Run({"bitfield", "foo", "set", "i8", "24", "-10"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i8", "24"}), IntArg(-10)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i32", "0"}), IntArg(-1996488714)); ASSERT_THAT(Run({"bitfield", "foo", "incrby", "i8", "0", "-8"}), IntArg(-128)); // nonalligned offset reads/writes unsigned Run({"bitfield", "foo", "set", "i64", "0", "0"}); // Set the bit battern 00000000 10000000 10000000 10000000 10000000 ASSERT_THAT(Run({"bitfield", "foo", "set", "u8", "1", "1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "1"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "set", "u8", "9", "1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "9"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "set", "u8", "17", "1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "17"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "set", "u8", "25", "1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "25"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "0"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "8"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "16"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "24"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "32"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u33", "0"}), IntArg(16843009)); // nonalligned offset reads/writes signed Run({"bitfield", "foo", "set", "i64", "0", "0"}); // Set the bit battern 1111111 11111111 0000000 000000001 ASSERT_THAT(Run({"bitfield", "foo", "set", "i8", "1", "-1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i8", "1"}), IntArg(-1)); ASSERT_THAT(Run({"bitfield", "foo", "set", "i8", "9", "-1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i8", "9"}), IntArg(-1)); ASSERT_THAT(Run({"bitfield", "foo", "set", "i8", "17", "0"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i8", "17"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "set", "i8", "25", "1"}), IntArg(0)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i8", "25"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "get", "i32", "1"}), IntArg(-65535)); // chaining Run({ "bitfield", "foo", "set", "u1", "0", "1", "set", "u1", "1", "1", "set", "u1", "2", "1", "set", "u1", "3", "1", "set", "u1", "4", "1", "set", "u1", "5", "1", "set", "u1", "6", "1", "set", "u1", "7", "1", }); ASSERT_THAT(Run({"bitfield", "foo", "get", "u8", "0"}), IntArg(255)); ASSERT_THAT(Run({ "bitfield", "foo", "set", "u1", "0", "0", "incrby", "u1", "0", "1", "get", "u1", "0", }), RespArray(ElementsAre(IntArg(1), IntArg(1), IntArg(1)))); // check for positional offsets Run({"bitfield", "foo", "set", "u8", "#0", "1", "set", "u8", "#1", "1", "set", "u8", "#2", "1"}); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "7"}), IntArg(1)); ASSERT_THAT(Run({"bitfield", "foo", "get", "u1", "15"}), IntArg(1)); } } // end of namespace dfly