From a0fcd8b98574d25e8012df17e9b87b011323748c Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Wed, 16 Oct 2024 12:35:45 +0200 Subject: [PATCH] add tests --- include/td/common/DataBuffer.h | 33 ++- include/td/common/VarInt.h | 2 +- include/td/misc/Test.h | 70 +++++ include/td/protocol/packet/PacketSender.h | 59 ----- src/td/misc/Test.cpp | 14 + src/td/protocol/command/CommandFactory.cpp | 26 ++ src/td/protocol/command/CommandVisitor.cpp | 11 + src/td/protocol/packet/PacketFactory.cpp | 26 ++ src/td/protocol/packet/PacketSerializer.cpp | 239 ++++++++++++++++++ src/td/protocol/packet/PacketVisitor.cpp | 11 + src/td/protocol/packet/Packets.cpp | 23 ++ .../protocol/packet/PacketFactory_test.cpp | 15 ++ .../protocol/packet/PacketSerializer_test.cpp | 31 +++ xmake.lua | 22 +- 14 files changed, 515 insertions(+), 67 deletions(-) create mode 100644 include/td/misc/Test.h delete mode 100644 include/td/protocol/packet/PacketSender.h create mode 100644 src/td/misc/Test.cpp create mode 100644 src/td/protocol/command/CommandFactory.cpp create mode 100644 src/td/protocol/command/CommandVisitor.cpp create mode 100644 src/td/protocol/packet/PacketFactory.cpp create mode 100644 src/td/protocol/packet/PacketSerializer.cpp create mode 100644 src/td/protocol/packet/PacketVisitor.cpp create mode 100644 src/td/protocol/packet/Packets.cpp create mode 100644 test/blitz/protocol/packet/PacketFactory_test.cpp create mode 100644 test/blitz/protocol/packet/PacketSerializer_test.cpp diff --git a/include/td/common/DataBuffer.h b/include/td/common/DataBuffer.h index f471ca8..1d5a635 100644 --- a/include/td/common/DataBuffer.h +++ b/include/td/common/DataBuffer.h @@ -10,8 +10,8 @@ #include #include #include -#include #include +#include namespace td { @@ -76,11 +76,23 @@ class DataBuffer { /** * \brief Append a vector to the buffer by first writing the size - * \param data The buffer to append + * \param data The vector to append */ template DataBuffer& operator<<(const std::vector& data) { - *this << VarInt {data.size()}; + *this << VarInt{data.size()}; + for (const auto& element : data) { + *this << element; + } + return *this; + } + + /** + * \brief Append an array to the buffer by first writing the size + * \param data The buffer to append + */ + template + DataBuffer& operator<<(const std::array& data) { for (const auto& element : data) { *this << element; } @@ -112,7 +124,7 @@ class DataBuffer { DataBuffer& operator>>(std::string& str); /** - * \brief Read a vector (size + data) from the buffer + * \brief Read a vector (size + data) from the buffer * \pre The vector is assumed to be empty */ template @@ -127,6 +139,19 @@ class DataBuffer { return *this; } + /** + * \brief Read an array from the buffer + */ + template + DataBuffer& operator>>(std::array& data) { + for (std::size_t i = 0; i < Size; i++) { + T newElement; + *this >> newElement; + data[i] = newElement; + } + return *this; + } + /** * \brief Write some data to the buffer * \param buffer The buffer to write diff --git a/include/td/common/VarInt.h b/include/td/common/VarInt.h index 02c33d5..12ac986 100644 --- a/include/td/common/VarInt.h +++ b/include/td/common/VarInt.h @@ -2,7 +2,7 @@ /** * \file VarInt.h - * \brief File containing the blitz::VarInt class + * \brief File containing the td::VarInt class */ #include diff --git a/include/td/misc/Test.h b/include/td/misc/Test.h new file mode 100644 index 0000000..58cb334 --- /dev/null +++ b/include/td/misc/Test.h @@ -0,0 +1,70 @@ +#pragma once + +/** + * \file Test.h + * \brief File containing unit testing utilities + */ + +#include +#include + +namespace td { +namespace test { + +/** + * \def TD_TEST_SUCCESSFUL + * \brief Used in tests to indicate that a test was successful + */ +#define TD_TEST_SUCCESSFUL 0 + +/** + * \def BLITZ_TEST_FAILED + * \brief Used in tests to indicate that a test failed + */ +#define BLITZ_TEST_FAILED 1 + +#ifndef __FUNCTION_NAME__ +#ifdef _WIN32 +#define __FUNCTION_NAME__ __FUNCTION__ +#else +#define __FUNCTION_NAME__ __PRETTY_FUNCTION__ +#endif +#endif + +/** + * \def blitz_test_assert + * \param ... The expression to evaluate + * \brief Evaluates the expression and exits the program if not valid. + * \note It works like a basic assert() but also in release mode + */ +#define td_test_assert(...) \ + if (!static_cast(__VA_ARGS__)) { \ + td::test::LogAssert(#__VA_ARGS__, __FILE__, __LINE__, __FUNCTION_NAME__); \ + std::exit(BLITZ_TEST_FAILED); \ + } + + +/** + * \def blitz_debug_assert + * \param ... The expression to execute + * \brief Assertion without checks in release mode + * \note The expression is always executed. However, in release, no checks are made ! + */ +#ifdef NDEBUG +#define td_debug_assert(...) __VA_ARGS__ +#else +#define td_debug_assert td_test_assert +#endif + + +/** + * \brief Prints an error message associated with a failed assertion + * \param expression The expression that was tested + * \param file The file in which the assertion failed + * \param line The line in the file in which the assertion failed + * \param function The function in which the assertion failed + */ +void LogAssert(const char* expression, const char* file, int line, const char* function); + +} // namespace test +} // namespace td diff --git a/include/td/protocol/packet/PacketSender.h b/include/td/protocol/packet/PacketSender.h deleted file mode 100644 index e25c85d..0000000 --- a/include/td/protocol/packet/PacketSender.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include - -namespace td { - -class NetworkInterface; - -namespace protocol { - -#define DeclarePacket(PacketName, Reliability, ...) void Visit(const protocol::packets::PacketName& a_Packet) override; - - - - - -/////////////////////// -/* PacketBroadcaster */ -/////////////////////// -class PacketBroadcaster : public protocol::PacketVisitor { - private: - NetworkInterface& m_NetworkInterface; - - public: - PacketBroadcaster(NetworkInterface& a_NetworkInterface) : m_NetworkInterface(a_NetworkInterface) {} - - void BroadcastPacket(const protocol::Packet& a_Packet) { - Check(a_Packet); - } - - DeclareAllPacket() -}; - - - - - -////////////////// -/* PacketSender */ -////////////////// -class PacketSender : public protocol::PacketVisitor { - private: - NetworkInterface& m_NetworkInterface; - PeerID m_PeerId; - - public: - PacketSender(PeerID a_PeerId, NetworkInterface& a_NetworkInterface) : m_PeerId(a_PeerId), m_NetworkInterface(a_NetworkInterface) {} - - void SendPacket(const protocol::Packet& a_Packet) { - Check(a_Packet); - } - - DeclareAllPacket() -}; - -#undef DeclarePacket - -} // namespace protocol -} // namespace td diff --git a/src/td/misc/Test.cpp b/src/td/misc/Test.cpp new file mode 100644 index 0000000..21b8543 --- /dev/null +++ b/src/td/misc/Test.cpp @@ -0,0 +1,14 @@ +#include +#include +#include + +namespace td { +namespace test { + +void LogAssert(const char* expression, const char* file, int line, const char* function) { + utils::LOGE(utils::Format("%s:%i: %s: Assertion failed !", file, line, function)); + utils::LOGE(utils::Format(" %i |\t%s;", line, expression)); +} + +} // namespace test +} // namespace td diff --git a/src/td/protocol/command/CommandFactory.cpp b/src/td/protocol/command/CommandFactory.cpp new file mode 100644 index 0000000..d1e8a87 --- /dev/null +++ b/src/td/protocol/command/CommandFactory.cpp @@ -0,0 +1,26 @@ +#include + +#include +#include +#include + +namespace td { +namespace protocol { +namespace CommandFactory { + +using CommandCreator = std::function()>; + +#define DeclareCommand(CommandName, ...) std::make_unique(), + +static std::array, static_cast(CommandType::COMMAND_COUNT)> Commands = { + DeclareAllCommand() +}; + +const std::unique_ptr& CreateReadOnlyCommand(CommandType a_Type) { + assert(a_Type < CommandType::COMMAND_COUNT); + return Commands[static_cast(a_Type)]; +} + +} // namespace CommandFactory +} // namespace protocol +} // namespace td diff --git a/src/td/protocol/command/CommandVisitor.cpp b/src/td/protocol/command/CommandVisitor.cpp new file mode 100644 index 0000000..a018309 --- /dev/null +++ b/src/td/protocol/command/CommandVisitor.cpp @@ -0,0 +1,11 @@ +#include + +namespace td { +namespace protocol { + +void CommandVisitor::Check(const Command& a_Command) { + a_Command.Accept(*this); +} + +} // namespace protocol +} // namespace td diff --git a/src/td/protocol/packet/PacketFactory.cpp b/src/td/protocol/packet/PacketFactory.cpp new file mode 100644 index 0000000..d1aa219 --- /dev/null +++ b/src/td/protocol/packet/PacketFactory.cpp @@ -0,0 +1,26 @@ +#include + +#include +#include +#include + +namespace td { +namespace protocol { +namespace PacketFactory { + +using PacketCreator = std::function()>; + +#define DeclarePacket(PacketName, ...) std::make_unique(), + +static std::array, static_cast(PacketType::PACKET_COUNT)> Packets = { + DeclareAllPacket() +}; + +const std::unique_ptr& CreateReadOnlyPacket(PacketType a_Type) { + assert(a_Type < PacketType::PACKET_COUNT); + return Packets[static_cast(a_Type)]; +} + +} // namespace PacketFactory +} // namespace protocol +} // namespace td diff --git a/src/td/protocol/packet/PacketSerializer.cpp b/src/td/protocol/packet/PacketSerializer.cpp new file mode 100644 index 0000000..144ad24 --- /dev/null +++ b/src/td/protocol/packet/PacketSerializer.cpp @@ -0,0 +1,239 @@ +#include + +#include +#include + +#include +#include + +namespace td { +namespace protocol { +namespace PacketSerializer { + +#define DeclarePacket(PacketName, ...) \ + void Visit(const packets::PacketName& a_Packet) override { \ + const auto& packetData = a_Packet.m_Data; \ + SerializePacketData(packetData); \ + } \ + \ + void SerializePacketData(const packets::PacketName::PacketDataType& a_Packet); + + + + +class Serializer : public PacketVisitor { + private: + DataBuffer& m_Buffer; + + public: + Serializer(DataBuffer& a_Buffer) : m_Buffer(a_Buffer) {} + + void Serialize(const Packet& a_Packet) { + m_Buffer << static_cast(a_Packet.GetType()); + Check(a_Packet); + } + + DeclareAllPacket() +}; + +#undef DeclarePacket + + + + + +#define DeclarePacket(PacketName, ...) \ + void Visit(const packets::PacketName& a_Packet) override { \ + auto packetPtr = PacketFactory::CreatePacket(); \ + auto& packetData = packetPtr->m_Data; \ + \ + DeserializePacketData(packetData); \ + \ + m_Packet = std::move(packetPtr); \ + } \ + \ + void DeserializePacketData(packets::PacketName::PacketDataType& a_Packet); + + + +class Deserializer : public PacketVisitor { + private: + DataBuffer& m_Buffer; + PacketPtr m_Packet; + + public: + Deserializer(DataBuffer& a_Buffer) : m_Buffer(a_Buffer) {} + + bool Deserialize(const PacketPtr& a_Packet) { + try { + Check(*a_Packet.get()); + } catch (std::exception& e) { + utils::LOGE(utils::Format("[PacketSerializer::Deserializer] %s", e.what())); + return false; + } + return true; + } + + PacketPtr& GetPacket() { + return m_Packet; + } + + DeclareAllPacket() +}; + + + + + +DataBuffer Serialize(const Packet& a_Packet) { + DataBuffer buffer; + + Serializer serializer(buffer); + serializer.Serialize(a_Packet); + + return buffer; +} + +std::unique_ptr Deserialize(DataBuffer& a_Data) { + PacketID packetId; + a_Data >> packetId; + + if (packetId >= static_cast(PacketType::PACKET_COUNT)) + return nullptr; + + PacketType packetType = PacketType(packetId); + + // for double-dispatch + const PacketPtr& emptyPacket = PacketFactory::CreateReadOnlyPacket(packetType); + + Deserializer deserializer(a_Data); + + if (deserializer.Deserialize(emptyPacket)) { + PacketPtr packet = std::move(deserializer.GetPacket()); + return packet; + } + + return nullptr; +} + + + + + +//--------------------------------------------- +// Packet serializer implementation +//---------------------------------------------- + + + + + +void Serializer::SerializePacketData(const pdata::PlayerLogin& a_Packet) { + m_Buffer << a_Packet.m_PlayerName; +} + +void Deserializer::DeserializePacketData(pdata::PlayerLogin& a_Packet) { + m_Buffer >> a_Packet.m_PlayerName; +} + + + + + +void Serializer::SerializePacketData(const pdata::LoggingSuccess& a_Packet) { + m_Buffer << a_Packet.m_PlayerId; +} + +void Deserializer::DeserializePacketData(pdata::LoggingSuccess& a_Packet) { + m_Buffer >> a_Packet.m_PlayerId; +} + + + + + +void Serializer::SerializePacketData(const pdata::PlayerJoin& a_Packet) { + m_Buffer << a_Packet.m_Player; +} + +void Deserializer::DeserializePacketData(pdata::PlayerJoin& a_Packet) { + m_Buffer >> a_Packet.m_Player; +} + + + + +void Serializer::SerializePacketData(const pdata::PlayerLeave& a_Packet) { + m_Buffer << a_Packet.m_PlayerId; +} + +void Deserializer::DeserializePacketData(pdata::PlayerLeave& a_Packet) { + m_Buffer >> a_Packet.m_PlayerId; +} + + + + + +void Serializer::SerializePacketData(const pdata::KeepAlive& a_Packet) { + m_Buffer << a_Packet.m_KeepAliveId; +} + +void Deserializer::DeserializePacketData(pdata::KeepAlive& a_Packet) { + m_Buffer >> a_Packet.m_KeepAliveId; +} + + + + + +void Serializer::SerializePacketData(const pdata::Disconnect& a_Packet) { + m_Buffer << a_Packet.m_Reason; +} + +void Deserializer::DeserializePacketData(pdata::Disconnect& a_Packet) { + m_Buffer >> a_Packet.m_Reason; +} + + + + + +void Serializer::SerializePacketData(const pdata::ChatMessage& a_Packet) { + m_Buffer << a_Packet.m_Text; +} + +void Deserializer::DeserializePacketData(pdata::ChatMessage& a_Packet) { + m_Buffer >> a_Packet.m_Text; +} + + + + + +void Serializer::SerializePacketData(const pdata::LockSteps& a_Packet) { + m_Buffer << a_Packet.m_FirstFrameNumber << a_Packet.m_LockSteps; +} + +void Deserializer::DeserializePacketData(pdata::LockSteps& a_Packet) { + m_Buffer >> a_Packet.m_FirstFrameNumber >> a_Packet.m_LockSteps; +} + + + + + +void Serializer::SerializePacketData(const pdata::BeginGame& a_Packet) { + m_Buffer << a_Packet.m_Map << a_Packet.m_BlueTeam << a_Packet.m_RedTeam << a_Packet.m_Map; +} + +void Deserializer::DeserializePacketData(pdata::BeginGame& a_Packet) { + m_Buffer >> a_Packet.m_Map >> a_Packet.m_BlueTeam >> a_Packet.m_RedTeam >> a_Packet.m_Map; +} + + + + +} // namespace PacketSerializer +} // namespace protocol +} // namespace td diff --git a/src/td/protocol/packet/PacketVisitor.cpp b/src/td/protocol/packet/PacketVisitor.cpp new file mode 100644 index 0000000..940046c --- /dev/null +++ b/src/td/protocol/packet/PacketVisitor.cpp @@ -0,0 +1,11 @@ +#include + +namespace td { +namespace protocol { + +void PacketVisitor::Check(const Packet& a_Packet) { + a_Packet.Accept(*this); +} + +} // namespace protocol +} // namespace td diff --git a/src/td/protocol/packet/Packets.cpp b/src/td/protocol/packet/Packets.cpp new file mode 100644 index 0000000..1fbc535 --- /dev/null +++ b/src/td/protocol/packet/Packets.cpp @@ -0,0 +1,23 @@ +#define TD_INSTANCIATE_PACKETS +#include + +#include + +namespace td { +namespace protocol { + +template +packets::ConcretePacket::ConcretePacket(const PacketDataType& a_Data) : m_Data(a_Data) {} + +template +void packets::ConcretePacket::Accept(PacketVisitor& a_Visitor) const { + a_Visitor.Visit(*this); +} + +#define DeclarePacket(PacketName, packetSendType, packetSenderType) \ + static_assert(static_cast(PacketSendType::packetSendType) && static_cast(PacketSenderType::packetSenderType)); + +DeclareAllPacket() + +} // namespace protocol +} // namespace td diff --git a/test/blitz/protocol/packet/PacketFactory_test.cpp b/test/blitz/protocol/packet/PacketFactory_test.cpp new file mode 100644 index 0000000..b905677 --- /dev/null +++ b/test/blitz/protocol/packet/PacketFactory_test.cpp @@ -0,0 +1,15 @@ +#include + +static int Test() { + for (std::size_t i = 0; i < static_cast(td::protocol::PacketType::PACKET_COUNT); i++) { + td::protocol::PacketType packetType = td::protocol::PacketType(i); + + if (td::protocol::PacketFactory::CreateReadOnlyPacket(packetType)->GetType() != packetType) + return 1; + } + return 0; +} + +int main() { + return Test(); +} \ No newline at end of file diff --git a/test/blitz/protocol/packet/PacketSerializer_test.cpp b/test/blitz/protocol/packet/PacketSerializer_test.cpp new file mode 100644 index 0000000..c803223 --- /dev/null +++ b/test/blitz/protocol/packet/PacketSerializer_test.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include + +namespace tp = td::protocol; + +static int TestAllPackets() { + for (std::size_t i = 0; i < static_cast(tp::PacketType::PACKET_COUNT); i++) { + const auto& packet = tp::PacketFactory::CreateReadOnlyPacket(tp::PacketType(i)); + + td::DataBuffer buffer = tp::PacketSerializer::Serialize(*packet.get()); + + tp::PacketPtr packet2 = tp::PacketSerializer::Deserialize(buffer); + + td_test_assert(packet2 != nullptr); + + td_test_assert(packet2->GetType() == packet->GetType()); + } + return 0; +} + +static void Test() { + tp::packets::ChatMessage packet({"caca"}); + td::DataBuffer buffer = tp::PacketSerializer::Serialize(packet); +} + +int main() { + Test(); + return TestAllPackets(); +} \ No newline at end of file diff --git a/xmake.lua b/xmake.lua index 18651fe..e2402fe 100644 --- a/xmake.lua +++ b/xmake.lua @@ -3,11 +3,27 @@ add_rules("mode.debug", "mode.release") add_requires("fpm") target("Tower-Defense2") - add_includedirs("include") - set_kind("binary") + add_includedirs("include", {public = true}) + set_kind("static") add_files("src/**.cpp") - add_packages("fpm") + add_packages("fpm", {public = true}) + + +-- Tests +for _, file in ipairs(os.files("test/**.cpp")) do + local name = path.basename(file) + target(name) + set_kind("binary") + + add_files(file) + + set_default(false) + + add_deps("Tower-Defense2") + + add_tests("compile_and_run") +end -- -- If you want to known more usage about xmake, please see https://xmake.io --