mirror of
https://github.com/deepseek-ai/3FS
synced 2025-06-26 18:16:45 +00:00
Initial commit
This commit is contained in:
24
tests/common/serde/TestBigEndian.cc
Normal file
24
tests/common/serde/TestBigEndian.cc
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "common/serde/BigEndian.h"
|
||||
#include "common/serde/Serde.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
namespace hf3fs::serde::test {
|
||||
namespace {
|
||||
|
||||
TEST(TestSerde, BigEndian) {
|
||||
{
|
||||
using BigSize = BigEndian<std::size_t>;
|
||||
BigSize size{0x123456789ABCDEF0ul};
|
||||
auto out = serde::serialize(size);
|
||||
ASSERT_EQ(out.size(), sizeof(std::size_t));
|
||||
ASSERT_EQ(out.front(), 0x12);
|
||||
ASSERT_EQ(out.back(), (char)0xF0);
|
||||
ASSERT_EQ(serde::toJsonString(size), std::to_string(size));
|
||||
|
||||
ASSERT_OK(serde::fromJsonString(size, "233"));
|
||||
ASSERT_EQ(size, 233);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::serde::test
|
||||
34
tests/common/serde/TestMessagePacket.cc
Normal file
34
tests/common/serde/TestMessagePacket.cc
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "common/serde/MessagePacket.h"
|
||||
#include "common/serde/Serde.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
namespace hf3fs::serde::test {
|
||||
namespace {
|
||||
|
||||
struct Demo {
|
||||
bool operator==(const Demo &) const = default;
|
||||
|
||||
SERDE_STRUCT_FIELD(foo, int{}, nullptr);
|
||||
SERDE_STRUCT_FIELD(bar, std::string{}, nullptr);
|
||||
};
|
||||
|
||||
TEST(TestMessagePacket, Normal) {
|
||||
// send.
|
||||
Demo req;
|
||||
req.foo = 100;
|
||||
req.bar = "Good";
|
||||
MessagePacket send(req);
|
||||
auto bytes = serde::serialize(send);
|
||||
XLOGF(INFO, "send json: {}, binary: {:02X}", send, fmt::join(bytes, "-"));
|
||||
|
||||
// recv.
|
||||
MessagePacket recv;
|
||||
ASSERT_OK(serde::deserialize(recv, bytes));
|
||||
Demo rsp;
|
||||
ASSERT_OK(serde::deserialize(rsp, recv.payload));
|
||||
ASSERT_EQ(req, rsp);
|
||||
XLOGF(INFO, "rsp json: {}", rsp);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::serde::test
|
||||
946
tests/common/serde/TestSerde.cc
Normal file
946
tests/common/serde/TestSerde.cc
Normal file
@@ -0,0 +1,946 @@
|
||||
#include <folly/json.h>
|
||||
|
||||
#include "common/serde/Serde.h"
|
||||
#include "common/utils/DownwardBytes.h"
|
||||
#include "common/utils/Reflection.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/RobinHood.h"
|
||||
#include "common/utils/StrongType.h"
|
||||
#include "fbs/mgmtd/RoutingInfo.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
namespace hf3fs::test {
|
||||
namespace {
|
||||
|
||||
bool checkValue(float b) { return b > 10.0; }
|
||||
|
||||
struct CheckString {
|
||||
constexpr CheckString() = default;
|
||||
constexpr bool operator()(const std::string &str) const { return !str.empty(); }
|
||||
};
|
||||
|
||||
struct Demo {
|
||||
SERDE_CLASS_FIELD(a, short{10}, [](short a) { return a > 0; }); // checker is lambda expr.
|
||||
SERDE_CLASS_FIELD(b, 20.0f, checkValue); // checker is function pointer.
|
||||
SERDE_CLASS_FIELD(c, std::string{"ok"}, CheckString{}); // checker is function object.
|
||||
};
|
||||
static_assert(serde::count<Demo>() == 3);
|
||||
static_assert(serde::name<Demo, 0>() == "a");
|
||||
static_assert(serde::name<Demo, 1>() == "b");
|
||||
static_assert(serde::name<Demo, 2>() == "c");
|
||||
static_assert(serde::SerdeType<Demo>);
|
||||
static_assert(!serde::SerdeType<int>);
|
||||
|
||||
TEST(TestSerde, Normal) {
|
||||
const Demo demo;
|
||||
ASSERT_EQ(serde::name<0>(demo), "a");
|
||||
ASSERT_EQ(serde::name<1>(demo), "b");
|
||||
ASSERT_EQ(serde::name<2>(demo), "c");
|
||||
ASSERT_EQ(serde::value<0>(demo), 10);
|
||||
ASSERT_EQ(serde::value<1>(demo), 20.0);
|
||||
ASSERT_EQ(serde::value<2>(demo), "ok");
|
||||
ASSERT_EQ(&serde::value<0>(demo), &demo.a());
|
||||
ASSERT_EQ(&serde::value<1>(demo), &demo.b());
|
||||
ASSERT_EQ(&serde::value<2>(demo), &demo.c());
|
||||
}
|
||||
|
||||
TEST(TestSerde, Serialize) {
|
||||
Demo demo;
|
||||
demo.a() = 100;
|
||||
demo.b() = 200.f;
|
||||
demo.c() = "hello";
|
||||
|
||||
const Demo &d = demo;
|
||||
auto out = serde::serialize(d);
|
||||
ASSERT_EQ(out.length(), serde::serializeLength(d));
|
||||
constexpr auto size = 1 + 2 + 4 + 1 + 5; // struct size + short + float + str length + str content.
|
||||
ASSERT_EQ(out.length(), size);
|
||||
|
||||
XLOGF(INFO, "out is {:02X}", fmt::join(out, ","));
|
||||
|
||||
Demo des;
|
||||
ASSERT_EQ(des.a(), 10);
|
||||
ASSERT_EQ(des.b(), 20.f);
|
||||
ASSERT_EQ(des.c(), "ok");
|
||||
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_EQ(des.a(), 100);
|
||||
ASSERT_EQ(des.b(), 200.f);
|
||||
ASSERT_EQ(des.c(), "hello");
|
||||
}
|
||||
|
||||
struct Nested {
|
||||
SERDE_CLASS_FIELD(a, Demo{});
|
||||
SERDE_CLASS_FIELD(b, Demo{});
|
||||
};
|
||||
|
||||
TEST(TestSerde, Serialize2) {
|
||||
Nested nested;
|
||||
nested.b().a() = 100;
|
||||
nested.b().b() = 200.f;
|
||||
nested.b().c() = "hello";
|
||||
|
||||
auto out = serde::serialize(nested);
|
||||
ASSERT_EQ(out.length(), serde::serializeLength(nested));
|
||||
constexpr auto size = 1 + (1 + 2 + 4 + 1 + 2) + (1 + 2 + 4 + 1 + 5);
|
||||
ASSERT_EQ(out.length(), size);
|
||||
|
||||
Nested des;
|
||||
ASSERT_EQ(des.a().a(), des.b().a());
|
||||
ASSERT_EQ(des.a().b(), des.b().b());
|
||||
ASSERT_EQ(des.a().c(), des.b().c());
|
||||
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_EQ(des.a().a(), 10);
|
||||
ASSERT_EQ(des.a().b(), 20.f);
|
||||
ASSERT_EQ(des.a().c(), "ok");
|
||||
ASSERT_EQ(des.b().a(), 100);
|
||||
ASSERT_EQ(des.b().b(), 200.f);
|
||||
ASSERT_EQ(des.b().c(), "hello");
|
||||
}
|
||||
|
||||
TEST(TestSerde, Vector) {
|
||||
struct Test {
|
||||
SERDE_CLASS_FIELD(a, (std::vector<int>{0, 1, 2}));
|
||||
SERDE_CLASS_FIELD(b, (std::vector<std::string>{"x", "y"}));
|
||||
};
|
||||
|
||||
Test a;
|
||||
auto out = serde::serialize(a);
|
||||
ASSERT_EQ(out.length(), serde::serializeLength(a));
|
||||
ASSERT_EQ(out.size(), 1 + 1 + 4 * 3 + 1 + 2 * (1 + 1)); // struct size + vsize + int * 3 + vsize + 2 * (ssize + c)
|
||||
|
||||
a.a() = std::vector<int>{0, 1, 0, 1};
|
||||
a.b() = std::vector<std::string>{"a", "b", "c", "d"};
|
||||
out = serde::serialize(a);
|
||||
|
||||
Test b;
|
||||
ASSERT_NE(b.a(), a.a());
|
||||
ASSERT_NE(b.b(), a.b());
|
||||
ASSERT_OK(serde::deserialize(b, out));
|
||||
ASSERT_EQ(b.a(), a.a());
|
||||
ASSERT_EQ(b.b(), a.b());
|
||||
}
|
||||
|
||||
TEST(TestSerde, Vector2) {
|
||||
struct Item {
|
||||
SERDE_STRUCT_FIELD(a, uint32_t{});
|
||||
SERDE_STRUCT_FIELD(b, std::string{});
|
||||
};
|
||||
struct Vector {
|
||||
SERDE_STRUCT_FIELD(vec, std::vector<Item>{});
|
||||
};
|
||||
|
||||
Vector vec;
|
||||
vec.vec.push_back({1, "one"});
|
||||
vec.vec.push_back({2, "two"});
|
||||
XLOGF(INFO, "vec is {}", vec);
|
||||
}
|
||||
|
||||
TEST(TestSerde, Vector3) {
|
||||
struct Item {
|
||||
SERDE_STRUCT_FIELD(a, std::vector<uint32_t>{});
|
||||
};
|
||||
|
||||
Item item;
|
||||
item.a.push_back(2);
|
||||
item.a.push_back(0);
|
||||
item.a.push_back(2);
|
||||
item.a.push_back(3);
|
||||
auto out = serde::serialize(item);
|
||||
|
||||
ASSERT_EQ(out.size(), 18);
|
||||
ASSERT_EQ(out[0], 17);
|
||||
ASSERT_EQ(out[1], 4);
|
||||
|
||||
Item des;
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_EQ(item.a, des.a);
|
||||
}
|
||||
|
||||
TEST(TestSerde, Set) {
|
||||
struct Test {
|
||||
SERDE_CLASS_FIELD(a, (std::set<int>{0, 1, 2}));
|
||||
SERDE_CLASS_FIELD(b, (std::set<std::string>{"x", "y"}));
|
||||
};
|
||||
|
||||
Test a;
|
||||
auto out = serde::serialize(a);
|
||||
ASSERT_EQ(out.length(), serde::serializeLength(a));
|
||||
ASSERT_EQ(out.size(), 1 + 1 + 4 * 3 + 1 + 2 * (1 + 1));
|
||||
|
||||
a.a() = std::set<int>{0, 1, 0, 1};
|
||||
a.b() = std::set<std::string>{"a", "b", "c", "d"};
|
||||
out = serde::serialize(a);
|
||||
|
||||
Test b;
|
||||
ASSERT_NE(b.a(), a.a());
|
||||
ASSERT_NE(b.b(), a.b());
|
||||
ASSERT_OK(serde::deserialize(b, out));
|
||||
ASSERT_EQ(b.a(), a.a());
|
||||
ASSERT_EQ(b.b(), a.b());
|
||||
}
|
||||
|
||||
TEST(TestSerde, Map) {
|
||||
struct Test {
|
||||
SERDE_CLASS_FIELD(a, (std::map<int, int>{}));
|
||||
SERDE_CLASS_FIELD(b, (robin_hood::unordered_map<std::string, std::string>{}));
|
||||
};
|
||||
|
||||
Test a;
|
||||
auto out = serde::serialize(a);
|
||||
ASSERT_EQ(out.length(), serde::serializeLength(a));
|
||||
ASSERT_EQ(out.size(), 1 + 1 + 1);
|
||||
|
||||
a.a()[1] = 2;
|
||||
a.a()[3] = 4;
|
||||
a.b()["hello"] = "world";
|
||||
a.b()["nice to"] = "meet you";
|
||||
out = serde::serialize(a);
|
||||
ASSERT_EQ(out.size(), 1 + 1 + 4 * 4 + 1 + 4 * 1 + 5 + 5 + 7 + 8);
|
||||
|
||||
Test b;
|
||||
ASSERT_NE(b.a(), a.a());
|
||||
ASSERT_NE(b.b(), a.b());
|
||||
ASSERT_OK(serde::deserialize(b, out));
|
||||
ASSERT_EQ(b.a(), a.a());
|
||||
ASSERT_EQ(b.b(), a.b());
|
||||
}
|
||||
|
||||
TEST(TestSerde, Optional) {
|
||||
struct Test {
|
||||
SERDE_CLASS_FIELD(a, std::optional<std::string>{});
|
||||
SERDE_CLASS_FIELD(b, std::optional<std::string>{"OK"});
|
||||
};
|
||||
|
||||
Test a;
|
||||
auto out = serde::serialize(a);
|
||||
ASSERT_EQ(out.length(), serde::serializeLength(a));
|
||||
ASSERT_EQ(out.size(), 1 + 1 + 1 + 1 + 2);
|
||||
|
||||
a.a() = "hello";
|
||||
a.b() = std::nullopt;
|
||||
out = serde::serialize(a);
|
||||
ASSERT_EQ(out.size(), 1 + 1 + 1 + 5 + 1);
|
||||
|
||||
Test b;
|
||||
ASSERT_NE(b.a(), a.a());
|
||||
ASSERT_NE(b.b(), a.b());
|
||||
ASSERT_OK(serde::deserialize(b, out));
|
||||
ASSERT_EQ(b.a(), a.a());
|
||||
ASSERT_EQ(b.b(), a.b());
|
||||
}
|
||||
|
||||
TEST(TestSerde, Variant) {
|
||||
struct Test {
|
||||
SERDE_CLASS_FIELD(a, (std::variant<int, float, std::string>{}));
|
||||
};
|
||||
|
||||
Test a;
|
||||
auto out = serde::serialize(a);
|
||||
ASSERT_EQ(out.length(), serde::serializeLength(a));
|
||||
ASSERT_EQ(out.size(), 1 + 1 + 3 + 4);
|
||||
|
||||
a.a() = "hello";
|
||||
out = serde::serialize(a);
|
||||
ASSERT_EQ(out.size(), 1 + 1 + 12 + 1 + 5);
|
||||
|
||||
Test b;
|
||||
ASSERT_NE(b.a(), a.a());
|
||||
ASSERT_OK(serde::deserialize(b, out));
|
||||
ASSERT_EQ(b.a(), a.a());
|
||||
}
|
||||
|
||||
TEST(TestSerde, Compatibility) {
|
||||
struct Test {
|
||||
SERDE_CLASS_FIELD(a, std::optional<std::string>{});
|
||||
SERDE_CLASS_FIELD(b, std::vector<int>{});
|
||||
};
|
||||
|
||||
Test a;
|
||||
a.a() = "ok";
|
||||
a.b() = {1, 2, 3};
|
||||
auto out = serde::serialize(a);
|
||||
ASSERT_EQ(out.length(), serde::serializeLength(a));
|
||||
ASSERT_EQ(out.length(), 1 + 1 + 1 + 2 + 1 + 4 * 3);
|
||||
XLOGF(INFO, "out is {:02x}", fmt::join(out, ","));
|
||||
|
||||
size_t size = out.length();
|
||||
for (size_t i = 1; i <= size; ++i) {
|
||||
std::string_view view = out;
|
||||
*reinterpret_cast<uint8_t *>(out.data()) = i - 1;
|
||||
view = view.substr(0, i);
|
||||
|
||||
Test t;
|
||||
if (i == 1) {
|
||||
ASSERT_OK(serde::deserialize(t, view));
|
||||
ASSERT_FALSE(t.a().has_value());
|
||||
ASSERT_TRUE(t.b().empty());
|
||||
} else if (i == 5) {
|
||||
ASSERT_OK(serde::deserialize(t, view));
|
||||
ASSERT_TRUE(t.a().has_value());
|
||||
ASSERT_EQ(t.a().value(), "ok");
|
||||
ASSERT_TRUE(t.b().empty());
|
||||
} else if (i == size) {
|
||||
ASSERT_OK(serde::deserialize(t, view));
|
||||
ASSERT_TRUE(t.a().has_value());
|
||||
ASSERT_EQ(t.a().value(), "ok");
|
||||
ASSERT_FALSE(t.b().empty());
|
||||
} else {
|
||||
XLOGF(INFO, "length is {}", i);
|
||||
ASSERT_FALSE(serde::deserialize(a, view));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Base {
|
||||
SERDE_STRUCT_FIELD(x, int{});
|
||||
SERDE_STRUCT_FIELD(y, int{});
|
||||
};
|
||||
|
||||
struct Derived : Base {
|
||||
SERDE_STRUCT_FIELD(z, int{});
|
||||
};
|
||||
|
||||
struct Grandson : Derived {
|
||||
SERDE_STRUCT_FIELD(x, int{});
|
||||
SERDE_STRUCT_FIELD(y, int{});
|
||||
SERDE_STRUCT_FIELD(z, int{});
|
||||
};
|
||||
|
||||
namespace v1 {
|
||||
struct Base {
|
||||
SERDE_STRUCT_FIELD(x, int{});
|
||||
};
|
||||
|
||||
struct Derived : Base {
|
||||
SERDE_STRUCT_FIELD(z, int{});
|
||||
};
|
||||
|
||||
struct Grandson : Derived {
|
||||
SERDE_STRUCT_FIELD(x, int{});
|
||||
SERDE_STRUCT_FIELD(y, int{});
|
||||
};
|
||||
}; // namespace v1
|
||||
|
||||
namespace v3 {
|
||||
struct Base {
|
||||
SERDE_STRUCT_FIELD(x, int{});
|
||||
SERDE_STRUCT_FIELD(y, int{});
|
||||
SERDE_STRUCT_FIELD(z, int{});
|
||||
};
|
||||
|
||||
struct Derived : Base {
|
||||
SERDE_STRUCT_FIELD(z, int{});
|
||||
SERDE_STRUCT_FIELD(a, int{});
|
||||
};
|
||||
|
||||
struct Grandson : Derived {
|
||||
SERDE_STRUCT_FIELD(x, int{});
|
||||
SERDE_STRUCT_FIELD(y, int{});
|
||||
SERDE_STRUCT_FIELD(z, int{});
|
||||
SERDE_STRUCT_FIELD(a, int{});
|
||||
};
|
||||
}; // namespace v3
|
||||
|
||||
static_assert(serde::count<Base>() == 2, "field count of Base must be 2");
|
||||
static_assert(serde::count<Derived>() == 3, "field count of Derived must be 3");
|
||||
static_assert(serde::count<Grandson>() == 6, "field count of Grandson must be 6");
|
||||
static_assert(serde::count<v1::Grandson>() == 4);
|
||||
|
||||
static_assert(serde::getter<Grandson, 0>() != serde::getter<Grandson, 3>());
|
||||
static_assert(serde::getter<Grandson, 1>() != serde::getter<Grandson, 4>());
|
||||
static_assert(serde::getter<Grandson, 2>() != serde::getter<Grandson, 5>());
|
||||
|
||||
static_assert(serde::name<Base, 0>() == "x");
|
||||
static_assert(serde::name<Base, 1>() == "y");
|
||||
|
||||
static_assert(serde::name<Derived, 0>() == "x");
|
||||
static_assert(serde::name<Derived, 1>() == "y");
|
||||
static_assert(serde::name<Derived, 2>() == "z");
|
||||
|
||||
static_assert(serde::name<Grandson, 0>() == "x");
|
||||
static_assert(serde::name<Grandson, 1>() == "y");
|
||||
static_assert(serde::name<Grandson, 2>() == "z");
|
||||
static_assert(serde::name<Grandson, 3>() == "x");
|
||||
static_assert(serde::name<Grandson, 4>() == "y");
|
||||
static_assert(serde::name<Grandson, 5>() == "z");
|
||||
|
||||
TEST(TestSerde, Inherit) {
|
||||
Grandson x;
|
||||
x.Base::x = 10;
|
||||
x.Base::y = 12;
|
||||
x.Derived::z = 16;
|
||||
x.Grandson::x = 20;
|
||||
x.Grandson::y = 24;
|
||||
x.Grandson::z = 28;
|
||||
ASSERT_EQ(serde::count(x), 6);
|
||||
ASSERT_EQ(serde::value<0>(x), 10);
|
||||
ASSERT_EQ(serde::value<3>(x), 20);
|
||||
|
||||
int typeCount = 1;
|
||||
refl::Helper::iterate<Grandson, true>([](auto t) { XLOGF(INFO, "field name: {}", t.name); },
|
||||
[&]() {
|
||||
++typeCount;
|
||||
XLOGF(INFO, "type changed");
|
||||
});
|
||||
ASSERT_EQ(typeCount, 3);
|
||||
|
||||
auto out = serde::serialize(x);
|
||||
Grandson o;
|
||||
ASSERT_OK(serde::deserialize(o, out));
|
||||
ASSERT_EQ(x.Base::x, o.Base::x);
|
||||
ASSERT_EQ(x.Base::y, o.Base::y);
|
||||
ASSERT_EQ(x.Derived::z, o.Derived::z);
|
||||
ASSERT_EQ(x.Grandson::x, o.Grandson::x);
|
||||
ASSERT_EQ(x.Grandson::y, o.Grandson::y);
|
||||
ASSERT_EQ(x.Grandson::z, o.Grandson::z);
|
||||
|
||||
v1::Grandson p;
|
||||
ASSERT_OK(serde::deserialize(p, out));
|
||||
ASSERT_EQ(x.Base::x, p.Base::x);
|
||||
ASSERT_EQ(x.Derived::z, p.Derived::z);
|
||||
ASSERT_EQ(x.Grandson::x, p.Grandson::x);
|
||||
ASSERT_EQ(x.Grandson::y, p.Grandson::y);
|
||||
|
||||
v3::Grandson q;
|
||||
ASSERT_OK(serde::deserialize(q, out));
|
||||
ASSERT_EQ(x.Base::x, q.Base::x);
|
||||
ASSERT_EQ(x.Base::y, q.Base::y);
|
||||
ASSERT_EQ(q.Base::z, 0);
|
||||
ASSERT_EQ(x.Derived::z, q.Derived::z);
|
||||
ASSERT_EQ(q.Derived::a, 0);
|
||||
ASSERT_EQ(x.Grandson::x, q.Grandson::x);
|
||||
ASSERT_EQ(x.Grandson::y, q.Grandson::y);
|
||||
ASSERT_EQ(x.Grandson::z, q.Grandson::z);
|
||||
ASSERT_EQ(q.Grandson::a, 0);
|
||||
}
|
||||
|
||||
struct WithStringView {
|
||||
SERDE_STRUCT_FIELD(msg, std::string_view{});
|
||||
SERDE_STRUCT_FIELD(test, std::string_view{});
|
||||
};
|
||||
|
||||
TEST(TestSerde, StringView) {
|
||||
WithStringView view;
|
||||
view.msg = "OK";
|
||||
view.test = "test";
|
||||
auto out = serde::serialize(view);
|
||||
ASSERT_EQ(out.length(), serde::serializeLength(view));
|
||||
|
||||
WithStringView des;
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_EQ(view.msg, des.msg);
|
||||
ASSERT_EQ(view.test, des.test);
|
||||
ASSERT_LT(out.data(), des.msg.data());
|
||||
ASSERT_LT(des.msg.data() + des.msg.length(), des.test.data());
|
||||
ASSERT_LE(des.test.data() + des.test.length(), out.data() + out.length());
|
||||
}
|
||||
|
||||
TEST(TestSerde, Result) {
|
||||
{
|
||||
Result<std::string> ser = "OK";
|
||||
XLOGF(INFO, "result: {}", serde::toJsonString(ser));
|
||||
|
||||
auto out = serde::serialize(ser);
|
||||
ASSERT_EQ(out.size(), 1 + 1 + 2);
|
||||
|
||||
Result<std::string> des = makeError(Status::OK);
|
||||
ASSERT_FALSE(des);
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_OK(des);
|
||||
ASSERT_EQ(des, ser);
|
||||
}
|
||||
|
||||
{
|
||||
Result<std::string> ser = makeError(StatusCode::kInvalidArg);
|
||||
XLOGF(INFO, "result: {}", serde::toJsonString(ser));
|
||||
|
||||
auto out = serde::serialize(ser);
|
||||
ASSERT_EQ(out.size(), 1 + 2 + 1);
|
||||
|
||||
Result<std::string> des = makeError(Status::OK);
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_EQ(des.error().code(), StatusCode::kInvalidArg);
|
||||
ASSERT_TRUE(des.error().message().empty());
|
||||
}
|
||||
|
||||
{
|
||||
Result<std::string> ser = makeError(StatusCode::kInvalidArg, "TT");
|
||||
XLOGF(INFO, "result: {}", serde::toJsonString(ser));
|
||||
|
||||
auto out = serde::serialize(ser);
|
||||
ASSERT_EQ(out.size(), 1 + 2 + 1 + 1 + 2);
|
||||
|
||||
Result<std::string> des = makeError(Status::OK);
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_EQ(des.error().code(), StatusCode::kInvalidArg);
|
||||
ASSERT_EQ(des.error().message(), "TT");
|
||||
}
|
||||
|
||||
{
|
||||
Result<Void> ser = Void{};
|
||||
XLOGF(INFO, "result: {}", serde::toJsonString(ser));
|
||||
|
||||
auto out = serde::serialize(ser);
|
||||
ASSERT_EQ(out.size(), 1);
|
||||
|
||||
Result<Void> des = makeError(Status::OK);
|
||||
ASSERT_FALSE(des);
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_OK(des);
|
||||
ASSERT_EQ(des, ser);
|
||||
}
|
||||
|
||||
{
|
||||
Result<Void> ser = makeError(StatusCode::kInvalidArg);
|
||||
XLOGF(INFO, "result: {}", serde::toJsonString(ser));
|
||||
|
||||
auto out = serde::serialize(ser);
|
||||
ASSERT_EQ(out.size(), 1 + 2 + 1);
|
||||
|
||||
Result<Void> des = makeError(Status::OK);
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_EQ(des.error().code(), StatusCode::kInvalidArg);
|
||||
ASSERT_TRUE(des.error().message().empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestSerde, NewDes) {
|
||||
Demo ser;
|
||||
ser.a() = 1;
|
||||
ser.b() = 2;
|
||||
ser.c() = "test";
|
||||
auto out = serde::serialize(ser);
|
||||
|
||||
Demo des;
|
||||
serde::In<std::string_view> in(out);
|
||||
ASSERT_OK(serde::deserialize(des, in));
|
||||
ASSERT_EQ(des.a(), 1);
|
||||
ASSERT_EQ(des.b(), 2);
|
||||
ASSERT_EQ(des.c(), "test");
|
||||
}
|
||||
|
||||
TEST(TestSerde, NewDesJson) {
|
||||
auto json = R"({
|
||||
"a": 1,
|
||||
"b": 2.0,
|
||||
"c": "test"
|
||||
})";
|
||||
|
||||
Demo des;
|
||||
ASSERT_OK(serde::fromJsonString(des, json));
|
||||
ASSERT_EQ(des.a(), 1);
|
||||
ASSERT_EQ(des.b(), 2);
|
||||
ASSERT_EQ(des.c(), "test");
|
||||
}
|
||||
|
||||
TEST(TestSerde, NewDesToml) {
|
||||
auto toml = R"(
|
||||
a = 1
|
||||
b = 2.0
|
||||
c = "test"
|
||||
)";
|
||||
|
||||
Demo des;
|
||||
ASSERT_OK(serde::fromTomlString(des, toml));
|
||||
ASSERT_EQ(des.a(), 1);
|
||||
ASSERT_EQ(des.b(), 2);
|
||||
ASSERT_EQ(des.c(), "test");
|
||||
}
|
||||
|
||||
TEST(TestSerde, NewDesOptional) {
|
||||
struct Foo {
|
||||
SERDE_STRUCT_FIELD(num, std::optional<float>{});
|
||||
SERDE_STRUCT_FIELD(str, std::optional<std::string>{});
|
||||
};
|
||||
|
||||
{
|
||||
Foo ser;
|
||||
auto out = serde::serialize(ser);
|
||||
|
||||
Foo des;
|
||||
serde::In<std::string_view> in(out);
|
||||
ASSERT_OK(serde::deserialize(des, in));
|
||||
ASSERT_FALSE(des.num.has_value());
|
||||
ASSERT_FALSE(des.str.has_value());
|
||||
}
|
||||
|
||||
{
|
||||
Foo ser;
|
||||
auto out = serde::toJsonString(ser);
|
||||
XLOGF(INFO, "json is {}", out);
|
||||
|
||||
Foo des;
|
||||
ASSERT_OK(serde::fromJsonString(des, out));
|
||||
ASSERT_FALSE(des.num.has_value());
|
||||
ASSERT_FALSE(des.str.has_value());
|
||||
}
|
||||
|
||||
{
|
||||
Foo ser;
|
||||
auto out = serde::toTomlString(ser);
|
||||
XLOGF(INFO, "toml is {}", out);
|
||||
|
||||
Foo des;
|
||||
ASSERT_OK(serde::fromTomlString(des, out));
|
||||
ASSERT_FALSE(des.num.has_value());
|
||||
ASSERT_FALSE(des.str.has_value());
|
||||
}
|
||||
|
||||
{
|
||||
Foo des;
|
||||
des.num = 20;
|
||||
des.str = "Test";
|
||||
ASSERT_OK(serde::fromJsonString(des, "{}"));
|
||||
ASSERT_FALSE(des.num.has_value());
|
||||
ASSERT_FALSE(des.str.has_value());
|
||||
}
|
||||
|
||||
{
|
||||
Foo des;
|
||||
des.num = 20;
|
||||
des.str = "Test";
|
||||
ASSERT_OK(serde::fromTomlString(des, ""));
|
||||
ASSERT_FALSE(des.num.has_value());
|
||||
ASSERT_FALSE(des.str.has_value());
|
||||
}
|
||||
|
||||
{
|
||||
Foo ser;
|
||||
ser.num = 27;
|
||||
ser.str = "OK";
|
||||
auto out = serde::serialize(ser);
|
||||
|
||||
Foo des;
|
||||
serde::In<std::string_view> in(out);
|
||||
ASSERT_OK(serde::deserialize(des, in));
|
||||
ASSERT_TRUE(des.num.has_value());
|
||||
ASSERT_EQ(ser.num, des.num);
|
||||
ASSERT_TRUE(des.str.has_value());
|
||||
ASSERT_EQ(ser.str, des.str);
|
||||
}
|
||||
|
||||
{
|
||||
Foo ser;
|
||||
ser.num = 27;
|
||||
ser.str = "OK";
|
||||
auto out = serde::toJsonString(ser);
|
||||
XLOGF(INFO, "json is {}", out);
|
||||
|
||||
Foo des;
|
||||
ASSERT_OK(serde::fromJsonString(des, out));
|
||||
ASSERT_TRUE(des.num.has_value());
|
||||
ASSERT_EQ(ser.num, des.num);
|
||||
ASSERT_TRUE(des.str.has_value());
|
||||
ASSERT_EQ(ser.str, des.str);
|
||||
}
|
||||
|
||||
{
|
||||
Foo ser;
|
||||
ser.num = 27;
|
||||
ser.str = "OK";
|
||||
auto out = serde::toTomlString(ser);
|
||||
XLOGF(INFO, "toml is {}", out);
|
||||
|
||||
Foo des;
|
||||
ASSERT_OK(serde::fromTomlString(des, out));
|
||||
ASSERT_TRUE(des.num.has_value());
|
||||
ASSERT_EQ(ser.num, des.num);
|
||||
ASSERT_TRUE(des.str.has_value());
|
||||
ASSERT_EQ(ser.str, des.str);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestSerde, NewDesVariant) {
|
||||
struct Foo {
|
||||
SERDE_STRUCT_FIELD(num, (std::variant<int, double>{}));
|
||||
SERDE_STRUCT_FIELD(val, (std::variant<std::string, Demo>{}));
|
||||
};
|
||||
|
||||
{
|
||||
Foo ser;
|
||||
auto out = serde::serialize(ser);
|
||||
Foo des;
|
||||
serde::In<std::string_view> in(out);
|
||||
ASSERT_OK(serde::deserialize(des, in));
|
||||
ASSERT_EQ(des.num.index(), 0);
|
||||
ASSERT_EQ(des.val.index(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
Foo ser;
|
||||
ser.num = 20.0;
|
||||
ser.val = Demo{};
|
||||
auto out = serde::serialize(ser);
|
||||
Foo des;
|
||||
ASSERT_EQ(des.num.index(), 0);
|
||||
ASSERT_EQ(des.val.index(), 0);
|
||||
serde::In<std::string_view> in(out);
|
||||
ASSERT_OK(serde::deserialize(des, in));
|
||||
ASSERT_EQ(des.num.index(), 1);
|
||||
ASSERT_EQ(des.val.index(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto json = R"({
|
||||
"num": {
|
||||
"type": "double",
|
||||
"value": 70
|
||||
},
|
||||
"val": {
|
||||
"type": "basic_string",
|
||||
"value": "Test"
|
||||
}
|
||||
})";
|
||||
|
||||
Foo des;
|
||||
ASSERT_OK(serde::fromJsonString(des, json));
|
||||
ASSERT_EQ(des.num.index(), 1);
|
||||
ASSERT_EQ(std::get<1>(des.num), 70.0);
|
||||
ASSERT_EQ(des.val.index(), 0);
|
||||
ASSERT_EQ(std::get<0>(des.val), "Test");
|
||||
}
|
||||
|
||||
{
|
||||
Foo ser;
|
||||
ser.num = 70.0;
|
||||
Demo demo;
|
||||
demo.c() = "OK";
|
||||
ser.val = demo;
|
||||
auto out = serde::toJsonString(ser);
|
||||
XLOGF(INFO, "json is {}", out);
|
||||
|
||||
Foo des;
|
||||
ASSERT_OK(serde::fromJsonString(des, out));
|
||||
ASSERT_EQ(des.num.index(), 1);
|
||||
ASSERT_EQ(std::get<1>(des.num), 70.0);
|
||||
ASSERT_EQ(des.val.index(), 1);
|
||||
ASSERT_EQ(std::get<1>(des.val).c(), "OK");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestSerde, NewDesContainer) {
|
||||
struct Foo {
|
||||
SERDE_STRUCT_FIELD(num, (std::vector<int>{}));
|
||||
SERDE_STRUCT_FIELD(val, (std::set<std::string>{}));
|
||||
SERDE_STRUCT_FIELD(map, (std::map<std::string, std::string>{}));
|
||||
SERDE_STRUCT_FIELD(map2, (std::map<int, std::string>{}));
|
||||
};
|
||||
|
||||
{
|
||||
auto json = R"({
|
||||
"num": [1, 2, 3, 4],
|
||||
"val": ["A", "B"],
|
||||
"map": {
|
||||
"A": "1",
|
||||
"B": "2"
|
||||
},
|
||||
"map2": {
|
||||
"1": "A",
|
||||
"2": "B"
|
||||
}
|
||||
})";
|
||||
|
||||
Foo des;
|
||||
ASSERT_OK(serde::fromJsonString(des, json));
|
||||
ASSERT_EQ(des.num.size(), 4);
|
||||
ASSERT_EQ(des.val.size(), 2);
|
||||
ASSERT_EQ(des.map.size(), 2);
|
||||
ASSERT_EQ(des.map["A"], "1");
|
||||
ASSERT_EQ(des.map["B"], "2");
|
||||
ASSERT_EQ(des.map2.size(), 2);
|
||||
ASSERT_EQ(des.map2[1], "A");
|
||||
ASSERT_EQ(des.map2[2], "B");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestSerde, NewDesEnum) {
|
||||
enum class Foo { A, B, C };
|
||||
|
||||
Foo foo;
|
||||
{
|
||||
auto json = serde::toJsonString(Foo::B);
|
||||
ASSERT_OK(serde::fromJsonString(foo, json));
|
||||
ASSERT_EQ(foo, Foo::B);
|
||||
}
|
||||
|
||||
ASSERT_FALSE(serde::fromJsonString(foo, "\"D\""));
|
||||
}
|
||||
|
||||
TEST(TestSerde, UniquePtr) {
|
||||
struct Foo {
|
||||
SERDE_STRUCT_FIELD(value, std::unique_ptr<std::string>{});
|
||||
};
|
||||
struct Bar {
|
||||
SERDE_STRUCT_FIELD(value, std::unique_ptr<Foo>{});
|
||||
};
|
||||
|
||||
Bar bar;
|
||||
XLOGF(INFO, "{}", bar);
|
||||
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::deserialize(des, serde::serialize(bar)));
|
||||
ASSERT_EQ(des.value, nullptr);
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromJsonString(des, serde::toJsonString(bar)));
|
||||
ASSERT_EQ(des.value, nullptr);
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromTomlString(des, serde::toTomlString(bar)));
|
||||
ASSERT_EQ(des.value, nullptr);
|
||||
}
|
||||
|
||||
bar.value = std::make_unique<Foo>();
|
||||
XLOGF(INFO, "{}", bar);
|
||||
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::deserialize(des, serde::serialize(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_EQ(des.value->value, nullptr);
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromJsonString(des, serde::toJsonString(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_EQ(des.value->value, nullptr);
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromTomlString(des, serde::toTomlString(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_EQ(des.value->value, nullptr);
|
||||
}
|
||||
|
||||
bar.value->value = std::make_unique<std::string>("hello");
|
||||
XLOGF(INFO, "{}", bar);
|
||||
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::deserialize(des, serde::serialize(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_NE(des.value->value, nullptr);
|
||||
ASSERT_EQ(*des.value->value, "hello");
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromJsonString(des, serde::toJsonString(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_NE(des.value->value, nullptr);
|
||||
ASSERT_EQ(*des.value->value, "hello");
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromTomlString(des, serde::toTomlString(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_NE(des.value->value, nullptr);
|
||||
ASSERT_EQ(*des.value->value, "hello");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestSerde, SharedPtr) {
|
||||
struct Foo {
|
||||
SERDE_STRUCT_FIELD(value, std::shared_ptr<std::string>{});
|
||||
};
|
||||
struct Bar {
|
||||
SERDE_STRUCT_FIELD(value, std::shared_ptr<Foo>{});
|
||||
};
|
||||
|
||||
Bar bar;
|
||||
XLOGF(INFO, "{}", bar);
|
||||
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::deserialize(des, serde::serialize(bar)));
|
||||
ASSERT_EQ(des.value, nullptr);
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromJsonString(des, serde::toJsonString(bar)));
|
||||
ASSERT_EQ(des.value, nullptr);
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromTomlString(des, serde::toTomlString(bar)));
|
||||
ASSERT_EQ(des.value, nullptr);
|
||||
}
|
||||
|
||||
bar.value = std::make_unique<Foo>();
|
||||
XLOGF(INFO, "{}", bar);
|
||||
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::deserialize(des, serde::serialize(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_EQ(des.value->value, nullptr);
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromJsonString(des, serde::toJsonString(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_EQ(des.value->value, nullptr);
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromTomlString(des, serde::toTomlString(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_EQ(des.value->value, nullptr);
|
||||
}
|
||||
|
||||
bar.value->value = std::make_unique<std::string>("hello");
|
||||
XLOGF(INFO, "{}", bar);
|
||||
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::deserialize(des, serde::serialize(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_NE(des.value->value, nullptr);
|
||||
ASSERT_EQ(*des.value->value, "hello");
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromJsonString(des, serde::toJsonString(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_NE(des.value->value, nullptr);
|
||||
ASSERT_EQ(*des.value->value, "hello");
|
||||
}
|
||||
{
|
||||
Bar des;
|
||||
ASSERT_OK(serde::fromTomlString(des, serde::toTomlString(bar)));
|
||||
ASSERT_NE(des.value, nullptr);
|
||||
ASSERT_NE(des.value->value, nullptr);
|
||||
ASSERT_EQ(*des.value->value, "hello");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestSerde, AutoFallbackVariant) {
|
||||
std::variant<String, Status> src = Status(StatusCode::kUnknown, "Unknown");
|
||||
static_assert(!serde::is_auto_fallback_variant_v<decltype(src)>);
|
||||
auto s = serde::serialize(src);
|
||||
serde::AutoFallbackVariant<String> dst;
|
||||
static_assert(serde::is_auto_fallback_variant_v<decltype(dst)>);
|
||||
ASSERT_OK(serde::deserialize(dst, s));
|
||||
auto *uvt = std::get_if<serde::UnknownVariantType>(&dst);
|
||||
ASSERT_TRUE(uvt != nullptr);
|
||||
ASSERT_EQ(uvt->type, "Status");
|
||||
}
|
||||
|
||||
TEST(TestSerde, UserBufferAllocator) {
|
||||
std::string ser = "hello world!";
|
||||
auto len = serde::serializeLength(ser);
|
||||
|
||||
std::string buf(len, '\0');
|
||||
serde::serializeToUserBuffer(ser, (uint8_t *)buf.data(), buf.size());
|
||||
|
||||
std::string der;
|
||||
serde::deserialize(der, buf);
|
||||
ASSERT_EQ(ser, der);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::test
|
||||
146
tests/common/serde/TestSerdeTo.cc
Normal file
146
tests/common/serde/TestSerdeTo.cc
Normal file
@@ -0,0 +1,146 @@
|
||||
#include <folly/container/OrderedMap.h>
|
||||
|
||||
#include "common/net/ib/RDMABuf.h"
|
||||
#include "common/serde/Serde.h"
|
||||
#include "common/utils/Address.h"
|
||||
#include "common/utils/Path.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/Size.h"
|
||||
#include "common/utils/StrongType.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
namespace hf3fs::test {
|
||||
namespace {
|
||||
|
||||
STRONG_TYPEDEF(uint32_t, ID);
|
||||
enum class Type { A, B, C, D };
|
||||
|
||||
struct Demo {
|
||||
bool operator==(const Demo &) const = default;
|
||||
|
||||
SERDE_STRUCT_FIELD(name, std::string{"Test"}, nullptr);
|
||||
SERDE_STRUCT_FIELD(size, 1_MB, nullptr);
|
||||
SERDE_STRUCT_FIELD(addr, net::Address{}, nullptr);
|
||||
SERDE_STRUCT_FIELD(id, ID{12138}, nullptr);
|
||||
SERDE_STRUCT_FIELD(type, Type::C, nullptr);
|
||||
SERDE_STRUCT_FIELD(values, (std::set<ID>{ID{1}, ID{2}}), nullptr);
|
||||
SERDE_STRUCT_FIELD(map, (std::map<std::string, Size>{{"a", 1_MB}, {"b", 2_MB}}), nullptr);
|
||||
SERDE_STRUCT_FIELD(option, std::optional<std::string>("OK"), nullptr);
|
||||
SERDE_STRUCT_FIELD(variant, (std::variant<int, std::string>("OK")), nullptr);
|
||||
SERDE_STRUCT_FIELD(path, Path{"/a/b/c"}, nullptr);
|
||||
};
|
||||
|
||||
TEST(TestSerde, ToToml) {
|
||||
Demo demo;
|
||||
XLOGF(INFO, "Toml: {}", serde::toTomlString(demo));
|
||||
|
||||
struct Demo2 {
|
||||
SERDE_STRUCT_FIELD(one, Demo{});
|
||||
SERDE_STRUCT_FIELD(two, Demo{});
|
||||
};
|
||||
XLOGF(INFO, "Toml: {}", serde::toTomlString(Demo2{}));
|
||||
}
|
||||
|
||||
TEST(TestSerde, ToJson) {
|
||||
Demo demo;
|
||||
demo.option = std::nullopt; // count - 1
|
||||
demo.variant = 100;
|
||||
demo.path = "/x/y/z";
|
||||
XLOGF(INFO, "Json: {}", serde::toJsonString(demo));
|
||||
|
||||
auto out = serde::serialize(demo);
|
||||
Demo des;
|
||||
ASSERT_NE(des.path, demo.path);
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_EQ(des, demo);
|
||||
ASSERT_EQ(des.path, demo.path);
|
||||
}
|
||||
|
||||
TEST(TestOrderedMap, Normal) {
|
||||
folly::OrderedMap<int, int> map;
|
||||
constexpr auto N = 1000;
|
||||
for (auto i = 0; i < N; ++i) {
|
||||
map[i] = i;
|
||||
}
|
||||
|
||||
auto idx = 0;
|
||||
for (auto [k, v] : map) {
|
||||
ASSERT_EQ(k, idx);
|
||||
ASSERT_EQ(v, idx);
|
||||
++idx;
|
||||
}
|
||||
ASSERT_EQ(idx, N);
|
||||
|
||||
for (auto i = 0; i < N; i += 2) {
|
||||
map.erase(i);
|
||||
}
|
||||
|
||||
idx = 1;
|
||||
for (auto [k, v] : map) {
|
||||
ASSERT_EQ(k, idx);
|
||||
ASSERT_EQ(v, idx);
|
||||
idx += 2;
|
||||
}
|
||||
ASSERT_EQ(idx, N + 1);
|
||||
|
||||
for (auto i = 0; i < N; i += 2) {
|
||||
map[i] = i;
|
||||
}
|
||||
ASSERT_EQ(map.size(), N);
|
||||
|
||||
idx = 1;
|
||||
for (auto [k, v] : map) {
|
||||
ASSERT_EQ(k, idx);
|
||||
ASSERT_EQ(v, idx);
|
||||
idx += 2;
|
||||
if (idx == N + 1) {
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(idx, N);
|
||||
|
||||
auto item = std::move(map);
|
||||
ASSERT_EQ(item.size(), N);
|
||||
ASSERT_EQ(map.size(), 0);
|
||||
item.clear();
|
||||
ASSERT_TRUE(item.empty());
|
||||
}
|
||||
|
||||
TEST(TestSerde, RDMARemoteBuf) {
|
||||
net::RDMARemoteBuf buf;
|
||||
XLOGF(INFO, "buf is {}", serde::toJsonString(buf));
|
||||
|
||||
auto rkeys = buf.rkeys();
|
||||
rkeys[0].devId = 1;
|
||||
rkeys[0].rkey = 100;
|
||||
rkeys[1].devId = 2;
|
||||
rkeys[1].rkey = 200;
|
||||
|
||||
buf = net::RDMARemoteBuf(0x1000, 1024, rkeys);
|
||||
XLOGF(INFO, "buf is {}", serde::toJsonString(buf));
|
||||
|
||||
auto out = serde::serialize(buf);
|
||||
ASSERT_EQ(out.size(), 8 + 8 + 1 + 8 * 2); // addr + size + len + rkey * 2
|
||||
|
||||
net::RDMARemoteBuf des;
|
||||
ASSERT_OK(serde::deserialize(des, out));
|
||||
ASSERT_EQ(des, buf);
|
||||
}
|
||||
|
||||
TEST(TestSerde, DoubleFormat) {
|
||||
struct Foo {
|
||||
SERDE_STRUCT_FIELD(value, 0ul);
|
||||
};
|
||||
|
||||
Foo foo;
|
||||
foo.value = ~0ul / 2;
|
||||
XLOGF(INFO, "foo is {}", foo);
|
||||
|
||||
auto json = serde::toJsonString(foo);
|
||||
Foo bar;
|
||||
ASSERT_OK(serde::fromJsonString(bar, json));
|
||||
ASSERT_EQ(bar.value, foo.value);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::test
|
||||
425
tests/common/serde/TestService.cc
Normal file
425
tests/common/serde/TestService.cc
Normal file
@@ -0,0 +1,425 @@
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "common/net/Client.h"
|
||||
#include "common/net/Server.h"
|
||||
#include "common/net/Transport.h"
|
||||
#include "common/net/WriteItem.h"
|
||||
#include "common/net/sync/Client.h"
|
||||
#include "common/serde/CallContext.h"
|
||||
#include "common/serde/ClientMockContext.h"
|
||||
#include "common/serde/MessagePacket.h"
|
||||
#include "common/serde/Serde.h"
|
||||
#include "common/serde/Service.h"
|
||||
#include "common/serde/Services.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Reflection.h"
|
||||
#include "common/utils/Thief.h"
|
||||
#include "common/utils/TypeTraits.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/common/net/ib/SetupIB.h"
|
||||
|
||||
namespace hf3fs::serde::test {
|
||||
namespace {
|
||||
|
||||
struct EchoReq {
|
||||
SERDE_STRUCT_FIELD(value, std::string{});
|
||||
};
|
||||
struct EchoRsp {
|
||||
SERDE_STRUCT_FIELD(value, std::string{});
|
||||
};
|
||||
struct FooReq {
|
||||
SERDE_STRUCT_FIELD(value, 0);
|
||||
};
|
||||
struct FooRsp {
|
||||
SERDE_STRUCT_FIELD(value, 0.0);
|
||||
};
|
||||
struct FourReq {
|
||||
SERDE_STRUCT_FIELD(value, 0);
|
||||
};
|
||||
struct FourRsp {
|
||||
SERDE_STRUCT_FIELD(value, 0);
|
||||
};
|
||||
|
||||
SERDE_SERVICE(DemoService, 1) {
|
||||
SERDE_SERVICE_METHOD(echo, 1, EchoReq, EchoRsp);
|
||||
SERDE_SERVICE_METHOD(foo, 2, FooReq, FooRsp);
|
||||
SERDE_SERVICE_METHOD(three, 3, int, int);
|
||||
SERDE_SERVICE_METHOD(four, 4, FourReq, FourRsp);
|
||||
};
|
||||
|
||||
struct ServiceImpl : public serde::ServiceWrapper<ServiceImpl, DemoService> {
|
||||
CoTryTask<EchoRsp> echo(const EchoReq &req) {
|
||||
cnt += 1;
|
||||
EchoRsp rsp;
|
||||
rsp.value = req.value;
|
||||
co_return rsp;
|
||||
}
|
||||
CoTryTask<FooRsp> foo(const FooReq &req) {
|
||||
cnt += 2;
|
||||
FooRsp rsp;
|
||||
rsp.value = req.value + 1;
|
||||
co_return rsp;
|
||||
}
|
||||
CoTryTask<int> three(int req) {
|
||||
cnt += 4;
|
||||
XLOG(ERR, "req is {}", req);
|
||||
co_return req * 2;
|
||||
}
|
||||
CoTryTask<FourRsp> four(const FourReq &req) {
|
||||
cnt += 8;
|
||||
FourRsp rsp;
|
||||
rsp.value = req.value * req.value;
|
||||
co_return rsp;
|
||||
}
|
||||
int cnt = 0;
|
||||
};
|
||||
|
||||
static_assert(DemoService<>::kServiceName == "DemoService");
|
||||
static_assert(DemoService<>::kServiceID == 1);
|
||||
static_assert(refl::Helper::Size<ServiceImpl> == 4);
|
||||
static_assert(!serde::SerdeType<ServiceImpl>);
|
||||
|
||||
struct FakeClient {
|
||||
template <NameWrapper kServiceName,
|
||||
NameWrapper kMethodName,
|
||||
class Req,
|
||||
class Rsp,
|
||||
uint16_t ServiceID,
|
||||
uint16_t MethodID>
|
||||
CoTryTask<Rsp> call(const Req &, auto...) {
|
||||
XLOGF(INFO, "call method {}:{}", ServiceID, MethodID);
|
||||
co_return Rsp{};
|
||||
}
|
||||
};
|
||||
|
||||
class TestService : public net::test::SetupIB {};
|
||||
|
||||
TEST_F(TestService, Client) {
|
||||
FakeClient ctx;
|
||||
DemoService a;
|
||||
folly::coro::blockingWait(a.echo(ctx, EchoReq{}));
|
||||
folly::coro::blockingWait(a.foo(ctx, FooReq{}));
|
||||
}
|
||||
|
||||
TEST_F(TestService, Server) {
|
||||
ServiceImpl impl;
|
||||
ASSERT_EQ(impl.cnt, 0);
|
||||
folly::coro::blockingWait((impl.*refl::Helper::FieldInfo<ServiceImpl, 0>::method)(EchoReq{}));
|
||||
ASSERT_EQ(impl.cnt, 1);
|
||||
folly::coro::blockingWait((impl.*refl::Helper::FieldInfo<ServiceImpl, 1>::method)(FooReq{}));
|
||||
ASSERT_EQ(impl.cnt, 3);
|
||||
|
||||
auto call = [&impl](auto type) {
|
||||
using T = std::decay_t<decltype(type)>;
|
||||
if constexpr (std::is_same_v<T, std::nullptr_t>) {
|
||||
XLOGF(INFO, "not found!");
|
||||
} else {
|
||||
folly::coro::blockingWait((impl.*T::method)(typename T::ReqType{}));
|
||||
}
|
||||
};
|
||||
callByIdx<refl::Helper::FieldInfoList<ServiceImpl>>(call, 0);
|
||||
ASSERT_EQ(impl.cnt, 4);
|
||||
callByIdx<refl::Helper::FieldInfoList<ServiceImpl>>(call, 1);
|
||||
ASSERT_EQ(impl.cnt, 6);
|
||||
callByIdx<refl::Helper::FieldInfoList<ServiceImpl>>(call, 4);
|
||||
ASSERT_EQ(impl.cnt, 6);
|
||||
}
|
||||
|
||||
struct FakeCallContext {
|
||||
Result<std::string> handle(const std::string &str) {
|
||||
RETURN_AND_LOG_ON_ERROR(serde::deserialize(recv_, str));
|
||||
auto methodPointer = MethodExtractor<ServiceImpl, FakeCallContext>::get(recv_.methodId);
|
||||
if (LIKELY(methodPointer != nullptr)) {
|
||||
return (this->*methodPointer)();
|
||||
} else {
|
||||
XLOGF(INFO, "method not found!");
|
||||
return makeError(RPCCode::kInvalidMethodID);
|
||||
}
|
||||
}
|
||||
|
||||
template <class FieldInfo>
|
||||
Result<std::string> call() {
|
||||
typename FieldInfo::ReqType req;
|
||||
RETURN_AND_LOG_ON_ERROR(serde::deserialize(req, recv_.payload));
|
||||
auto rsp = folly::coro::blockingWait((impl_.*FieldInfo::method)(req));
|
||||
RETURN_AND_LOG_ON_ERROR(rsp);
|
||||
MessagePacket send(*rsp);
|
||||
return serde::serialize(send);
|
||||
}
|
||||
|
||||
ServiceImpl impl_;
|
||||
MessagePacket<> recv_;
|
||||
};
|
||||
|
||||
class FakeServer {
|
||||
public:
|
||||
template <NameWrapper kServiceName,
|
||||
NameWrapper kMethodName,
|
||||
class Req,
|
||||
class Rsp,
|
||||
uint16_t ServiceID,
|
||||
uint16_t MethodID>
|
||||
CoTryTask<Rsp> call(const Req &req, auto...) {
|
||||
XLOGF(INFO, "call method {}:{}", ServiceID, MethodID);
|
||||
MessagePacket send(req);
|
||||
send.serviceId = ServiceID;
|
||||
send.methodId = MethodID;
|
||||
auto bytes = serde::serialize(send);
|
||||
|
||||
FakeCallContext ctx;
|
||||
auto result = ctx.handle(bytes);
|
||||
CO_RETURN_ON_ERROR(result);
|
||||
MessagePacket recv;
|
||||
CO_RETURN_ON_ERROR(serde::deserialize(recv, *result));
|
||||
Rsp rsp;
|
||||
CO_RETURN_ON_ERROR(serde::deserialize(rsp, recv.payload));
|
||||
co_return rsp;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TestService, ClientToServer) {
|
||||
FakeServer ctx;
|
||||
DemoService a;
|
||||
|
||||
{
|
||||
EchoReq req;
|
||||
req.value = "hello";
|
||||
auto result = folly::coro::blockingWait(a.echo(ctx, req));
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(req.value, result->value);
|
||||
}
|
||||
|
||||
{
|
||||
FooReq req;
|
||||
req.value = 100;
|
||||
auto result = folly::coro::blockingWait(a.foo(ctx, req));
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(req.value + 1, result->value);
|
||||
}
|
||||
|
||||
{
|
||||
auto req = 100;
|
||||
auto result = folly::coro::blockingWait(a.three(ctx, req));
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(req * 2, *result);
|
||||
}
|
||||
|
||||
{
|
||||
FourReq req;
|
||||
req.value = 100;
|
||||
auto result = folly::coro::blockingWait(a.four(ctx, req));
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(req.value * req.value, result->value);
|
||||
}
|
||||
}
|
||||
|
||||
static_assert(sizeof(CallContext::MethodType) == 16);
|
||||
|
||||
template <class Context = serde::CallContext>
|
||||
struct RealService : public serde::ServiceWrapper<RealService<Context>, DemoService> {
|
||||
CoTryTask<EchoRsp> echo(Context &, const EchoReq &req) {
|
||||
cnt += 1;
|
||||
EchoRsp rsp;
|
||||
rsp.value = req.value;
|
||||
co_return rsp;
|
||||
}
|
||||
CoTryTask<FooRsp> foo(Context &, const FooReq &req) {
|
||||
cnt += 2;
|
||||
FooRsp rsp;
|
||||
rsp.value = req.value + 1;
|
||||
co_return rsp;
|
||||
}
|
||||
CoTryTask<int> three(Context &, const int &req) {
|
||||
cnt += 4;
|
||||
co_return req * 2;
|
||||
}
|
||||
CoTryTask<FourRsp> four(Context &, const FourReq &) {
|
||||
cnt += 8;
|
||||
co_await folly::coro::sleep(std::chrono::milliseconds(10));
|
||||
co_return makeError(StatusCode::kInvalidConfig);
|
||||
}
|
||||
std::atomic<int> cnt = 0;
|
||||
};
|
||||
|
||||
TEST_F(TestService, Normal) {
|
||||
Services services;
|
||||
ASSERT_OK(services.addService(std::make_unique<RealService<>>(), false));
|
||||
|
||||
for (auto o = 0; o < 2; ++o) {
|
||||
auto &service = services.getServiceById(RealService<>::kServiceID + o, false);
|
||||
ASSERT_NE(service.getter, nullptr);
|
||||
|
||||
for (auto i = 0; i < 100; ++i) {
|
||||
if (o == 0 && 1 <= i && i <= 4) {
|
||||
ASSERT_NE(service.getter(i), nullptr);
|
||||
} else {
|
||||
ASSERT_EQ(service.getter(i), &CallContext::invalidId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TestService, AddSerdeService) {
|
||||
net::Server::Config config;
|
||||
net::Server server(config);
|
||||
ASSERT_OK(server.setup());
|
||||
ASSERT_OK(server.addSerdeService(std::make_unique<RealService<>>()));
|
||||
|
||||
ASSERT_OK(server.start());
|
||||
server.stopAndJoin();
|
||||
}
|
||||
|
||||
TEST_F(TestService, CallContext) {
|
||||
auto service = std::make_unique<RealService<>>();
|
||||
auto pointer = service.get();
|
||||
|
||||
Services services;
|
||||
ASSERT_OK(services.addService(std::move(service), false));
|
||||
|
||||
EchoReq req;
|
||||
req.value = "hello";
|
||||
MessagePacket send(req);
|
||||
send.serviceId = 1;
|
||||
send.methodId = 1;
|
||||
auto bytes = serde::serialize(send);
|
||||
|
||||
MessagePacket<> recv;
|
||||
ASSERT_OK(serde::deserialize(recv, bytes));
|
||||
|
||||
net::Server::Config config;
|
||||
net::Server server(config);
|
||||
ASSERT_OK(server.setup());
|
||||
ASSERT_OK(server.start());
|
||||
auto tr = net::Transport::create(net::Address{}, server.groups().front()->ioWorker());
|
||||
|
||||
{
|
||||
ASSERT_EQ(pointer->cnt, 0);
|
||||
CallContext ctx(recv, tr, services.getServiceById(recv.serviceId, false));
|
||||
folly::coro::blockingWait(ctx.handle());
|
||||
ASSERT_EQ(pointer->cnt, 1);
|
||||
}
|
||||
|
||||
{
|
||||
recv.methodId = 0;
|
||||
CallContext ctx(recv, tr, services.getServiceById(recv.serviceId, false));
|
||||
folly::coro::blockingWait(ctx.handle());
|
||||
ASSERT_EQ(pointer->cnt, 1);
|
||||
}
|
||||
|
||||
{
|
||||
recv.serviceId = 0;
|
||||
CallContext ctx(recv, tr, services.getServiceById(recv.serviceId, false));
|
||||
folly::coro::blockingWait(ctx.handle());
|
||||
ASSERT_EQ(pointer->cnt, 1);
|
||||
}
|
||||
|
||||
server.stopAndJoin();
|
||||
}
|
||||
|
||||
TEST_F(TestService, ClientContext) {
|
||||
// 1. create service.
|
||||
auto service = std::make_unique<RealService<>>();
|
||||
auto pointer = service.get();
|
||||
|
||||
// 2. start server.
|
||||
auto ctx = serde::ClientMockContext::create(std::move(service), net::Address::RDMA);
|
||||
|
||||
// 4. call client ctx.
|
||||
{
|
||||
DemoService stub;
|
||||
EchoReq req{"hello"};
|
||||
auto result = folly::coro::blockingWait(stub.echo(ctx, req));
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(result->value, req.value);
|
||||
ASSERT_EQ(pointer->cnt, 1);
|
||||
}
|
||||
|
||||
{
|
||||
DemoService stub;
|
||||
FooReq req{100};
|
||||
auto result = folly::coro::blockingWait(stub.foo(ctx, req));
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(result->value, req.value + 1);
|
||||
ASSERT_EQ(pointer->cnt, 3);
|
||||
}
|
||||
|
||||
{
|
||||
DemoService stub;
|
||||
FooReq req{100};
|
||||
auto result = stub.fooSync(ctx, req);
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(result->value, req.value + 1);
|
||||
ASSERT_EQ(pointer->cnt, 5);
|
||||
}
|
||||
|
||||
{
|
||||
DemoService stub;
|
||||
Timestamp timestamp{};
|
||||
auto result = folly::coro::blockingWait(stub.three(ctx, 100, nullptr, ×tamp));
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(*result, 200);
|
||||
ASSERT_EQ(pointer->cnt, 9);
|
||||
serde::iterate(
|
||||
[](std::string_view name, auto &value) {
|
||||
if (value == 0) {
|
||||
XLOGF(ERR, "{} is zero!", name);
|
||||
ASSERT_NE(value, 0);
|
||||
}
|
||||
},
|
||||
timestamp);
|
||||
XLOGF(INFO, "timestamp is {}", timestamp);
|
||||
}
|
||||
|
||||
{
|
||||
DemoService stub;
|
||||
FourReq req{100};
|
||||
auto result = folly::coro::blockingWait(stub.four(ctx, req));
|
||||
ASSERT_FALSE(result);
|
||||
XLOGF(INFO, "result is error: {}", result.error());
|
||||
}
|
||||
|
||||
{
|
||||
DemoService stub;
|
||||
FourReq req{100};
|
||||
net::UserRequestOptions options;
|
||||
options.timeout = 1_us;
|
||||
auto result = folly::coro::blockingWait(stub.four(ctx, req, &options));
|
||||
ASSERT_FALSE(result);
|
||||
XLOGF(INFO, "result is error: {}", result.error());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TestService, CallSync) {
|
||||
using namespace hf3fs::net;
|
||||
|
||||
// note: No IPoIB device in gitlab ci docker runner.
|
||||
std::array<Address::Type, 4> networks{Address::LOCAL, Address::TCP /*, Address::IPoIB */};
|
||||
for (auto network : networks) {
|
||||
Server::Config serverConfig;
|
||||
serverConfig.groups(0).set_network_type(network);
|
||||
Server server_{serverConfig};
|
||||
|
||||
ASSERT_OK(server_.addSerdeService(std::make_unique<RealService<>>()));
|
||||
ASSERT_OK(server_.setup());
|
||||
ASSERT_OK(server_.start());
|
||||
auto serverAddr = server_.groups().front()->addressList().front();
|
||||
|
||||
net::sync::Client::Config clientConfig;
|
||||
net::sync::Client client_{clientConfig};
|
||||
auto ctx = client_.serdeCtx(serverAddr);
|
||||
|
||||
EchoReq req{"hello"};
|
||||
auto result = DemoService<>::echoSync(ctx, req);
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(result->value, req.value);
|
||||
|
||||
server_.stopAndJoin();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::serde::test
|
||||
32
tests/common/serde/TestTypeName.cc
Normal file
32
tests/common/serde/TestTypeName.cc
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <boost/intrusive/intrusive_fwd.hpp>
|
||||
|
||||
#include "common/serde/TypeName.h"
|
||||
#include "common/utils/Nameof.hpp"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
namespace hf3fs::test {
|
||||
namespace {
|
||||
|
||||
static_assert(serde::type_name_v<int> == "int");
|
||||
static_assert(serde::type_name_v<float> == "float");
|
||||
static_assert(serde::type_name_v<double> == "double");
|
||||
static_assert(serde::type_name_v<std::string> == "basic_string"); // template arguments are ignored
|
||||
static_assert(serde::type_name_v<std::vector<int>> == "vector"); // template arguments are ignored
|
||||
|
||||
struct Foo {};
|
||||
static_assert(serde::type_name_v<Foo> == "Foo");
|
||||
|
||||
struct Bar {
|
||||
static constexpr std::string_view kTypeNameForSerde = "BAR";
|
||||
};
|
||||
static_assert(serde::type_name_v<Bar> == "BAR");
|
||||
|
||||
static_assert(serde::variant_type_names_v<std::variant<int, float>>[0] == "int");
|
||||
static_assert(serde::variant_type_names_v<std::variant<int, float>>[1] == "float");
|
||||
static_assert(serde::variant_type_names_v<std::variant<std::string, std::vector<int>>>[0] == "basic_string");
|
||||
static_assert(serde::variant_type_names_v<std::variant<std::string, std::vector<int>>>[1] == "vector");
|
||||
static_assert(serde::variant_type_names_v<std::variant<Foo, Bar>>[0] == "Foo");
|
||||
static_assert(serde::variant_type_names_v<std::variant<Foo, Bar>>[1] == "BAR");
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::test
|
||||
43
tests/common/serde/TestVisit.cc
Normal file
43
tests/common/serde/TestVisit.cc
Normal file
@@ -0,0 +1,43 @@
|
||||
#include <folly/json.h>
|
||||
|
||||
#include "common/serde/Serde.h"
|
||||
#include "common/serde/Visit.h"
|
||||
#include "common/utils/Nameof.hpp"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
template <>
|
||||
class hf3fs::serde::VisitOut<void> {
|
||||
public:
|
||||
auto tableBegin() { XLOGF(INFO, "start a table"); }
|
||||
void tableEnd() { XLOGF(INFO, "end a table"); }
|
||||
void arrayBegin() { XLOGF(INFO, "start an array"); }
|
||||
void arrayEnd() { XLOGF(INFO, "end an array"); }
|
||||
void variantBegin() { XLOGF(INFO, "start a variant"); }
|
||||
void variantEnd() { XLOGF(INFO, "end a variant"); }
|
||||
void key(std::string_view key) { XLOGF(INFO, "key {}", key); }
|
||||
void value(auto &&value) { XLOGF(INFO, "value {}", nameof::nameof_full_type<std::decay_t<decltype(value)>>()); }
|
||||
};
|
||||
|
||||
namespace hf3fs::test {
|
||||
namespace {
|
||||
|
||||
enum class Type {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
};
|
||||
|
||||
struct Foo {
|
||||
SERDE_STRUCT_FIELD(val, double{});
|
||||
SERDE_STRUCT_FIELD(type, Type::A);
|
||||
SERDE_STRUCT_FIELD(num, (std::variant<int, double>{}));
|
||||
SERDE_STRUCT_FIELD(vec, std::vector<std::string>{});
|
||||
};
|
||||
|
||||
TEST(TestVist, Normal) {
|
||||
serde::VisitOut<void> out;
|
||||
serde::visit(Foo{}, out);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::test
|
||||
Reference in New Issue
Block a user