diff --git a/Src/Tests/Any.cpp b/Src/Tests/Any.cpp index df39808..a288f1c 100644 --- a/Src/Tests/Any.cpp +++ b/Src/Tests/Any.cpp @@ -2,7 +2,7 @@ // Created by Arthur on 08/10/2025. // -#include +#include #include #include @@ -19,55 +19,87 @@ namespace CCT_ANONYMOUS_NAMESPACE std::array buf{}; // ensure > small buffer }; - TEST(Any, SmallValue) + SCENARIO("Any") { - auto a = Any::Make(42); - ASSERT_TRUE(a.HasValue()); - ASSERT_TRUE(a.Is()); - ASSERT_FALSE(a.Is()); - EXPECT_EQ(a.As(), 42); - - a.Reset(); - EXPECT_FALSE(a.HasValue()); - } + GIVEN("An Any holding a small value (int 42)") + { + auto a = Any::Make(42); - TEST(Any, LargeValue) - { - Big b(99); - auto a = Any::Make(b); - ASSERT_TRUE(a.HasValue()); - ASSERT_TRUE(a.Is()); - auto v = a.As(); - EXPECT_EQ(v.x, 99); - } + THEN("It has a value, is of type int, and returns the correct value") + { + REQUIRE(a.HasValue()); + REQUIRE(a.Is()); + REQUIRE_FALSE(a.Is()); + CHECK(a.As() == 42); + } - TEST(Any, Pointer) - { - int v = 5; - auto a = Any::Make(&v); - ASSERT_TRUE(a.HasValue()); - ASSERT_TRUE(a.Is()); - int* p = a.As(); - *p = 11; - EXPECT_EQ(v, 11); - } + WHEN("Reset() is called") + { + a.Reset(); + THEN("It has no value") { CHECK_FALSE(a.HasValue()); } + } + } - TEST(Any, Reference) - { - int v = 7; - auto a = Any::Make(v); - ASSERT_TRUE(a.HasValue()); - ASSERT_TRUE(a.Is()); - int& r = a.As(); - r = 15; - EXPECT_EQ(v, 15); - } + GIVEN("An Any holding a large value (Big with x=99)") + { + Big b(99); + auto a = Any::Make(b); - TEST(Any, WrongCastThrows) - { - auto a = Any::Make(3); - ASSERT_TRUE(a.Is()); - EXPECT_THROW((void)a.As(), std::bad_cast); - } -} + THEN("It has a value, is of type Big, and x is 99") + { + REQUIRE(a.HasValue()); + REQUIRE(a.Is()); + CHECK(a.As().x == 99); + } + } + GIVEN("An Any holding a pointer to int") + { + int v = 5; + auto a = Any::Make(&v); + + THEN("It has a value and is of type int*") + { + REQUIRE(a.HasValue()); + REQUIRE(a.Is()); + } + + WHEN("The pointer is dereferenced and modified") + { + int* p = a.As(); + *p = 11; + THEN("The original value is modified") { CHECK(v == 11); } + } + } + + GIVEN("An Any holding a reference to int") + { + int v = 7; + auto a = Any::Make(v); + + THEN("It has a value and is of type int&") + { + REQUIRE(a.HasValue()); + REQUIRE(a.Is()); + } + + WHEN("The reference is modified") + { + int& r = a.As(); + r = 15; + THEN("The original value is modified") { CHECK(v == 15); } + } + } + + GIVEN("An Any holding an int") + { + auto a = Any::Make(3); + REQUIRE(a.Is()); + + WHEN("Cast to float") + { + THEN("It throws std::bad_cast") { CHECK_THROWS_AS((void)a.As(), std::bad_cast); } + } + } + } +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/Cast.cpp b/Src/Tests/Cast.cpp index ebaee3c..bc3645d 100644 --- a/Src/Tests/Cast.cpp +++ b/Src/Tests/Cast.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include @@ -30,29 +30,42 @@ namespace CCT_ANONYMOUS_NAMESPACE } }; - TEST(CastTest, ValidCast) { - Derived derived; - Base& base = derived; + SCENARIO("Cast") + { + GIVEN("A Derived object referenced as Base&") + { + Derived derived; + Base& base = derived; - Derived& casted = Cast(base); - - EXPECT_EQ(casted.Speak(), DerivedStr); - } - - TEST(CastTest, RvalueCast) { - Derived derived; - Base&& base = std::move(derived); + WHEN("Cast to Derived&") + { + Derived& casted = Cast(base); + THEN("The cast succeeds and Speak() returns DerivedStr") { CHECK(casted.Speak() == DerivedStr); } + } + } - Derived&& casted = Cast(std::move(base)); + GIVEN("A Derived object as Base&&") + { + Derived derived; + Base&& base = std::move(derived); - EXPECT_EQ(casted.Speak(), DerivedStr); - } + WHEN("Cast to Derived&&") + { + Derived&& casted = Cast(std::move(base)); + THEN("The cast succeeds") { CHECK(casted.Speak() == DerivedStr); } + } + } - TEST(CastTest, ConstReferenceCast) { - const Derived derived; - const Base& base = derived; - auto& casted = Cast(base); + GIVEN("A const Derived object as const Base&") + { + const Derived derived; + const Base& base = derived; - EXPECT_EQ(casted.Speak(), DerivedStr); + WHEN("Cast to const Derived&") + { + auto& casted = Cast(base); + THEN("The cast succeeds") { CHECK(casted.Speak() == DerivedStr); } + } + } } -} +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/DeferredExit.cpp b/Src/Tests/DeferredExit.cpp index d8044dc..4032098 100644 --- a/Src/Tests/DeferredExit.cpp +++ b/Src/Tests/DeferredExit.cpp @@ -2,7 +2,7 @@ // Created by arthur on 23/02/2023. // -#include +#include #include "Concerto/Core/DeferredExit/DeferredExit.hpp" #include "Concerto/Core/Types/Types.hpp" @@ -11,15 +11,18 @@ namespace CCT_ANONYMOUS_NAMESPACE { using namespace cct; - TEST(DeferredExit, General) + SCENARIO("DeferredExit") { - Int32 i = 0; + GIVEN("A DeferredExit with a callback that sets i = 42") { - DeferredExit _([&]() + Int32 i = 0; { - i = 42; - }); + DeferredExit _([&]() + { + i = 42; + }); + } + THEN("The callback is invoked when the DeferredExit goes out of scope") { REQUIRE(i == 42); } } - ASSERT_EQ(i, 42); } -}// namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/DynLib.cpp b/Src/Tests/DynLib.cpp index ce508b2..c79ee72 100644 --- a/Src/Tests/DynLib.cpp +++ b/Src/Tests/DynLib.cpp @@ -2,7 +2,7 @@ // Created by arthur on 02/06/2024. // -#include +#include #include #ifdef CCT_PLATFORM_POSIX @@ -13,51 +13,82 @@ namespace CCT_ANONYMOUS_NAMESPACE { - TEST(DynLib, Invoke) + SCENARIO("DynLib - Invoke") { - cct::DynLib lib; - bool res = lib.Load(PREFIX"concerto-core-dummy"); - EXPECT_TRUE(res); - - lib.Invoke("Dummy"); - const cct::FunctionRef func = lib.GetFunction("Dummy"); - ASSERT_EQ(func.operator bool(), true); - { - const int value = lib.Invoke("DummyInt"); - ASSERT_EQ(value, 42); - } + GIVEN("A DynLib loaded with the dummy library") { - const int* globalValue = lib.GetValue("GlobalInt"); - if (globalValue == nullptr) - FAIL(); - ASSERT_EQ(globalValue != nullptr, true); - ASSERT_EQ(*globalValue, 42); - } - { - const int* notExisting = lib.GetValue("NotExisting"); - ASSERT_EQ(notExisting, nullptr); - } - { - const int value = lib.Invoke("Increment", 5); - ASSERT_EQ(value, 5 + 1); - } - { - ASSERT_THROW(lib.Invoke("NotExisting", 5), std::runtime_error); - } + cct::DynLib lib; + REQUIRE(lib.Load(PREFIX "concerto-core-dummy")); + + WHEN("Dummy() is invoked") + { + lib.Invoke("Dummy"); + const cct::FunctionRef func = lib.GetFunction("Dummy"); + THEN("The function handle is valid") { CHECK(func.operator bool() == true); } + } - res = lib.Unload(); - ASSERT_EQ(res, true); + WHEN("DummyInt() is invoked") + { + THEN("It returns 42") { CHECK(lib.Invoke("DummyInt") == 42); } + } + + WHEN("GlobalInt is retrieved") + { + const int* globalValue = lib.GetValue("GlobalInt"); + THEN("The pointer is valid and the value is 42") + { + REQUIRE(globalValue != nullptr); + CHECK(*globalValue == 42); + } + } + + WHEN("A non-existing value is retrieved") + { + const int* notExisting = lib.GetValue("NotExisting"); + THEN("It returns nullptr") { CHECK(notExisting == nullptr); } + } + + WHEN("Increment(5) is invoked") + { + THEN("It returns 6") { CHECK(lib.Invoke("Increment", 5) == 6); } + } + + WHEN("A non-existing function is invoked") + { + THEN("It throws std::runtime_error") { CHECK_THROWS_AS(lib.Invoke("NotExisting", 5), std::runtime_error); } + } + + WHEN("The library is unloaded") + { + bool res = lib.Unload(); + THEN("Unload returns true") { CHECK(res == true); } + } + } } - TEST(DynLib, Loading) + SCENARIO("DynLib - Loading") { - cct::DynLib lib; - EXPECT_FALSE(lib.Unload()); - bool res = lib.Load(PREFIX"not-exist"); - EXPECT_FALSE(res); - EXPECT_EQ(lib.GetSymbol("foo"), nullptr); - - res = lib.Load(PREFIX"concerto-core-dummy"); - EXPECT_TRUE(res); + GIVEN("An empty DynLib") + { + cct::DynLib lib; + + THEN("Unload on an empty library returns false") { CHECK_FALSE(lib.Unload()); } + + WHEN("A non-existing library is loaded") + { + bool res = lib.Load(PREFIX "not-exist"); + THEN("Load returns false and GetSymbol returns nullptr") + { + CHECK_FALSE(res); + CHECK(lib.GetSymbol("foo") == nullptr); + } + } + + WHEN("The dummy library is loaded") + { + bool res = lib.Load(PREFIX "concerto-core-dummy"); + THEN("Load returns true") { CHECK(res); } + } + } } -} // namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/Enet.cpp b/Src/Tests/Enet.cpp index 371ca64..d076959 100644 --- a/Src/Tests/Enet.cpp +++ b/Src/Tests/Enet.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #include @@ -19,101 +19,130 @@ namespace CCT_ANONYMOUS_NAMESPACE using namespace cct; using namespace cct::net; - TEST(Enet, BasicConnection) + SCENARIO("Enet - BasicConnection") { - ENet::Initialize(); - bool running = true; - std::thread serverThread([&]() { - IpAddress listeningIp("0.0.0.0", 2121); - EnetServer server(listeningIp); - Int32 count = -1; - ENetEvent event; - while (running) + GIVEN("An ENet server listening on port 2121") + { + ENet::Initialize(); + bool running = true; + std::thread serverThread([&]() { + IpAddress listeningIp("0.0.0.0", 2121); + EnetServer server(listeningIp); + Int32 count = -1; + ENetEvent event; + while (running) + { + Int32 ret = server.PollEvent(&event, 100); + if (ret <= 0) + continue; + if (event.eventType == ENetEvent::Type::Connect) + count += 2; + else if (event.eventType == ENetEvent::Type::Disconnect) + count--; + } + REQUIRE(count == 0); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + WHEN("A client connects then disconnects") { - Int32 ret = server.PollEvent(&event, 100); - if (ret <= 0) - continue; - if (event.eventType == ENetEvent::Type::Connect) - count += 2; - else if (event.eventType == ENetEvent::Type::Disconnect) - count--; + EnetClient client; + IpAddress ip("127.0.0.1", 2121); + Int32 callbackCount = 0; + client.Connect(ip); + ENetEvent event; + Int32 ret = client.PollEvent(&event, 100); + REQUIRE(ret > 0); + REQUIRE(event.eventType == ENetEvent::Type::Connect); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ret = client.PollEvent(&event, 100); + client.Disconnect(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ret = client.PollEvent(&event, 100); + + THEN("The disconnect event is received and no unexpected callbacks fired") + { + REQUIRE(ret > 0); + REQUIRE(event.eventType == ENetEvent::Type::Disconnect); + CHECK(callbackCount == 0); + } } - ASSERT_EQ(count, 0); - }); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - EnetClient client; - IpAddress ip("127.0.0.1", 2121); - Int32 callbackCount = 0; - client.Connect(ip); - ENetEvent event; - Int32 ret = client.PollEvent(&event, 100); - ASSERT_TRUE(ret > 0 && event.eventType == ENetEvent::Type::Connect); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - ret = client.PollEvent(&event, 100); - client.Disconnect(); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - ret = client.PollEvent(&event, 100); - ASSERT_TRUE(ret > 0 && event.eventType == ENetEvent::Type::Disconnect); - ASSERT_EQ(callbackCount, 0); - running = false; - serverThread.join(); - ENet::Deinitialize(); + + running = false; + serverThread.join(); + ENet::Deinitialize(); + } } - TEST(Enet, SendingPacket) + SCENARIO("Enet - SendingPacket") { - ENet::Initialize(); - bool running = true; - constexpr UInt8 PacketType = 0xF; - std::thread serverThread([&]() { - IpAddress listeningIp("0.0.0.0", 2121); - EnetServer server(listeningIp); - ENetEvent event; - while (running) + GIVEN("An ENet server listening on port 2121") + { + ENet::Initialize(); + bool running = true; + constexpr UInt8 PacketType = 0xF; + std::thread serverThread([&]() { + IpAddress listeningIp("0.0.0.0", 2121); + EnetServer server(listeningIp); + ENetEvent event; + while (running) + { + Int32 ret = server.PollEvent(&event, 100); + if (ret <= 0) + continue; + if (event.eventType == ENetEvent::Type::Receive) + { + ENetPacket& packet = *event.packet; + UInt8 packetType = 0; + packet >> packetType; + REQUIRE(PacketType == packetType); + Int32 v42, v84; + packet >> v42 >> v84; + REQUIRE(v42 == 42); + REQUIRE(v84 == 84); + REQUIRE(server.SendPacket(packet, event.peer.get())); + } + } + }); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + WHEN("A client sends a packet with PacketType, 42, 84") { - Int32 ret = server.PollEvent(&event, 100); - if (ret <= 0) - continue; - if (event.eventType == ENetEvent::Type::Receive) + EnetClient client; + IpAddress ip("127.0.0.1", 2121); + Int32 callbackCount = 0; + client.Connect(ip); + ENetEvent event; + Int32 ret = client.PollEvent(&event, 100); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + ENetPacket packet; + packet << PacketType << Int32(42) << Int32(84); + REQUIRE(client.SendPacket(packet)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ret = client.PollEvent(&event, 100); + + THEN("The server echoes it back correctly") { - ENetPacket& packet = *event.packet; - UInt8 packetType = 0; - packet >> packetType; - ASSERT_TRUE(PacketType == packetType); - Int32 v42, v84; - packet >> v42 >> v84; - ASSERT_EQ(v42, 42); - ASSERT_EQ(v84, 84); - ASSERT_TRUE(server.SendPacket(packet, event.peer.get())); + if (event.eventType == ENetEvent::Type::Receive) + { + ENetPacket& newPacket = *event.packet; + CHECK(packet.GetSize() == newPacket.GetSize()); + CHECK(packet == newPacket); + } + else + { + FAIL("Expected Receive event"); + } } } - }); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - EnetClient client; - IpAddress ip("127.0.0.1", 2121); - Int32 callbackCount = 0; - client.Connect(ip); - ENetEvent event; - Int32 ret = client.PollEvent(&event, 100); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - ENetPacket packet; - packet << PacketType << Int32(42) << Int32(84); - ASSERT_TRUE(client.SendPacket(packet)); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - ret = client.PollEvent(&event, 100); - if (event.eventType == ENetEvent::Type::Receive) - { - ENetPacket& newPacket = *event.packet; - ASSERT_EQ(packet.GetSize(), newPacket.GetSize()); - ASSERT_EQ(packet, newPacket); + + running = false; + serverThread.join(); + ENet::Deinitialize(); } - else - ASSERT_FALSE(false); - running = false; - serverThread.join(); - ENet::Deinitialize(); } } // namespace CCT_ANONYMOUS_NAMESPACE -#endif // CCT_ENABLE_ENET \ No newline at end of file +#endif // CCT_ENABLE_ENET diff --git a/Src/Tests/EnumFlags.cpp b/Src/Tests/EnumFlags.cpp index f480ab5..c1c7030 100644 --- a/Src/Tests/EnumFlags.cpp +++ b/Src/Tests/EnumFlags.cpp @@ -2,7 +2,7 @@ // Created by Arthur on 06/10/2025. // -#include +#include #include @@ -19,51 +19,94 @@ namespace CCT_ANONYMOUS_NAMESPACE { using namespace cct; - TEST(EnumFlags, BasicOrAndContains) + SCENARIO("EnumFlags - basic OR, AND and Contains") { - auto f = MyFlags::A | MyFlags::B; - EXPECT_TRUE(f.Contains(MyFlags::A)); - EXPECT_TRUE(f.Contains(MyFlags::B)); - EXPECT_FALSE(f.Contains(MyFlags::C)); + GIVEN("EnumFlags with A and B set") + { + auto f = MyFlags::A | MyFlags::B; - auto g = f & MyFlags::A; - EXPECT_TRUE(g.Contains(MyFlags::A)); - EXPECT_FALSE(g.Contains(MyFlags::B)); - EXPECT_FALSE(g.Contains(MyFlags::C)); + THEN("Contains A and B, but not C") + { + CHECK(f.Contains(MyFlags::A)); + CHECK(f.Contains(MyFlags::B)); + CHECK_FALSE(f.Contains(MyFlags::C)); + } - EXPECT_TRUE(static_cast(f)); - EXPECT_FALSE(static_cast(EnumFlags{})); + THEN("Is truthy, while empty flags are falsy") + { + CHECK(static_cast(f)); + CHECK_FALSE(static_cast(EnumFlags{})); + } + + WHEN("ANDed with A") + { + auto g = f & MyFlags::A; + THEN("Result contains only A") + { + CHECK(g.Contains(MyFlags::A)); + CHECK_FALSE(g.Contains(MyFlags::B)); + CHECK_FALSE(g.Contains(MyFlags::C)); + } + } + } } - TEST(EnumFlags, XorToggleReset) + SCENARIO("EnumFlags - XOR, Toggle, Set, Reset") { - EnumFlags f = MyFlags::A; - f ^= MyFlags::B; - EXPECT_TRUE(f.Contains(MyFlags::B)); - f.Toggle(MyFlags::A); - EXPECT_FALSE(f.Contains(MyFlags::A)); - f.Set(MyFlags::C); - EXPECT_TRUE(f.Contains(MyFlags::C)); - f.Reset(MyFlags::B); - EXPECT_FALSE(f.Contains(MyFlags::B)); + GIVEN("EnumFlags with only A set") + { + EnumFlags f = MyFlags::A; + + WHEN("XOR with B, Toggle A, Set C, then Reset B") + { + f ^= MyFlags::B; + CHECK(f.Contains(MyFlags::B)); + f.Toggle(MyFlags::A); + CHECK_FALSE(f.Contains(MyFlags::A)); + f.Set(MyFlags::C); + CHECK(f.Contains(MyFlags::C)); + f.Reset(MyFlags::B); + THEN("B is no longer set") { CHECK_FALSE(f.Contains(MyFlags::B)); } + } + } } - TEST(EnumFlags, UnaryNotAndClear) + SCENARIO("EnumFlags - unary NOT and Clear") { - EnumFlags f = MyFlags::A | MyFlags::B; - auto notA = ~EnumFlags(MyFlags::A); - auto masked = notA & MyFlags::A; - EXPECT_TRUE(masked.None()); + GIVEN("EnumFlags with A and B set") + { + EnumFlags f = MyFlags::A | MyFlags::B; - f.Clear(); - EXPECT_TRUE(f.None()); - EXPECT_FALSE(f.Any()); + WHEN("NOT A is masked with A") + { + auto notA = ~EnumFlags(MyFlags::A); + auto masked = notA & MyFlags::A; + THEN("The masked result has no bits set") { CHECK(masked.None()); } + } + + WHEN("Clear() is called") + { + f.Clear(); + THEN("None() is true and Any() is false") + { + CHECK(f.None()); + CHECK_FALSE(f.Any()); + } + } + } } - TEST(EnumFlags, RawValue) + SCENARIO("EnumFlags - raw underlying value") { - EnumFlags f = MyFlags::A | MyFlags::C; - using U = EnumFlags::Underlying; - EXPECT_EQ(f.Value(), static_cast(static_cast(MyFlags::A) | static_cast(MyFlags::C))); + GIVEN("EnumFlags with A and C set") + { + EnumFlags f = MyFlags::A | MyFlags::C; + + THEN("Value() equals the bitwise OR of A and C as underlying type") + { + using U = EnumFlags::Underlying; + CHECK(f.Value() == static_cast(static_cast(MyFlags::A) | static_cast(MyFlags::C))); + } + } } -} +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/EulerAngles.cpp b/Src/Tests/EulerAngles.cpp index 3cd921c..208ac14 100644 --- a/Src/Tests/EulerAngles.cpp +++ b/Src/Tests/EulerAngles.cpp @@ -2,7 +2,8 @@ // Created by arthur on 14/03/2023. // -#include +#include +#include #include "Concerto/Core/Math/EulerAngles/EulerAngles.hpp" @@ -11,97 +12,142 @@ namespace CCT_ANONYMOUS_NAMESPACE using namespace cct; constexpr float near = 0.001f; - TEST(EulerAngles, Constructor) + SCENARIO("EulerAngles - construction") { - EulerAnglesf e(1.f, 2.f, 3.f); - ASSERT_EQ(1.f, e.Pitch()); - ASSERT_EQ(2.f, e.Yaw()); - ASSERT_EQ(3.f, e.Roll()); + GIVEN("EulerAngles(1, 2, 3)") + { + EulerAnglesf e(1.f, 2.f, 3.f); + THEN("Pitch, Yaw, Roll are correct") + { + REQUIRE(e.Pitch() == 1.f); + REQUIRE(e.Yaw() == 2.f); + REQUIRE(e.Roll() == 3.f); + } + } } - TEST(EulerAngles, operatorAdd) + SCENARIO("EulerAngles - arithmetic operators") { - EulerAnglesf e1(1.f, 2.f, 3.f); - EulerAnglesf e2(1.f, 2.f, 3.f); - EulerAnglesf e3 = e1 + e2; - ASSERT_EQ(2.f, e3.Pitch()); - ASSERT_EQ(4.f, e3.Yaw()); - ASSERT_EQ(6.f, e3.Roll()); - } + GIVEN("Two EulerAngles(1, 2, 3)") + { + EulerAnglesf e1(1.f, 2.f, 3.f); + EulerAnglesf e2(1.f, 2.f, 3.f); - TEST(EulerAngles, operatorSub) - { - EulerAnglesf e1(1.f, 2.f, 3.f); - EulerAnglesf e2(1.f, 2.f, 3.f); - EulerAnglesf e3 = e1 - e2; - ASSERT_EQ(0.f, e3.Pitch()); - ASSERT_EQ(0.f, e3.Yaw()); - ASSERT_EQ(0.f, e3.Roll()); - } + WHEN("Added") + { + EulerAnglesf e3 = e1 + e2; + THEN("Result is (2, 4, 6)") + { + REQUIRE(e3.Pitch() == 2.f); + REQUIRE(e3.Yaw() == 4.f); + REQUIRE(e3.Roll() == 6.f); + } + } - TEST(EulerAngles, operatorMul) - { - EulerAnglesf e1(1.f, 2.f, 3.f); - EulerAnglesf e2 = e1 * e1; - ASSERT_EQ(1.f, e2.Pitch()); - ASSERT_EQ(4.f, e2.Yaw()); - ASSERT_EQ(9.f, e2.Roll()); - } + WHEN("Subtracted") + { + EulerAnglesf e3 = e1 - e2; + THEN("Result is (0, 0, 0)") + { + REQUIRE(e3.Pitch() == 0.f); + REQUIRE(e3.Yaw() == 0.f); + REQUIRE(e3.Roll() == 0.f); + } + } - TEST(EulerAngles, operatorDiv) - { - EulerAnglesf e1(1.f, 2.f, 3.f); - EulerAnglesf e2 = e1 / e1; - ASSERT_EQ(1.f, e2.Pitch()); - ASSERT_EQ(1.f, e2.Yaw()); - ASSERT_EQ(1.f, e2.Roll()); - } + WHEN("Multiplied") + { + EulerAnglesf e2r = e1 * e1; + THEN("Result is (1, 4, 9)") + { + REQUIRE(e2r.Pitch() == 1.f); + REQUIRE(e2r.Yaw() == 4.f); + REQUIRE(e2r.Roll() == 9.f); + } + } - TEST(EulerAngles, operatorAddAssign) - { - EulerAnglesf e1(1.f, 2.f, 3.f); - EulerAnglesf e2(1.f, 2.f, 3.f); - e1 += e2; - ASSERT_EQ(2.f, e1.Pitch()); - ASSERT_EQ(4.f, e1.Yaw()); - ASSERT_EQ(6.f, e1.Roll()); + WHEN("Divided") + { + EulerAnglesf e2r = e1 / e1; + THEN("Result is (1, 1, 1)") + { + REQUIRE(e2r.Pitch() == 1.f); + REQUIRE(e2r.Yaw() == 1.f); + REQUIRE(e2r.Roll() == 1.f); + } + } + } } - TEST(EulerAngles, operatorSubAssign) + SCENARIO("EulerAngles - compound assignment operators") { - EulerAnglesf e1(1.f, 2.f, 3.f); - EulerAnglesf e2(1.f, 2.f, 3.f); - e1 -= e2; - ASSERT_EQ(0, e1.Pitch()); - ASSERT_EQ(0, e1.Yaw()); - ASSERT_EQ(0, e1.Roll()); - } + GIVEN("Two EulerAngles(1, 2, 3)") + { + EulerAnglesf e1(1.f, 2.f, 3.f); + EulerAnglesf e2(1.f, 2.f, 3.f); - TEST(EulerAngles, operatorMulAssign) - { - EulerAnglesf e1(1.f, 2.f, 3.f); - e1 *= e1; - ASSERT_EQ(1.f, e1.Pitch()); - ASSERT_EQ(4.f, e1.Yaw()); - ASSERT_EQ(9.f, e1.Roll()); - } + WHEN("+=") + { + e1 += e2; + THEN("e1 is (2, 4, 6)") + { + REQUIRE(e1.Pitch() == 2.f); + REQUIRE(e1.Yaw() == 4.f); + REQUIRE(e1.Roll() == 6.f); + } + } - TEST(EulerAngles, operatorDivAssign) - { - EulerAnglesf e1(1.f, 2.f, 3.f); - e1 /= e1; - ASSERT_EQ(1, e1.Pitch()); - ASSERT_EQ(1, e1.Yaw()); - ASSERT_EQ(1, e1.Roll()); + WHEN("-=") + { + e1 -= e2; + THEN("e1 is (0, 0, 0)") + { + REQUIRE(e1.Pitch() == 0); + REQUIRE(e1.Yaw() == 0); + REQUIRE(e1.Roll() == 0); + } + } + + WHEN("*=") + { + e1 *= e1; + THEN("e1 is (1, 4, 9)") + { + REQUIRE(e1.Pitch() == 1.f); + REQUIRE(e1.Yaw() == 4.f); + REQUIRE(e1.Roll() == 9.f); + } + } + + WHEN("/=") + { + e1 /= e1; + THEN("e1 is (1, 1, 1)") + { + REQUIRE(e1.Pitch() == 1); + REQUIRE(e1.Yaw() == 1); + REQUIRE(e1.Roll() == 1); + } + } + } } - TEST(EulerAngles, ToQuaternion) + SCENARIO("EulerAngles - ToQuaternion round-trip") { - EulerAnglesf e1(45.f, 25.f, 68.f); - Quaternionf q = e1.ToQuaternion(); - EulerAnglesf e2 = q.ToEulerAngles(); - EXPECT_NEAR(e1.Pitch(), e2.Pitch(), near); - EXPECT_NEAR(e1.Roll(), e2.Roll(), near); - EXPECT_NEAR(e1.Yaw(), e2.Yaw(), near); + GIVEN("EulerAngles(45, 25, 68)") + { + EulerAnglesf e1(45.f, 25.f, 68.f); + WHEN("Converted to Quaternion and back") + { + Quaternionf q = e1.ToQuaternion(); + EulerAnglesf e2 = q.ToEulerAngles(); + THEN("Pitch, Yaw, Roll are within tolerance") + { + CHECK_THAT(e2.Pitch(), Catch::Matchers::WithinAbs(e1.Pitch(), near)); + CHECK_THAT(e2.Roll(), Catch::Matchers::WithinAbs(e1.Roll(), near)); + CHECK_THAT(e2.Yaw(), Catch::Matchers::WithinAbs(e1.Yaw(), near)); + } + } + } } -}// namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/FunctionRef.cpp b/Src/Tests/FunctionRef.cpp index bd4a2cc..1eef439 100644 --- a/Src/Tests/FunctionRef.cpp +++ b/Src/Tests/FunctionRef.cpp @@ -2,47 +2,67 @@ // Created by arthur on 03/06/2024. // -#include +#include #include #include namespace CCT_ANONYMOUS_NAMESPACE { - void foo(int& value) { value = 42;} + void foo(int& value) { value = 42; } - TEST(FunctionRef, HasValue) + SCENARIO("FunctionRef") { - cct::FunctionRef func; - ASSERT_EQ(func.operator bool(), false); - auto lambda = []() {}; - func = lambda; - ASSERT_EQ(func.operator bool(), true); - } + GIVEN("An empty FunctionRef") + { + cct::FunctionRef func; - TEST(FunctionRef, Lambda) - { - int value = 0; - auto lambda = [&value]() {value = 42;}; - cct::FunctionRef func = lambda; - func(); - ASSERT_EQ(value, 42); - } + THEN("It evaluates to false") { REQUIRE(func.operator bool() == false); } - TEST(FunctionRef, Function) - { - cct::FunctionRef func = foo; - int value = 0; - func(value); - ASSERT_EQ(value, 42); - } + WHEN("A lambda is assigned") + { + auto lambda = []() {}; + func = lambda; + THEN("It evaluates to true") { REQUIRE(func.operator bool() == true); } + } + } - TEST(FunctionRef, Args) - { - auto lambda = [](std::size_t& value, const std::string& str) {value = str.size();}; - cct::FunctionRef func = lambda; - std::size_t value = 0; - std::string str = "Hello"; - func(value, str); - ASSERT_EQ(value, str.size()); + GIVEN("A FunctionRef holding a capturing lambda") + { + int value = 0; + auto lambda = [&value]() { value = 42; }; + cct::FunctionRef func = lambda; + + WHEN("The function is called") + { + func(); + THEN("The captured value is set to 42") { REQUIRE(value == 42); } + } + } + + GIVEN("A FunctionRef holding a free function") + { + cct::FunctionRef func = foo; + + WHEN("The function is called with int 0") + { + int value = 0; + func(value); + THEN("The value is set to 42") { REQUIRE(value == 42); } + } + } + + GIVEN("A FunctionRef holding a lambda with multiple arguments") + { + auto lambda = [](std::size_t& value, const std::string& str) { value = str.size(); }; + cct::FunctionRef func = lambda; + + WHEN("Called with a string of length 5") + { + std::size_t value = 0; + std::string str = "Hello"; + func(value, str); + THEN("value equals the string size") { REQUIRE(value == str.size()); } + } + } } -} // namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/IpAddress..cpp b/Src/Tests/IpAddress..cpp index 5925ffe..cc81172 100644 --- a/Src/Tests/IpAddress..cpp +++ b/Src/Tests/IpAddress..cpp @@ -2,7 +2,7 @@ // Created by arthur on 09/08/2023. // -#include +#include #include "Concerto/Core/Network/IpAddress/IpAddress.hpp" namespace CCT_ANONYMOUS_NAMESPACE @@ -12,52 +12,82 @@ namespace CCT_ANONYMOUS_NAMESPACE static constexpr UInt32 LocalHost = 2130706433; static constexpr IpAddress::IPv4 LocalHostArray = {127, 0, 0, 1}; - TEST(IpAddress, ParseIPV4) + SCENARIO("IpAddress - ParseIPV4") { - IpAddress ip(LocalHost, 2121); - EXPECT_EQ(ip.ToUInt32(), LocalHost); - EXPECT_EQ(ip.GetProtocol(), IpProtocol::Ipv4); - EXPECT_EQ(ip.GetIPv4(), LocalHostArray); - - ip = IpAddress("127.0.0.1", 2121); - EXPECT_EQ(ip.ToUInt32(), LocalHost); - EXPECT_EQ(ip.GetIPv4(), LocalHostArray); - EXPECT_EQ(ip.GetProtocol(), IpProtocol::Ipv4); - - ip = IpAddress(LocalHostArray, 2121); - EXPECT_EQ(ip.ToUInt32(), LocalHost); - EXPECT_EQ(ip.GetIPv4(), LocalHostArray); - EXPECT_EQ(ip.GetProtocol(), IpProtocol::Ipv4); - - ip = IpAddress("xxx.x.x.x", 2121); - EXPECT_EQ(ip.GetProtocol(), IpProtocol::Error); + GIVEN("LocalHost constructed from UInt32") + { + IpAddress ip(LocalHost, 2121); + THEN("ToUInt32, GetProtocol and GetIPv4 are correct") + { + CHECK(ip.ToUInt32() == LocalHost); + CHECK(ip.GetProtocol() == IpProtocol::Ipv4); + CHECK(ip.GetIPv4() == LocalHostArray); + } + } + + GIVEN("LocalHost constructed from string \"127.0.0.1\"") + { + IpAddress ip("127.0.0.1", 2121); + THEN("ToUInt32, GetIPv4 and GetProtocol are correct") + { + CHECK(ip.ToUInt32() == LocalHost); + CHECK(ip.GetIPv4() == LocalHostArray); + CHECK(ip.GetProtocol() == IpProtocol::Ipv4); + } + } + + GIVEN("LocalHost constructed from IPv4 array") + { + IpAddress ip(LocalHostArray, 2121); + THEN("ToUInt32, GetIPv4 and GetProtocol are correct") + { + CHECK(ip.ToUInt32() == LocalHost); + CHECK(ip.GetIPv4() == LocalHostArray); + CHECK(ip.GetProtocol() == IpProtocol::Ipv4); + } + } + + GIVEN("An invalid IP string \"xxx.x.x.x\"") + { + IpAddress ip("xxx.x.x.x", 2121); + THEN("Protocol is Error") { CHECK(ip.GetProtocol() == IpProtocol::Error); } + } } - TEST(IpAddress, IsIpV4) + SCENARIO("IpAddress - IsIpV4") { - EXPECT_TRUE(IpAddress::IsIpV4("127.0.0.1")); - - EXPECT_FALSE(IpAddress::IsIpV4("1277.0.0.1")); - EXPECT_FALSE(IpAddress::IsIpV4("127.1277.0.1")); - EXPECT_FALSE(IpAddress::IsIpV4("127.0.1277.1")); - EXPECT_FALSE(IpAddress::IsIpV4("127.0.127.1277")); - EXPECT_FALSE(IpAddress::IsIpV4("127..0.1")); - EXPECT_FALSE(IpAddress::IsIpV4("0:0:0:0:0:0:0:1")); + THEN("Valid IPv4 addresses are recognized") { CHECK(IpAddress::IsIpV4("127.0.0.1")); } + + THEN("Invalid IPv4 addresses are rejected") + { + CHECK_FALSE(IpAddress::IsIpV4("1277.0.0.1")); + CHECK_FALSE(IpAddress::IsIpV4("127.1277.0.1")); + CHECK_FALSE(IpAddress::IsIpV4("127.0.1277.1")); + CHECK_FALSE(IpAddress::IsIpV4("127.0.127.1277")); + CHECK_FALSE(IpAddress::IsIpV4("127..0.1")); + CHECK_FALSE(IpAddress::IsIpV4("0:0:0:0:0:0:0:1")); + } } - - TEST(IpAddress, IsIpV6) + + SCENARIO("IpAddress - IsIpV6") { - EXPECT_TRUE(IpAddress::IsIpV6("0:0:0:0:0:0:0:1")); - EXPECT_TRUE(IpAddress::IsIpV6("::1")); - EXPECT_TRUE(IpAddress::IsIpV6("2001:db8:3333:4444:5555:6666:7777:8888")); - EXPECT_TRUE(IpAddress::IsIpV6("2001:0db8:0001:0000:0000:0ab9:C0A8:010")); - EXPECT_TRUE(IpAddress::IsIpV6("fe80:0000:0000:0000:0000:0000:0000:1")); - EXPECT_TRUE(IpAddress::IsIpV6("2001:db8::1234:5678")); - EXPECT_TRUE(IpAddress::IsIpV6("::1234:5678")); - EXPECT_TRUE(IpAddress::IsIpV6("2001:db8::")); - - EXPECT_FALSE(IpAddress::IsIpV6("192.168.1.1")); - EXPECT_FALSE(IpAddress::IsIpV6("2001:0db8:85a3:0000:0000:8a2e:0370:7334:1234")); - EXPECT_FALSE(IpAddress::IsIpV6("fe80:::1")); + THEN("Valid IPv6 addresses are recognized") + { + CHECK(IpAddress::IsIpV6("0:0:0:0:0:0:0:1")); + CHECK(IpAddress::IsIpV6("::1")); + CHECK(IpAddress::IsIpV6("2001:db8:3333:4444:5555:6666:7777:8888")); + CHECK(IpAddress::IsIpV6("2001:0db8:0001:0000:0000:0ab9:C0A8:010")); + CHECK(IpAddress::IsIpV6("fe80:0000:0000:0000:0000:0000:0000:1")); + CHECK(IpAddress::IsIpV6("2001:db8::1234:5678")); + CHECK(IpAddress::IsIpV6("::1234:5678")); + CHECK(IpAddress::IsIpV6("2001:db8::")); + } + + THEN("Invalid IPv6 addresses are rejected") + { + CHECK_FALSE(IpAddress::IsIpV6("192.168.1.1")); + CHECK_FALSE(IpAddress::IsIpV6("2001:0db8:85a3:0000:0000:8a2e:0370:7334:1234")); + CHECK_FALSE(IpAddress::IsIpV6("fe80:::1")); + } } -}// namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/Logger.cpp b/Src/Tests/Logger.cpp index 2be9636..e7376f9 100644 --- a/Src/Tests/Logger.cpp +++ b/Src/Tests/Logger.cpp @@ -2,7 +2,7 @@ // Created by arthur on 02/03/2024. // -#include +#include #include @@ -33,13 +33,16 @@ namespace CCT_ANONYMOUS_NAMESPACE std::streambuf* _old; }; - //TEST(Debug, Logger) + //SCENARIO("Logger - Debug output") //{ - // ScoppedCoutRedirector redirector; - // Logger::Debug("Test string {}", 25); - - // auto currentLocation = std::source_location::current(); - // const std::string content = Terminal::Color::CYAN + std::string(currentLocation.function_name()) + ":" + std::to_string(currentLocation.line()) + " message: Test string 25" + Terminal::Color::DEFAULT; - // EXPECT_EQ(redirector.Str(), "Test string 25"); + // GIVEN("A redirected cout") + // { + // ScoppedCoutRedirector redirector; + // Logger::Debug("Test string {}", 25); + // + // auto currentLocation = std::source_location::current(); + // const std::string content = Terminal::Color::CYAN + std::string(currentLocation.function_name()) + ":" + std::to_string(currentLocation.line()) + " message: Test string 25" + Terminal::Color::DEFAULT; + // THEN("The output matches") { CHECK(redirector.Str() == "Test string 25"); } + // } //} -} \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/Matrix.cpp b/Src/Tests/Matrix.cpp index bb3a8de..dad4db8 100644 --- a/Src/Tests/Matrix.cpp +++ b/Src/Tests/Matrix.cpp @@ -2,7 +2,7 @@ // Created by arthur on 30/08/2022. // -#include +#include #include "Concerto/Core/Math/Matrix/Matrix.hpp" @@ -10,338 +10,250 @@ namespace CCT_ANONYMOUS_NAMESPACE { using namespace cct; - TEST(Matrix, Constructor) + SCENARIO("Matrix - construction and dimensions") { - Matrix m{}; - ASSERT_EQ(3, m.GetWidth()); - ASSERT_EQ(3, m.GetHeight()); + GIVEN("A default-constructed 3x3 float matrix") + { + Matrix m{}; + THEN("Width and Height are 3") + { + REQUIRE(m.GetWidth() == 3); + REQUIRE(m.GetHeight() == 3); + } + } } - TEST(Matrix, GetElement) + SCENARIO("Matrix - element access") { - Matrix m(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - ASSERT_EQ(1.f, m.GetElement(0, 0)); - ASSERT_EQ(2.f, m.GetElement(0, 1)); - ASSERT_EQ(3.f, m.GetElement(0, 2)); - ASSERT_EQ(4.f, m.GetElement(1, 0)); - ASSERT_EQ(5.f, m.GetElement(1, 1)); - ASSERT_EQ(6.f, m.GetElement(1, 2)); - ASSERT_EQ(7.f, m.GetElement(2, 0)); - ASSERT_EQ(8.f, m.GetElement(2, 1)); - ASSERT_EQ(9.f, m.GetElement(2, 2)); + GIVEN("A 3x3 matrix with values 1..9") + { + Matrix m(1.f, 2.f, 3.f, + 4.f, 5.f, 6.f, + 7.f, 8.f, 9.f); + THEN("GetElement returns the correct values per row/column") + { + REQUIRE(m.GetElement(0, 0) == 1.f); + REQUIRE(m.GetElement(0, 1) == 2.f); + REQUIRE(m.GetElement(0, 2) == 3.f); + REQUIRE(m.GetElement(1, 0) == 4.f); + REQUIRE(m.GetElement(1, 1) == 5.f); + REQUIRE(m.GetElement(1, 2) == 6.f); + REQUIRE(m.GetElement(2, 0) == 7.f); + REQUIRE(m.GetElement(2, 1) == 8.f); + REQUIRE(m.GetElement(2, 2) == 9.f); + } + } } - TEST(Matrix, operaorAdd) + SCENARIO("Matrix - matrix arithmetic operators") { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m3 = m1 + m2; - ASSERT_EQ(2.f, m3(0, 0)); - ASSERT_EQ(4.f, m3(0, 1)); - ASSERT_EQ(6.f, m3(0, 2)); - ASSERT_EQ(8.f, m3(1, 0)); - ASSERT_EQ(10.f, m3(1, 1)); - ASSERT_EQ(12.f, m3(1, 2)); - ASSERT_EQ(14.f, m3(2, 0)); - ASSERT_EQ(16.f, m3(2, 1)); - ASSERT_EQ(18.f, m3(2, 2)); - } + GIVEN("Two identical 3x3 matrices with values 1..9") + { + Matrix m1(1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f); + Matrix m2(1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f); - TEST(Matrix, operatorSub) - { + WHEN("Added (operator+)") + { + Matrix m3 = m1 + m2; + THEN("Each element is doubled") + { + REQUIRE(m3(0, 0) == 2.f); REQUIRE(m3(0, 1) == 4.f); REQUIRE(m3(0, 2) == 6.f); + REQUIRE(m3(1, 0) == 8.f); REQUIRE(m3(1, 1) == 10.f); REQUIRE(m3(1, 2) == 12.f); + REQUIRE(m3(2, 0) == 14.f); REQUIRE(m3(2, 1) == 16.f); REQUIRE(m3(2, 2) == 18.f); + } + } - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m3 = m1 - m2; - ASSERT_EQ(0.f, m3(0, 0)); - ASSERT_EQ(0.f, m3(0, 1)); - ASSERT_EQ(0.f, m3(0, 2)); - ASSERT_EQ(0.f, m3(1, 0)); - ASSERT_EQ(0.f, m3(1, 1)); - ASSERT_EQ(0.f, m3(1, 2)); - ASSERT_EQ(0.f, m3(2, 0)); - ASSERT_EQ(0.f, m3(2, 1)); - ASSERT_EQ(0.f, m3(2, 2)); - } + WHEN("Subtracted (operator-)") + { + Matrix m3 = m1 - m2; + THEN("All elements are zero") + { + for (int r = 0; r < 3; ++r) + for (int c = 0; c < 3; ++c) + REQUIRE(m3(r, c) == 0.f); + } + } - TEST(Matrix, operatorMult) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); + WHEN("Multiplied (operator*)") + { + Matrix m3 = m1 * m2; + THEN("Result is the matrix product") + { + REQUIRE(m3(0, 0) == 30.f); REQUIRE(m3(0, 1) == 36.f); REQUIRE(m3(0, 2) == 42.f); + REQUIRE(m3(1, 0) == 66.f); REQUIRE(m3(1, 1) == 81.f); REQUIRE(m3(1, 2) == 96.f); + REQUIRE(m3(2, 0) == 102.f); REQUIRE(m3(2, 1) == 126.f); REQUIRE(m3(2, 2) == 150.f); + } + } - Matrix m2(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m3 = m1 * m2; - ASSERT_EQ(30.f, m3(0, 0)); - ASSERT_EQ(36.f, m3(0, 1)); - ASSERT_EQ(42.f, m3(0, 2)); - ASSERT_EQ(66.f, m3(1, 0)); - ASSERT_EQ(81.f, m3(1, 1)); - ASSERT_EQ(96.f, m3(1, 2)); - ASSERT_EQ(102.f, m3(2, 0)); - ASSERT_EQ(126.f, m3(2, 1)); - ASSERT_EQ(150.f, m3(2, 2)); + WHEN("Divided (operator/)") + { + Matrix m3 = m1 / m2; + THEN("All elements are 1") + { + for (int r = 0; r < 3; ++r) + for (int c = 0; c < 3; ++c) + REQUIRE(m3(r, c) == 1.f); + } + } + } } - TEST(Matrix, operatorDiv) + SCENARIO("Matrix - scalar arithmetic operators") { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m3 = m1 / m2; - ASSERT_EQ(1.f, m3(0, 0)); - ASSERT_EQ(1.f, m3(0, 1)); - ASSERT_EQ(1.f, m3(0, 2)); - ASSERT_EQ(1.f, m3(1, 0)); - ASSERT_EQ(1.f, m3(1, 1)); - ASSERT_EQ(1.f, m3(1, 2)); - ASSERT_EQ(1.f, m3(2, 0)); - ASSERT_EQ(1.f, m3(2, 1)); - ASSERT_EQ(1.f, m3(2, 2)); - } + GIVEN("A 3x3 matrix with values 1..9") + { + Matrix m1(1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f); - TEST(Matrix, operatorAddScalar) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2 = m1 + 1.f; - ASSERT_EQ(2.f, m2(0, 0)); - ASSERT_EQ(3.f, m2(0, 1)); - ASSERT_EQ(4.f, m2(0, 2)); - ASSERT_EQ(5.f, m2(1, 0)); - ASSERT_EQ(6.f, m2(1, 1)); - ASSERT_EQ(7.f, m2(1, 2)); - ASSERT_EQ(8.f, m2(2, 0)); - ASSERT_EQ(9.f, m2(2, 1)); - ASSERT_EQ(10.f, m2(2, 2)); - } + WHEN("Scalar 1 added") + { + Matrix m2 = m1 + 1.f; + THEN("Each element is incremented by 1") + { + REQUIRE(m2(0, 0) == 2.f); REQUIRE(m2(0, 1) == 3.f); REQUIRE(m2(0, 2) == 4.f); + REQUIRE(m2(1, 0) == 5.f); REQUIRE(m2(1, 1) == 6.f); REQUIRE(m2(1, 2) == 7.f); + REQUIRE(m2(2, 0) == 8.f); REQUIRE(m2(2, 1) == 9.f); REQUIRE(m2(2, 2) == 10.f); + } + } - TEST(Matrix, operatorSubScalar) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2 = m1 - 1.f; - ASSERT_EQ(0.f, m2(0, 0)); - ASSERT_EQ(1.f, m2(0, 1)); - ASSERT_EQ(2.f, m2(0, 2)); - ASSERT_EQ(3.f, m2(1, 0)); - ASSERT_EQ(4.f, m2(1, 1)); - ASSERT_EQ(5.f, m2(1, 2)); - ASSERT_EQ(6.f, m2(2, 0)); - ASSERT_EQ(7.f, m2(2, 1)); - ASSERT_EQ(8.f, m2(2, 2)); - } + WHEN("Scalar 1 subtracted") + { + Matrix m2 = m1 - 1.f; + THEN("Each element is decremented by 1") + { + REQUIRE(m2(0, 0) == 0.f); REQUIRE(m2(0, 1) == 1.f); REQUIRE(m2(0, 2) == 2.f); + REQUIRE(m2(1, 0) == 3.f); REQUIRE(m2(1, 1) == 4.f); REQUIRE(m2(1, 2) == 5.f); + REQUIRE(m2(2, 0) == 6.f); REQUIRE(m2(2, 1) == 7.f); REQUIRE(m2(2, 2) == 8.f); + } + } - TEST(Matrix, operatorMultScalar) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2 = m1 * 2.f; - ASSERT_EQ(2.f, m2(0, 0)); - ASSERT_EQ(4.f, m2(0, 1)); - ASSERT_EQ(6.f, m2(0, 2)); - ASSERT_EQ(8.f, m2(1, 0)); - ASSERT_EQ(10.f, m2(1, 1)); - ASSERT_EQ(12.f, m2(1, 2)); - ASSERT_EQ(14.f, m2(2, 0)); - ASSERT_EQ(16.f, m2(2, 1)); - ASSERT_EQ(18.f, m2(2, 2)); - } + WHEN("Multiplied by scalar 2") + { + Matrix m2 = m1 * 2.f; + THEN("Each element is doubled") + { + REQUIRE(m2(0, 0) == 2.f); REQUIRE(m2(0, 1) == 4.f); REQUIRE(m2(0, 2) == 6.f); + REQUIRE(m2(1, 0) == 8.f); REQUIRE(m2(1, 1) == 10.f); REQUIRE(m2(1, 2) == 12.f); + REQUIRE(m2(2, 0) == 14.f); REQUIRE(m2(2, 1) == 16.f); REQUIRE(m2(2, 2) == 18.f); + } + } - TEST(Matrix, operatorDivScalar) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2 = m1 / 2.f; - ASSERT_EQ(0.5f, m2(0, 0)); - ASSERT_EQ(1.f, m2(0, 1)); - ASSERT_EQ(1.5f, m2(0, 2)); - ASSERT_EQ(2.f, m2(1, 0)); - ASSERT_EQ(2.5f, m2(1, 1)); - ASSERT_EQ(3.f, m2(1, 2)); - ASSERT_EQ(3.5f, m2(2, 0)); - ASSERT_EQ(4.f, m2(2, 1)); - ASSERT_EQ(4.5f, m2(2, 2)); + WHEN("Divided by scalar 2") + { + Matrix m2 = m1 / 2.f; + THEN("Each element is halved") + { + REQUIRE(m2(0, 0) == 0.5f); REQUIRE(m2(0, 1) == 1.f); REQUIRE(m2(0, 2) == 1.5f); + REQUIRE(m2(1, 0) == 2.f); REQUIRE(m2(1, 1) == 2.5f); REQUIRE(m2(1, 2) == 3.f); + REQUIRE(m2(2, 0) == 3.5f); REQUIRE(m2(2, 1) == 4.f); REQUIRE(m2(2, 2) == 4.5f); + } + } + } } - TEST(Matrix, operatorAddAssign) + SCENARIO("Matrix - compound assignment operators (matrix)") { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - m1 += m2; - ASSERT_EQ(2.f, m1(0, 0)); - ASSERT_EQ(4.f, m1(0, 1)); - ASSERT_EQ(6.f, m1(0, 2)); - ASSERT_EQ(8.f, m1(1, 0)); - ASSERT_EQ(10.f, m1(1, 1)); - ASSERT_EQ(12.f, m1(1, 2)); - ASSERT_EQ(14.f, m1(2, 0)); - ASSERT_EQ(16.f, m1(2, 1)); - ASSERT_EQ(18.f, m1(2, 2)); - } + GIVEN("Two identical 3x3 matrices with values 1..9") + { + Matrix m1(1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f); + Matrix m2(1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f); - TEST(Matrix, operatorSubAssign) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - m1 -= m2; - ASSERT_EQ(0.f, m1(0, 0)); - ASSERT_EQ(0.f, m1(0, 1)); - ASSERT_EQ(0.f, m1(0, 2)); - ASSERT_EQ(0.f, m1(1, 0)); - ASSERT_EQ(0.f, m1(1, 1)); - ASSERT_EQ(0.f, m1(1, 2)); - ASSERT_EQ(0.f, m1(2, 0)); - ASSERT_EQ(0.f, m1(2, 1)); - ASSERT_EQ(0.f, m1(2, 2)); - } + WHEN("+=") + { + m1 += m2; + THEN("m1 elements are doubled") + { + REQUIRE(m1(0, 0) == 2.f); REQUIRE(m1(0, 1) == 4.f); REQUIRE(m1(0, 2) == 6.f); + REQUIRE(m1(1, 0) == 8.f); REQUIRE(m1(1, 1) == 10.f); REQUIRE(m1(1, 2) == 12.f); + REQUIRE(m1(2, 0) == 14.f); REQUIRE(m1(2, 1) == 16.f); REQUIRE(m1(2, 2) == 18.f); + } + } - TEST(Matrix, operatorMultAssign) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - m1 *= m2; - ASSERT_EQ(1.f, m1(0, 0)); - ASSERT_EQ(4.f, m1(0, 1)); - ASSERT_EQ(9.f, m1(0, 2)); - ASSERT_EQ(16.f, m1(1, 0)); - ASSERT_EQ(25.f, m1(1, 1)); - ASSERT_EQ(36.f, m1(1, 2)); - ASSERT_EQ(49.f, m1(2, 0)); - ASSERT_EQ(64.f, m1(2, 1)); - ASSERT_EQ(81.f, m1(2, 2)); - } + WHEN("-=") + { + m1 -= m2; + THEN("m1 elements are all zero") + { + for (int r = 0; r < 3; ++r) + for (int c = 0; c < 3; ++c) + REQUIRE(m1(r, c) == 0.f); + } + } - TEST(Matrix, operatorDivAssign) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - Matrix m2(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - m1 /= m2; - ASSERT_EQ(1.f, m1(0, 0)); - ASSERT_EQ(1.f, m1(0, 1)); - ASSERT_EQ(1.f, m1(0, 2)); - ASSERT_EQ(1.f, m1(1, 0)); - ASSERT_EQ(1.f, m1(1, 1)); - ASSERT_EQ(1.f, m1(1, 2)); - ASSERT_EQ(1.f, m1(2, 0)); - ASSERT_EQ(1.f, m1(2, 1)); - ASSERT_EQ(1.f, m1(2, 2)); - } + WHEN("*=") + { + m1 *= m2; + THEN("m1 elements are squared") + { + REQUIRE(m1(0, 0) == 1.f); REQUIRE(m1(0, 1) == 4.f); REQUIRE(m1(0, 2) == 9.f); + REQUIRE(m1(1, 0) == 16.f); REQUIRE(m1(1, 1) == 25.f); REQUIRE(m1(1, 2) == 36.f); + REQUIRE(m1(2, 0) == 49.f); REQUIRE(m1(2, 1) == 64.f); REQUIRE(m1(2, 2) == 81.f); + } + } - TEST(Matrix, operatorAddAssignScalar) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - m1 += 1.f; - ASSERT_EQ(2.f, m1(0, 0)); - ASSERT_EQ(3.f, m1(0, 1)); - ASSERT_EQ(4.f, m1(0, 2)); - ASSERT_EQ(5.f, m1(1, 0)); - ASSERT_EQ(6.f, m1(1, 1)); - ASSERT_EQ(7.f, m1(1, 2)); - ASSERT_EQ(8.f, m1(2, 0)); - ASSERT_EQ(9.f, m1(2, 1)); - ASSERT_EQ(10.f, m1(2, 2)); + WHEN("/=") + { + m1 /= m2; + THEN("m1 elements are all 1") + { + for (int r = 0; r < 3; ++r) + for (int c = 0; c < 3; ++c) + REQUIRE(m1(r, c) == 1.f); + } + } + } } - TEST(Matrix, operatorSubAssignScalar) + SCENARIO("Matrix - compound assignment operators (scalar)") { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - m1 -= 1.f; - ASSERT_EQ(0.f, m1(0, 0)); - ASSERT_EQ(1.f, m1(0, 1)); - ASSERT_EQ(2.f, m1(0, 2)); - ASSERT_EQ(3.f, m1(1, 0)); - ASSERT_EQ(4.f, m1(1, 1)); - ASSERT_EQ(5.f, m1(1, 2)); - ASSERT_EQ(6.f, m1(2, 0)); - ASSERT_EQ(7.f, m1(2, 1)); - ASSERT_EQ(8.f, m1(2, 2)); - } + GIVEN("A 3x3 matrix with values 1..9") + { + Matrix m1(1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f); - TEST(Matrix, operatorMultAssignScalar) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - m1 *= 2.f; - ASSERT_EQ(2.f, m1(0, 0)); - ASSERT_EQ(4.f, m1(0, 1)); - ASSERT_EQ(6.f, m1(0, 2)); - ASSERT_EQ(8.f, m1(1, 0)); - ASSERT_EQ(10.f, m1(1, 1)); - ASSERT_EQ(12.f, m1(1, 2)); - ASSERT_EQ(14.f, m1(2, 0)); - ASSERT_EQ(16.f, m1(2, 1)); - ASSERT_EQ(18.f, m1(2, 2)); - } + WHEN("+= scalar 1") + { + m1 += 1.f; + THEN("Each element is incremented by 1") + { + REQUIRE(m1(0, 0) == 2.f); REQUIRE(m1(0, 1) == 3.f); REQUIRE(m1(0, 2) == 4.f); + REQUIRE(m1(1, 0) == 5.f); REQUIRE(m1(1, 1) == 6.f); REQUIRE(m1(1, 2) == 7.f); + REQUIRE(m1(2, 0) == 8.f); REQUIRE(m1(2, 1) == 9.f); REQUIRE(m1(2, 2) == 10.f); + } + } - TEST(Matrix, operatorDivAssignScalar) - { - Matrix m1(1.f, 2.f, 3.f, - 4.f, 5.f, 6.f, - 7.f, 8.f, 9.f); - m1 /= 2.f; - ASSERT_EQ(0.5f, m1(0, 0)); - ASSERT_EQ(1.f, m1(0, 1)); - ASSERT_EQ(1.5f, m1(0, 2)); - ASSERT_EQ(2.f, m1(1, 0)); - ASSERT_EQ(2.5f, m1(1, 1)); - ASSERT_EQ(3.f, m1(1, 2)); - ASSERT_EQ(3.5f, m1(2, 0)); - ASSERT_EQ(4.f, m1(2, 1)); - ASSERT_EQ(4.5f, m1(2, 2)); - } + WHEN("-= scalar 1") + { + m1 -= 1.f; + THEN("Each element is decremented by 1") + { + REQUIRE(m1(0, 0) == 0.f); REQUIRE(m1(0, 1) == 1.f); REQUIRE(m1(0, 2) == 2.f); + REQUIRE(m1(1, 0) == 3.f); REQUIRE(m1(1, 1) == 4.f); REQUIRE(m1(1, 2) == 5.f); + REQUIRE(m1(2, 0) == 6.f); REQUIRE(m1(2, 1) == 7.f); REQUIRE(m1(2, 2) == 8.f); + } + } - //TEST(Matrix, Translate) - //{ - // Matrix4f matrix = Matrix4f::Identity(); - // matrix.Translate(Vector3f(1.0f, 2.0f, 3.0f)); + WHEN("*= scalar 2") + { + m1 *= 2.f; + THEN("Each element is doubled") + { + REQUIRE(m1(0, 0) == 2.f); REQUIRE(m1(0, 1) == 4.f); REQUIRE(m1(0, 2) == 6.f); + REQUIRE(m1(1, 0) == 8.f); REQUIRE(m1(1, 1) == 10.f); REQUIRE(m1(1, 2) == 12.f); + REQUIRE(m1(2, 0) == 14.f); REQUIRE(m1(2, 1) == 16.f); REQUIRE(m1(2, 2) == 18.f); + } + } - // constexpr Matrix4f expected( - // 1.0f, 0.0f, 0.0f, 1.0f, - // 0.0f, 1.0f, 0.0f, 2.0f, - // 0.0f, 0.0f, 1.0f, 3.0f, - // 0.0f, 0.0f, 0.0f, 1.0f); + WHEN("/= scalar 2") + { + m1 /= 2.f; + THEN("Each element is halved") + { + REQUIRE(m1(0, 0) == 0.5f); REQUIRE(m1(0, 1) == 1.f); REQUIRE(m1(0, 2) == 1.5f); + REQUIRE(m1(1, 0) == 2.f); REQUIRE(m1(1, 1) == 2.5f); REQUIRE(m1(1, 2) == 3.f); + REQUIRE(m1(2, 0) == 3.5f); REQUIRE(m1(2, 1) == 4.f); REQUIRE(m1(2, 2) == 4.5f); + } + } + } + } - // ASSERT_EQ(matrix, expected); - //} -}// namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file + //SCENARIO("Matrix - Translate") { ... } // commented out in original +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/Packet.cpp b/Src/Tests/Packet.cpp index a79cdb3..3b05d84 100644 --- a/Src/Tests/Packet.cpp +++ b/Src/Tests/Packet.cpp @@ -2,7 +2,7 @@ // Created by arthur on 30/05/2023. // -#include +#include #include #include "Concerto/Core/Network/Packet/Packet.hpp" @@ -20,64 +20,134 @@ namespace CCT_ANONYMOUS_NAMESPACE UInt32 Size; }; - TEST(Packet, Constructor) + SCENARIO("Packet - construction") { - Packet packet(PacketType, nullptr, 0); - EXPECT_EQ(packet.GetPacketType(), PacketType); - EXPECT_EQ(packet.GetSize(), Packet::HeaderSize); - EXPECT_EQ(packet.GetDataSize(), 0); - EXPECT_EQ(packet.Capacity(), Packet::HeaderSize); - - Packet packet2(PacketType, 5); - EXPECT_EQ(packet2.GetPacketType(), PacketType); - EXPECT_EQ(packet2.GetSize(), Packet::HeaderSize); - EXPECT_EQ(packet2.GetDataSize(), 0); - EXPECT_EQ(packet2.Capacity(), Packet::HeaderSize + 5); - - Packet packet3; - EXPECT_EQ(packet3.GetPacketType(), 0); - EXPECT_EQ(packet3.GetSize(), 0); - EXPECT_EQ(packet3.GetSize(), 0); - EXPECT_EQ(packet3.Capacity(), Packet::HeaderSize); + GIVEN("A Packet constructed from PacketType and no data") + { + Packet packet(PacketType, nullptr, 0); + THEN("Sizes and type are correct") + { + CHECK(packet.GetPacketType() == PacketType); + CHECK(packet.GetSize() == Packet::HeaderSize); + CHECK(packet.GetDataSize() == 0); + CHECK(packet.Capacity() == Packet::HeaderSize); + } + } + + GIVEN("A Packet with reserved capacity of 5") + { + Packet packet2(PacketType, 5); + THEN("Capacity includes the reserved space") + { + CHECK(packet2.GetPacketType() == PacketType); + CHECK(packet2.GetSize() == Packet::HeaderSize); + CHECK(packet2.GetDataSize() == 0); + CHECK(packet2.Capacity() == Packet::HeaderSize + 5); + } + } + + GIVEN("A default-constructed Packet") + { + Packet packet3; + THEN("Type is 0 and sizes are zero/HeaderSize") + { + CHECK(packet3.GetPacketType() == 0); + CHECK(packet3.GetSize() == 0); + CHECK(packet3.GetSize() == 0); + CHECK(packet3.Capacity() == Packet::HeaderSize); + } + } } - TEST(Packet, DecodeHeader) + SCENARIO("Packet - DecodeHeader") { - PacketHeader header = {}; - Packet packet(PacketType, nullptr, 0); - EXPECT_TRUE(packet.EncodeHeader()); - EXPECT_TRUE(packet.DecodeHeader(&header.PacketType, &header.Size)); - EXPECT_EQ(header.PacketType, ByteSwappedPacketType); - EXPECT_EQ(header.Size, 0); - - Packet packet2(PacketType, 5); - EXPECT_TRUE(packet2.EncodeHeader()); - EXPECT_TRUE(packet2.DecodeHeader(&header.PacketType, &header.Size)); - EXPECT_EQ(header.PacketType, ByteSwappedPacketType); - EXPECT_EQ(header.Size, 0); - - Packet packet3; - EXPECT_FALSE(packet3.EncodeHeader()); - EXPECT_FALSE(packet3.DecodeHeader(&header.PacketType, &header.Size)); - EXPECT_EQ(header.PacketType, ByteSwappedPacketType); - EXPECT_EQ(header.Size, 0); - - const char* data = "Hello"; - Packet packet4(PacketType, data, 5); - EXPECT_TRUE(packet4.EncodeHeader()); - EXPECT_TRUE(packet4.DecodeHeader(&header.PacketType, &header.Size)); - EXPECT_EQ(header.PacketType, ByteSwappedPacketType); - EXPECT_EQ(header.Size, 5); - - Packet packet5(PacketType); - packet5 << UInt32(12) << UInt32(13); - EXPECT_TRUE(packet5.EncodeHeader()); - EXPECT_TRUE(packet5.DecodeHeader(&header.PacketType, &header.Size)); - EXPECT_EQ(header.PacketType, ByteSwappedPacketType); - EXPECT_EQ(header.Size, 2 * sizeof(Int32)); - UInt32 a, b; - packet5 >> a >> b; - EXPECT_EQ(a, 12); - EXPECT_EQ(b, 13); + GIVEN("A Packet from PacketType with no data") + { + PacketHeader header = {}; + Packet packet(PacketType, nullptr, 0); + + WHEN("Header is encoded then decoded") + { + CHECK(packet.EncodeHeader()); + CHECK(packet.DecodeHeader(&header.PacketType, &header.Size)); + THEN("Header fields match") + { + CHECK(header.PacketType == ByteSwappedPacketType); + CHECK(header.Size == 0); + } + } + } + + GIVEN("A Packet with reserved capacity of 5") + { + PacketHeader header = {}; + Packet packet2(PacketType, 5); + + WHEN("Header is encoded then decoded") + { + CHECK(packet2.EncodeHeader()); + CHECK(packet2.DecodeHeader(&header.PacketType, &header.Size)); + THEN("Header fields match") + { + CHECK(header.PacketType == ByteSwappedPacketType); + CHECK(header.Size == 0); + } + } + } + + GIVEN("A default-constructed (invalid) Packet") + { + PacketHeader header = {}; + Packet packet3; + + WHEN("EncodeHeader/DecodeHeader are called") + { + THEN("Both return false") + { + CHECK_FALSE(packet3.EncodeHeader()); + CHECK_FALSE(packet3.DecodeHeader(&header.PacketType, &header.Size)); + } + } + } + + GIVEN("A Packet with raw data \"Hello\"") + { + PacketHeader header = {}; + const char* data = "Hello"; + Packet packet4(PacketType, data, 5); + + WHEN("Header is encoded then decoded") + { + CHECK(packet4.EncodeHeader()); + CHECK(packet4.DecodeHeader(&header.PacketType, &header.Size)); + THEN("Size reflects the 5 data bytes") + { + CHECK(header.PacketType == ByteSwappedPacketType); + CHECK(header.Size == 5); + } + } + } + + GIVEN("A Packet with two UInt32 values (12 and 13) written via <<") + { + PacketHeader header = {}; + Packet packet5(PacketType); + packet5 << UInt32(12) << UInt32(13); + + WHEN("Header is encoded then decoded, and values are read back") + { + CHECK(packet5.EncodeHeader()); + CHECK(packet5.DecodeHeader(&header.PacketType, &header.Size)); + THEN("Size and read-back values are correct") + { + CHECK(header.PacketType == ByteSwappedPacketType); + CHECK(header.Size == 2 * sizeof(Int32)); + UInt32 a, b; + packet5 >> a >> b; + CHECK(a == 12); + CHECK(b == 13); + } + } + } } -} // namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/Quaternion.cpp b/Src/Tests/Quaternion.cpp index 5e4609e..1792cf1 100644 --- a/Src/Tests/Quaternion.cpp +++ b/Src/Tests/Quaternion.cpp @@ -2,7 +2,8 @@ // Created by arthur on 04/09/2022. // -#include +#include +#include #include "Concerto/Core/Math/Quaternion/Quaternion.hpp" @@ -11,217 +12,326 @@ namespace CCT_ANONYMOUS_NAMESPACE // results are from https://www.redcrab-software.com/en/Calculator/Quaternion-Calculator constexpr float near = 1.2e-05f; using namespace cct; - using namespace cct; - TEST(Quaternion, Quaternion) + SCENARIO("Quaternion - construction") { - Quaternionf quaternion(1, 2, 3, 4); - EXPECT_EQ(quaternion.X(), 1); - EXPECT_EQ(quaternion.Y(), 2); - EXPECT_EQ(quaternion.Z(), 3); - EXPECT_EQ(quaternion.W(), 4); - } + GIVEN("A Quaternion(1, 2, 3, 4)") + { + Quaternionf quaternion(1, 2, 3, 4); + THEN("Components are correct") + { + CHECK(quaternion.X() == 1); + CHECK(quaternion.Y() == 2); + CHECK(quaternion.Z() == 3); + CHECK(quaternion.W() == 4); + } + } - TEST(Quaternion, QuaternionFromEuler) - { - Quaternionf quaternion(EulerAnglesf(45, 25, 68)); - EXPECT_NEAR(quaternion.X(), 0.421557218f, near); - EXPECT_NEAR(quaternion.Y(), 0.374699116f, near); - EXPECT_NEAR(quaternion.Z(), 0.435713291, near); - EXPECT_NEAR(quaternion.W(), 0.701458514f, near); + GIVEN("A Quaternion from EulerAngles(45, 25, 68)") + { + Quaternionf quaternion(EulerAnglesf(45, 25, 68)); + THEN("Components match expected values within tolerance") + { + CHECK_THAT(quaternion.X(), Catch::Matchers::WithinAbs(0.421557218f, near)); + CHECK_THAT(quaternion.Y(), Catch::Matchers::WithinAbs(0.374699116f, near)); + CHECK_THAT(quaternion.Z(), Catch::Matchers::WithinAbs(0.435713291f, near)); + CHECK_THAT(quaternion.W(), Catch::Matchers::WithinAbs(0.701458514f, near)); + } + } } - TEST(Quaternion, operatorAdd) + SCENARIO("Quaternion - arithmetic operators") { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2(1, 2, 3, 4); - Quaternionf quaternion3 = quaternion1 + quaternion2; - EXPECT_EQ(quaternion3.X(), 2); - EXPECT_EQ(quaternion3.Y(), 4); - EXPECT_EQ(quaternion3.Z(), 6); - EXPECT_EQ(quaternion3.W(), 8); - } + GIVEN("Two Quaternions(1, 2, 3, 4)") + { + Quaternionf q1(1, 2, 3, 4); + Quaternionf q2(1, 2, 3, 4); - TEST(Quaternion, operatorSub) - { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2(1, 2, 3, 4); - Quaternionf quaternion3 = quaternion1 - quaternion2; - EXPECT_EQ(quaternion3.X(), 0); - EXPECT_EQ(quaternion3.Y(), 0); - EXPECT_EQ(quaternion3.Z(), 0); - EXPECT_EQ(quaternion3.W(), 0); - } + WHEN("Added") + { + Quaternionf q3 = q1 + q2; + THEN("Components are doubled") + { + CHECK(q3.X() == 2); + CHECK(q3.Y() == 4); + CHECK(q3.Z() == 6); + CHECK(q3.W() == 8); + } + } - TEST(Quaternion, operatorMul) - { - EulerAnglesf eulerAngles(10, 20, 30); - Quaternionf quaternion1(eulerAngles); - Quaternionf quaternion2(eulerAngles); - Quaternionf quaternion3 = quaternion1 * quaternion2; - EXPECT_NEAR(quaternion3.X(), 0.240985841f, near); - EXPECT_NEAR(quaternion3.Y(), 0.357305080f, near); - EXPECT_NEAR(quaternion3.Z(), 0.451658517f, near); - EXPECT_NEAR(quaternion3.W(), 0.781193554f, near); - } + WHEN("Subtracted") + { + Quaternionf q3 = q1 - q2; + THEN("Components are zero") + { + CHECK(q3.X() == 0); + CHECK(q3.Y() == 0); + CHECK(q3.Z() == 0); + CHECK(q3.W() == 0); + } + } + } - TEST(Quaternion, operatorDiv) - { - Quaternionf quaternion1(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); - Quaternionf quaternion2(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); - Quaternionf quaternion3 = quaternion1 / quaternion2; - EXPECT_NEAR(quaternion3.X(), 0.f, near); - EXPECT_NEAR(quaternion3.Y(), 0.f, near); - EXPECT_NEAR(quaternion3.Z(), 0.f, near); - EXPECT_NEAR(quaternion3.W(), 1.f, near); - } + GIVEN("Two Quaternions from EulerAngles(10, 20, 30)") + { + EulerAnglesf eulerAngles(10, 20, 30); + Quaternionf q1(eulerAngles); + Quaternionf q2(eulerAngles); - TEST(Quaternion, operatorAddEqual) - { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2(1, 2, 3, 4); - quaternion1 += quaternion2; - EXPECT_EQ(quaternion1.X(), 2); - EXPECT_EQ(quaternion1.Y(), 4); - EXPECT_EQ(quaternion1.Z(), 6); - EXPECT_EQ(quaternion1.W(), 8); - } + WHEN("Multiplied") + { + Quaternionf q3 = q1 * q2; + THEN("Product matches expected values within tolerance") + { + CHECK_THAT(q3.X(), Catch::Matchers::WithinAbs(0.240985841f, near)); + CHECK_THAT(q3.Y(), Catch::Matchers::WithinAbs(0.357305080f, near)); + CHECK_THAT(q3.Z(), Catch::Matchers::WithinAbs(0.451658517f, near)); + CHECK_THAT(q3.W(), Catch::Matchers::WithinAbs(0.781193554f, near)); + } + } + } - TEST(Quaternion, operatorSubEqual) - { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2(1, 2, 3, 4); - quaternion1 -= quaternion2; - EXPECT_EQ(quaternion1.X(), 0); - EXPECT_EQ(quaternion1.Y(), 0); - EXPECT_EQ(quaternion1.Z(), 0); - EXPECT_EQ(quaternion1.W(), 0); - } + GIVEN("Two identical unit Quaternions") + { + Quaternionf q1(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); + Quaternionf q2(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); - TEST(Quaternion, operatorMulEqual) - { - Quaternionf quaternion1(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); - Quaternionf quaternion2(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); - quaternion1 *= quaternion2; - EXPECT_NEAR(quaternion1.X(), 0.6401839f, near); - EXPECT_NEAR(quaternion1.Y(), 0.3005481f, near); - EXPECT_NEAR(quaternion1.Z(), -0.705649f, near); - EXPECT_NEAR(quaternion1.W(), 0.04353243, near); + WHEN("Divided") + { + Quaternionf q3 = q1 / q2; + THEN("Result is identity (0, 0, 0, 1) within tolerance") + { + CHECK_THAT(q3.X(), Catch::Matchers::WithinAbs(0.f, near)); + CHECK_THAT(q3.Y(), Catch::Matchers::WithinAbs(0.f, near)); + CHECK_THAT(q3.Z(), Catch::Matchers::WithinAbs(0.f, near)); + CHECK_THAT(q3.W(), Catch::Matchers::WithinAbs(1.f, near)); + } + } + } } - TEST(Quaternion, operatorDivEqual) + SCENARIO("Quaternion - scalar operators") { - Quaternionf quaternion1(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); - Quaternionf quaternion2(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); - quaternion1 /= quaternion2; - EXPECT_NEAR(quaternion1.X(), 0.f, near); - EXPECT_NEAR(quaternion1.Y(), 0.f, near); - EXPECT_NEAR(quaternion1.Z(), 0.f, near); - EXPECT_NEAR(quaternion1.W(), 1.f, near); - } + GIVEN("A Quaternion(1, 2, 3, 4)") + { + Quaternionf q1(1, 2, 3, 4); - TEST(Quaternion, operatorEqual) - { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2(1, 2, 3, 4); - EXPECT_EQ(quaternion1, quaternion2); - } + WHEN("Scalar 1 added") + { + Quaternionf q2 = q1 + 1; + THEN("Components are incremented") + { + CHECK(q2.X() == 2); + CHECK(q2.Y() == 3); + CHECK(q2.Z() == 4); + CHECK(q2.W() == 5); + } + } - TEST(Quaternion, operatorNotEqual) - { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2(1, 2, 3, 5); - EXPECT_NE(quaternion1, quaternion2); - } + WHEN("Scalar 1 subtracted") + { + Quaternionf q2 = q1 - 1; + THEN("Components are decremented") + { + CHECK(q2.X() == 0); + CHECK(q2.Y() == 1); + CHECK(q2.Z() == 2); + CHECK(q2.W() == 3); + } + } - TEST(Quaternion, operatorAddScalar) - { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2 = quaternion1 + 1; - EXPECT_EQ(quaternion2.X(), 2); - EXPECT_EQ(quaternion2.Y(), 3); - EXPECT_EQ(quaternion2.Z(), 4); - EXPECT_EQ(quaternion2.W(), 5); - } + WHEN("Multiplied by scalar 2") + { + Quaternionf q2 = q1 * 2; + THEN("Components are doubled") + { + CHECK(q2.X() == 2); + CHECK(q2.Y() == 4); + CHECK(q2.Z() == 6); + CHECK(q2.W() == 8); + } + } - TEST(Quaternion, operatorSubScalar) - { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2 = quaternion1 - 1; - EXPECT_EQ(quaternion2.X(), 0); - EXPECT_EQ(quaternion2.Y(), 1); - EXPECT_EQ(quaternion2.Z(), 2); - EXPECT_EQ(quaternion2.W(), 3); + WHEN("Divided by scalar 2") + { + Quaternionf q2 = q1 / 2; + THEN("Components are halved") + { + CHECK(q2.X() == 0.5); + CHECK(q2.Y() == 1); + CHECK(q2.Z() == 1.5); + CHECK(q2.W() == 2); + } + } + } } - TEST(Quaternion, operatorMulScalar) + SCENARIO("Quaternion - compound assignment operators") { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2 = quaternion1 * 2; - EXPECT_EQ(quaternion2.X(), 2); - EXPECT_EQ(quaternion2.Y(), 4); - EXPECT_EQ(quaternion2.Z(), 6); - EXPECT_EQ(quaternion2.W(), 8); + GIVEN("Two Quaternions(1, 2, 3, 4)") + { + Quaternionf q1(1, 2, 3, 4); + Quaternionf q2(1, 2, 3, 4); + + WHEN("+=") + { + q1 += q2; + THEN("Components are doubled") + { + CHECK(q1.X() == 2); + CHECK(q1.Y() == 4); + CHECK(q1.Z() == 6); + CHECK(q1.W() == 8); + } + } + + WHEN("-=") + { + q1 -= q2; + THEN("Components are zero") + { + CHECK(q1.X() == 0); + CHECK(q1.Y() == 0); + CHECK(q1.Z() == 0); + CHECK(q1.W() == 0); + } + } + } + + GIVEN("Two identical unit Quaternions") + { + Quaternionf q1(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); + Quaternionf q2(0.4431357f, 0.2080396f, -0.4884507f, 0.7223339f); + + WHEN("*=") + { + q1 *= q2; + THEN("Product matches expected values within tolerance") + { + CHECK_THAT(q1.X(), Catch::Matchers::WithinAbs(0.6401839f, near)); + CHECK_THAT(q1.Y(), Catch::Matchers::WithinAbs(0.3005481f, near)); + CHECK_THAT(q1.Z(), Catch::Matchers::WithinAbs(-0.705649f, near)); + CHECK_THAT(q1.W(), Catch::Matchers::WithinAbs(0.04353243f, near)); + } + } + + WHEN("/=") + { + q1 /= q2; + THEN("Result is identity (0, 0, 0, 1) within tolerance") + { + CHECK_THAT(q1.X(), Catch::Matchers::WithinAbs(0.f, near)); + CHECK_THAT(q1.Y(), Catch::Matchers::WithinAbs(0.f, near)); + CHECK_THAT(q1.Z(), Catch::Matchers::WithinAbs(0.f, near)); + CHECK_THAT(q1.W(), Catch::Matchers::WithinAbs(1.f, near)); + } + } + } } - TEST(Quaternion, operatorDivScalar) + SCENARIO("Quaternion - equality operators") { - Quaternionf quaternion1(1, 2, 3, 4); - Quaternionf quaternion2 = quaternion1 / 2; - EXPECT_EQ(quaternion2.X(), 0.5); - EXPECT_EQ(quaternion2.Y(), 1); - EXPECT_EQ(quaternion2.Z(), 1.5); - EXPECT_EQ(quaternion2.W(), 2); + GIVEN("Two identical Quaternions(1, 2, 3, 4)") + { + Quaternionf q1(1, 2, 3, 4); + Quaternionf q2(1, 2, 3, 4); + THEN("They are equal") { CHECK(q1 == q2); } + } + + GIVEN("Two Quaternions differing in W") + { + Quaternionf q1(1, 2, 3, 4); + Quaternionf q2(1, 2, 3, 5); + THEN("They are not equal") { CHECK(q1 != q2); } + } } - TEST(Quaternion, operatorMultVector) + SCENARIO("Quaternion - vector multiplication") { - EulerAnglesf eulerAngles(0, 0, 0); - Quaternionf quaternion1(eulerAngles); - Vector3f vec = quaternion1 * Vector3f::Forward(); - EXPECT_EQ(vec, Vector3f::Forward()); + GIVEN("An identity Quaternion (from zero EulerAngles)") + { + EulerAnglesf eulerAngles(0, 0, 0); + Quaternionf q(eulerAngles); + + WHEN("Multiplied by Vector3f::Forward()") + { + Vector3f vec = q * Vector3f::Forward(); + THEN("The vector is unchanged") { CHECK(vec == Vector3f::Forward()); } + } + } } - TEST(Quaternion, Normalize) + SCENARIO("Quaternion - Normalize") { - EulerAnglesf eulerAngles(45, 50, 78); - Quaternionf quaternion1(eulerAngles); - quaternion1.Normalize(); - EXPECT_NEAR(1.f, quaternion1.Length(), near); + GIVEN("A Quaternion from EulerAngles(45, 50, 78)") + { + EulerAnglesf eulerAngles(45, 50, 78); + Quaternionf q(eulerAngles); + + WHEN("Normalized") + { + q.Normalize(); + THEN("Length is 1 within tolerance") { CHECK_THAT(q.Length(), Catch::Matchers::WithinAbs(1.f, near)); } + } + } } - TEST(Quaternion, ToEulerAngles) + SCENARIO("Quaternion - ToEulerAngles round-trip") { - Quaternionf q1(EulerAnglesf(0, 0, 0)); - EulerAnglesf angles1 = q1.ToEulerAngles(); - EXPECT_NEAR(angles1.Pitch(), 0, near); - EXPECT_NEAR(angles1.Yaw(), 0, near); - EXPECT_NEAR(angles1.Roll(), 0, near); - - Quaternionf q2(EulerAnglesf(30, 25, 68)); - EulerAnglesf angles2 = q2.ToEulerAngles(); - EXPECT_NEAR(angles2.Pitch(), 30.f, near); - EXPECT_NEAR(angles2.Yaw(), 25.f, near); - EXPECT_NEAR(angles2.Roll(), 68.f, near); + GIVEN("An identity Quaternion (from zero angles)") + { + Quaternionf q1(EulerAnglesf(0, 0, 0)); + WHEN("Converted to EulerAngles") + { + EulerAnglesf angles = q1.ToEulerAngles(); + THEN("All angles are ~0") + { + CHECK_THAT(angles.Pitch(), Catch::Matchers::WithinAbs(0.f, near)); + CHECK_THAT(angles.Yaw(), Catch::Matchers::WithinAbs(0.f, near)); + CHECK_THAT(angles.Roll(), Catch::Matchers::WithinAbs(0.f, near)); + } + } + } + + GIVEN("A Quaternion from EulerAngles(30, 25, 68)") + { + Quaternionf q2(EulerAnglesf(30, 25, 68)); + WHEN("Converted to EulerAngles") + { + EulerAnglesf angles = q2.ToEulerAngles(); + THEN("Angles match within tolerance") + { + CHECK_THAT(angles.Pitch(), Catch::Matchers::WithinAbs(30.f, near)); + CHECK_THAT(angles.Yaw(), Catch::Matchers::WithinAbs(25.f, near)); + CHECK_THAT(angles.Roll(), Catch::Matchers::WithinAbs(68.f, near)); + } + } + } } - TEST(Quaternion, ToRotationMatrix) + SCENARIO("Quaternion - ToRotationMatrix") { - //results calculated from https://www.andre-gaschler.com/rotationconverter/ - //Angles are expressed in radians - auto matrix1 = Quaternionf(-0.122257f, 0.4018732f, -0.9020968f, -0.0988561f).ToRotationMatrix(); - constexpr Matrix4f expected( - -0.9505614f, -0.2766193f, 0.1411200f, 0.f, - 0.0800920f, -0.6574507f, -0.7492288f, 0.f, - 0.3000306f, -0.7008854f, 0.6471023f, 0.f, - 0.f, 0.f, 0.f, 1.f - ); - - const auto* matData= matrix1.Data(); - const auto* expectData = expected.Data(); - for (std::size_t i = 0; i < matrix1.GetSize(); ++i) + GIVEN("A specific unit Quaternion") { - EXPECT_NEAR(matData[i], expectData[i], near); + // results calculated from https://www.andre-gaschler.com/rotationconverter/ + // Angles are expressed in radians + auto matrix1 = Quaternionf(-0.122257f, 0.4018732f, -0.9020968f, -0.0988561f).ToRotationMatrix(); + constexpr Matrix4f expected( + -0.9505614f, -0.2766193f, 0.1411200f, 0.f, + 0.0800920f, -0.6574507f, -0.7492288f, 0.f, + 0.3000306f, -0.7008854f, 0.6471023f, 0.f, + 0.f, 0.f, 0.f, 1.f + ); + + THEN("All matrix elements match within tolerance") + { + const auto* matData = matrix1.Data(); + const auto* expectData = expected.Data(); + for (std::size_t i = 0; i < matrix1.GetSize(); ++i) + { + CHECK_THAT(matData[i], Catch::Matchers::WithinAbs(expectData[i], near)); + } + } } } -}// namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/Result.cpp b/Src/Tests/Result.cpp index d2cdad2..6939224 100644 --- a/Src/Tests/Result.cpp +++ b/Src/Tests/Result.cpp @@ -4,13 +4,14 @@ #include -#include +#include #include #include namespace CCT_ANONYMOUS_NAMESPACE { using namespace cct; + class Bar { public: @@ -22,55 +23,70 @@ namespace CCT_ANONYMOUS_NAMESPACE Int32 c; }; - TEST(Result, ValueContruction) - { - Result result(28); - ASSERT_TRUE(result.IsOk()); - ASSERT_FALSE(result.IsError()); - ASSERT_EQ(result.GetValue(), 28); - } - - TEST(Result, ErrorConstruction) - { - Result result(std::string("Foo")); - ASSERT_FALSE(result.IsOk()); - ASSERT_TRUE(result.IsError()); - ASSERT_EQ(result.GetError(), "Foo"); - } - - TEST(Result, ValueVariadicConstruction) + SCENARIO("Result") { - Result result(std::in_place_type_t(), 1, true, 2); - - ASSERT_TRUE(result.IsOk()); - ASSERT_FALSE(result.IsError()); - auto value = result.GetValue(); - ASSERT_EQ(value.a, 1); - ASSERT_EQ(value.b, true); - ASSERT_EQ(value.c, 2); - } + GIVEN("A Result initialized with value 28") + { + Result result(28); + THEN("It is Ok and has the correct value") + { + REQUIRE(result.IsOk()); + REQUIRE_FALSE(result.IsError()); + REQUIRE(result.GetValue() == 28); + } + } - TEST(Result, ErrorVariadicConstruction) - { - Result result(std::in_place_type_t(), 1, true, 2); + GIVEN("A Result initialized with error \"Foo\"") + { + Result result(std::string("Foo")); + THEN("It is an error and has the correct message") + { + REQUIRE_FALSE(result.IsOk()); + REQUIRE(result.IsError()); + REQUIRE(result.GetError() == "Foo"); + } + } - ASSERT_FALSE(result.IsOk()); - ASSERT_TRUE(result.IsError()); - auto value = result.GetError(); - ASSERT_EQ(value.a, 1); - ASSERT_EQ(value.b, true); - ASSERT_EQ(value.c, 2); - } + GIVEN("A Result initialized with variadic value args (1, true, 2)") + { + Result result(std::in_place_type_t(), 1, true, 2); + THEN("It is Ok and the Bar fields are correct") + { + REQUIRE(result.IsOk()); + REQUIRE_FALSE(result.IsError()); + auto value = result.GetValue(); + REQUIRE(value.a == 1); + REQUIRE(value.b == true); + REQUIRE(value.c == 2); + } + } - TEST(Result, VoidTemplateSpecialisation) - { - Result result(std::in_place_type_t(), 1, true, 2); + GIVEN("A Result initialized with variadic error args (1, true, 2)") + { + Result result(std::in_place_type_t(), 1, true, 2); + THEN("It is an error and the Bar fields are correct") + { + REQUIRE_FALSE(result.IsOk()); + REQUIRE(result.IsError()); + auto value = result.GetError(); + REQUIRE(value.a == 1); + REQUIRE(value.b == true); + REQUIRE(value.c == 2); + } + } - ASSERT_FALSE(result.IsOk()); - ASSERT_TRUE(result.IsError()); - auto value = result.GetError(); - ASSERT_EQ(value.a, 1); - ASSERT_EQ(value.b, true); - ASSERT_EQ(value.c, 2); + GIVEN("A Result initialized with variadic error args (1, true, 2)") + { + Result result(std::in_place_type_t(), 1, true, 2); + THEN("It is an error and the Bar fields are correct") + { + REQUIRE_FALSE(result.IsOk()); + REQUIRE(result.IsError()); + auto value = result.GetError(); + REQUIRE(value.a == 1); + REQUIRE(value.b == true); + REQUIRE(value.c == 2); + } + } } -} // namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/Serialize.cpp b/Src/Tests/Serialize.cpp index 714ef1a..5cb5366 100644 --- a/Src/Tests/Serialize.cpp +++ b/Src/Tests/Serialize.cpp @@ -2,28 +2,33 @@ // Created by arthur on 10/08/2023. // -#include +#include #include namespace CCT_ANONYMOUS_NAMESPACE { using namespace cct; - using namespace cct; - TEST(Serialize, Basics) + SCENARIO("Serialize - Transform round-trip") { - Vector3f location(10.f, 15.f, 20.f); - Quaternion rotation = EulerAngles(20.f, 30.f, 40.f).ToQuaternion(); - Vector3f scale(10.f, 20.f, 30.f); - Transform transform(location, rotation, scale); - - Stream stream; - transform.Serialize(stream); - - stream.SetCursorPos(0); - Transform result; - result.Deserialize(stream); + GIVEN("A Transform with non-trivial location, rotation, and scale") + { + Vector3f location(10.f, 15.f, 20.f); + Quaternion rotation = EulerAngles(20.f, 30.f, 40.f).ToQuaternion(); + Vector3f scale(10.f, 20.f, 30.f); + Transform transform(location, rotation, scale); + + WHEN("Serialized to a Stream then deserialized") + { + Stream stream; + transform.Serialize(stream); + + stream.SetCursorPos(0); + Transform result; + result.Deserialize(stream); - ASSERT_EQ(transform, result); + THEN("The deserialized Transform equals the original") { REQUIRE(transform == result); } + } + } } -}// namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/Socket.cpp b/Src/Tests/Socket.cpp index b3a1422..785d75e 100644 --- a/Src/Tests/Socket.cpp +++ b/Src/Tests/Socket.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "Concerto/Core/Network/Socket/Socket.hpp" #include "Concerto/Core/Buffer/Buffer.hpp" @@ -13,77 +13,103 @@ #include #endif - namespace CCT_ANONYMOUS_NAMESPACE { using namespace cct; using namespace cct::net; - TEST(Socket, TcpServer) + SCENARIO("Socket - TCP server/client") { - Socket::Initialize(); - const IpAddress ipAddress(127, 0, 0, 1, 8080); - Socket server(SocketType::Tcp, IpProtocol::Ipv4); - server.SetBlocking(true); - server.Listen(ipAddress); - Socket client(SocketType::Tcp, IpProtocol::Ipv4); - client.Connect(ipAddress); - const std::string helloWorld = "Hello World"; - Buffer buffer(11); - std::memcpy(buffer.GetRawData(), helloWorld.c_str(), 11); - client.Send(buffer); - - Socket serverClient(SocketType::Tcp, IpProtocol::Ipv4); - serverClient.SetBlocking(true); - EXPECT_TRUE(server.Accept(serverClient)); - - Buffer buffer2(1024); + GIVEN("A TCP server listening on 127.0.0.1:8080 and a client that connects") + { + Socket::Initialize(); + const IpAddress ipAddress(127, 0, 0, 1, 8080); + Socket server(SocketType::Tcp, IpProtocol::Ipv4); + server.SetBlocking(true); + server.Listen(ipAddress); + + Socket client(SocketType::Tcp, IpProtocol::Ipv4); + client.Connect(ipAddress); + + const std::string helloWorld = "Hello World"; + Buffer buffer(11); + std::memcpy(buffer.GetRawData(), helloWorld.c_str(), 11); + client.Send(buffer); + + Socket serverClient(SocketType::Tcp, IpProtocol::Ipv4); + serverClient.SetBlocking(true); + REQUIRE(server.Accept(serverClient)); + + Buffer buffer2(1024); #ifdef CCT_PLATFORM_MACOS // because the CI is failing in release mode - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); #endif - const std::size_t availableBytes = serverClient.GetAvailableBytes(); - ASSERT_EQ(availableBytes, 11); - const std::size_t receivedSize = serverClient.Receive(buffer2); - buffer2.Resize(receivedSize); - ASSERT_EQ(buffer2, buffer); - Socket::UnInitialize(); + WHEN("The server reads the data sent by the client") + { + const std::size_t availableBytes = serverClient.GetAvailableBytes(); + const std::size_t receivedSize = serverClient.Receive(buffer2); + buffer2.Resize(receivedSize); + + THEN("11 bytes are available and the content matches the sent buffer") + { + REQUIRE(availableBytes == 11); + REQUIRE(buffer2 == buffer); + } + } + + Socket::UnInitialize(); + } } - TEST(Socket, UdpServer) + SCENARIO("Socket - UDP server/client") { - Socket::Initialize(); - Socket server(SocketType::Udp, IpProtocol::Ipv4); - server.SetBlocking(true); - auto ipAddress = IpAddress::AnyIPV4; - ipAddress.SetPort(8080); - ASSERT_TRUE(server.Bind(ipAddress)); - - - Socket client(SocketType::Udp, IpProtocol::Ipv4); - IpAddress ip(127, 0, 0, 1, 8080); - client.Connect(ip); - std::string helloWorld = "Hello World"; - Buffer buffer(11); - std::memcpy(buffer.GetRawData(), helloWorld.c_str(), 11); - client.Send(buffer); - - Buffer receivedBuffer(1024); - std::size_t receivedSize = server.Receive(receivedBuffer); - receivedBuffer.Resize(receivedSize); - ASSERT_EQ(receivedBuffer, buffer); - - Socket::UnInitialize(); + GIVEN("A UDP server bound to any:8080 and a client that sends data") + { + Socket::Initialize(); + Socket server(SocketType::Udp, IpProtocol::Ipv4); + server.SetBlocking(true); + auto ipAddress = IpAddress::AnyIPV4; + ipAddress.SetPort(8080); + REQUIRE(server.Bind(ipAddress)); + + Socket client(SocketType::Udp, IpProtocol::Ipv4); + IpAddress ip(127, 0, 0, 1, 8080); + client.Connect(ip); + + std::string helloWorld = "Hello World"; + Buffer buffer(11); + std::memcpy(buffer.GetRawData(), helloWorld.c_str(), 11); + client.Send(buffer); + + WHEN("The server reads the data") + { + Buffer receivedBuffer(1024); + std::size_t receivedSize = server.Receive(receivedBuffer); + receivedBuffer.Resize(receivedSize); + + THEN("The received buffer matches the sent buffer") { REQUIRE(receivedBuffer == buffer); } + } + + Socket::UnInitialize(); + } } - TEST(Socket, Errors) + SCENARIO("Socket - error handling") { - Socket::Initialize(); - Socket server(SocketType::Tcp, IpProtocol::Ipv4); - Socket client(SocketType::Udp, IpProtocol::Ipv4); - EXPECT_FALSE(server.Accept(client)); - - Socket::UnInitialize(); + GIVEN("A TCP server socket and a UDP client socket") + { + Socket::Initialize(); + Socket server(SocketType::Tcp, IpProtocol::Ipv4); + Socket client(SocketType::Udp, IpProtocol::Ipv4); + + WHEN("Accept is called with a UDP client") + { + THEN("Accept returns false") { CHECK_FALSE(server.Accept(client)); } + } + + Socket::UnInitialize(); + } } -}// namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/SparseVector.cpp b/Src/Tests/SparseVector.cpp index 936fd8d..6282e3b 100644 --- a/Src/Tests/SparseVector.cpp +++ b/Src/Tests/SparseVector.cpp @@ -3,175 +3,217 @@ // #include -#include +#include #include namespace CCT_ANONYMOUS_NAMESPACE { using namespace cct; - TEST(SparseVector, IntEmplace) - { - SparseVector sparseVector; - sparseVector.Emplace(5, 999); - sparseVector.Emplace(100, 999); - ASSERT_TRUE(sparseVector.Has(5)); - ASSERT_TRUE(sparseVector.Has(100)); - ASSERT_FALSE(sparseVector.Has(0)); - ASSERT_FALSE(sparseVector.Has(102)); - ASSERT_EQ(sparseVector[5], 999); - ASSERT_EQ(sparseVector[100], 999); - ASSERT_THROW(sparseVector[0], std::runtime_error); - ASSERT_THROW(sparseVector[102], std::out_of_range); - } - - TEST(SparseVector, HardToCopyEmplace) - { - using Ptr = std::unique_ptr; - class HardToCopy - { - public: - HardToCopy() = default; - HardToCopy(Ptr a, int b, int c) : - _ptr(std::move(a)), _b(b), _c(c) {} - HardToCopy(const HardToCopy&) = delete; - HardToCopy(HardToCopy&&) = default; - ~HardToCopy() = default; - HardToCopy& operator=(const HardToCopy&) = delete; - HardToCopy& operator=(HardToCopy&&) = default; - private: - Ptr _ptr; - int _b; - int _c; - }; - SparseVector sparseVector; - sparseVector.Emplace(0, HardToCopy()); - - auto ptr = std::make_unique(5); - sparseVector.Emplace(1, std::move(ptr), 2, 3); - } - - TEST(SparseVector, DefaultConstructor) - { - SparseVector sparseVector; - ASSERT_FALSE(sparseVector.Has(0)); - ASSERT_FALSE(sparseVector.Has(1)); - ASSERT_FALSE(sparseVector.Has(2)); - } - - TEST(SparseVector, CopyConstructor) - { - SparseVector sparseVector1; - sparseVector1.Emplace(0, 1); - sparseVector1.Emplace(1, 2); - sparseVector1.Emplace(2, 3); - - SparseVector sparseVector2(sparseVector1); - ASSERT_TRUE(sparseVector2.Has(0)); - ASSERT_TRUE(sparseVector2.Has(1)); - ASSERT_TRUE(sparseVector2.Has(2)); - ASSERT_EQ(sparseVector2[0], 1); - ASSERT_EQ(sparseVector2[1], 2); - ASSERT_EQ(sparseVector2[2], 3); - } - - TEST(SparseVector, MoveConstructor) - { - SparseVector sparseVector1; - sparseVector1.Emplace(0, 1); - sparseVector1.Emplace(1, 2); - sparseVector1.Emplace(2, 3); - - SparseVector sparseVector2(std::move(sparseVector1)); - ASSERT_TRUE(sparseVector2.Has(0)); - ASSERT_TRUE(sparseVector2.Has(1)); - ASSERT_TRUE(sparseVector2.Has(2)); - ASSERT_EQ(sparseVector2[0], 1); - ASSERT_EQ(sparseVector2[1], 2); - ASSERT_EQ(sparseVector2[2], 3); - } - - TEST(SparseVector, CopyAssignment) - { - SparseVector sparseVector1; - sparseVector1.Emplace(0, 1); - sparseVector1.Emplace(1, 2); - sparseVector1.Emplace(2, 3); - - SparseVector sparseVector2; - sparseVector2 = sparseVector1; - ASSERT_TRUE(sparseVector2.Has(0)); - ASSERT_TRUE(sparseVector2.Has(1)); - ASSERT_TRUE(sparseVector2.Has(2)); - ASSERT_EQ(sparseVector2[0], 1); - ASSERT_EQ(sparseVector2[1], 2); - ASSERT_EQ(sparseVector2[2], 3); - } - - TEST(SparseVector, MoveAssignment) - { - SparseVector sparseVector1; - sparseVector1.Emplace(0, 1); - sparseVector1.Emplace(1, 2); - sparseVector1.Emplace(2, 3); - - SparseVector sparseVector2; - sparseVector2 = std::move(sparseVector1); - ASSERT_TRUE(sparseVector2.Has(0)); - ASSERT_TRUE(sparseVector2.Has(1)); - ASSERT_TRUE(sparseVector2.Has(2)); - ASSERT_EQ(sparseVector2[0], 1); - ASSERT_EQ(sparseVector2[1], 2); - ASSERT_EQ(sparseVector2[2], 3); - } - - TEST(SparseVector, Clear) - { - SparseVector sparseVector; - sparseVector.Emplace(0, 1); - sparseVector.Emplace(1, 2); - sparseVector.Emplace(2, 3); - - sparseVector.Clear(); - ASSERT_FALSE(sparseVector.Has(0)); - ASSERT_FALSE(sparseVector.Has(1)); - ASSERT_FALSE(sparseVector.Has(2)); - } - - TEST(SparseVector, Erase) - { - SparseVector sparseVector; - sparseVector.Emplace(0, 1); - sparseVector.Emplace(1, 2); - sparseVector.Emplace(2, 3); - - sparseVector.Erase(1); - ASSERT_TRUE(sparseVector.Has(0)); - ASSERT_FALSE(sparseVector.Has(1)); - ASSERT_TRUE(sparseVector.Has(2)); - ASSERT_EQ(sparseVector[0], 1); - ASSERT_EQ(sparseVector[2], 3); - } - - TEST(SparseVector, Iterator) - { - SparseVector sparseVector; - sparseVector.Emplace(0, 1); - sparseVector.Emplace(1, 2); - sparseVector.Emplace(2, 3); - - int i = 0; - for (auto& value : sparseVector) - { - ASSERT_EQ(value, i + 1); - i++; - } - - i = 0; - for (const auto& value : sparseVector) - { - ASSERT_EQ(value, i + 1); - i++; - } - } -} \ No newline at end of file + SCENARIO("SparseVector - emplace and access") + { + GIVEN("A SparseVector with values at indices 5 and 100") + { + SparseVector sparseVector; + sparseVector.Emplace(5, 999); + sparseVector.Emplace(100, 999); + + THEN("Has() is correct and operator[] returns the right values") + { + REQUIRE(sparseVector.Has(5)); + REQUIRE(sparseVector.Has(100)); + REQUIRE_FALSE(sparseVector.Has(0)); + REQUIRE_FALSE(sparseVector.Has(102)); + REQUIRE(sparseVector[5] == 999); + REQUIRE(sparseVector[100] == 999); + } + + THEN("Accessing missing indices throws the correct exceptions") + { + REQUIRE_THROWS_AS(sparseVector[0], std::runtime_error); + REQUIRE_THROWS_AS(sparseVector[102], std::out_of_range); + } + } + + GIVEN("A SparseVector") + { + using Ptr = std::unique_ptr; + class HardToCopy + { + public: + HardToCopy() = default; + HardToCopy(Ptr a, int b, int c) : _ptr(std::move(a)), _b(b), _c(c) {} + HardToCopy(const HardToCopy&) = delete; + HardToCopy(HardToCopy&&) = default; + ~HardToCopy() = default; + HardToCopy& operator=(const HardToCopy&) = delete; + HardToCopy& operator=(HardToCopy&&) = default; + private: + Ptr _ptr; + int _b; + int _c; + }; + + SparseVector sparseVector; + + WHEN("Emplace with a default element and then with moved args") + { + sparseVector.Emplace(0, HardToCopy()); + auto ptr = std::make_unique(5); + sparseVector.Emplace(1, std::move(ptr), 2, 3); + THEN("No crash and elements are inserted") { CHECK(sparseVector.Has(0)); } + } + } + } + + SCENARIO("SparseVector - default construction") + { + GIVEN("A default-constructed SparseVector") + { + SparseVector sparseVector; + THEN("No elements are present") + { + REQUIRE_FALSE(sparseVector.Has(0)); + REQUIRE_FALSE(sparseVector.Has(1)); + REQUIRE_FALSE(sparseVector.Has(2)); + } + } + } + + SCENARIO("SparseVector - copy and move semantics") + { + GIVEN("A SparseVector with values at 0, 1, 2") + { + SparseVector sparseVector1; + sparseVector1.Emplace(0, 1); + sparseVector1.Emplace(1, 2); + sparseVector1.Emplace(2, 3); + + WHEN("Copy constructed") + { + SparseVector sparseVector2(sparseVector1); + THEN("The copy has the same elements") + { + REQUIRE(sparseVector2.Has(0)); + REQUIRE(sparseVector2.Has(1)); + REQUIRE(sparseVector2.Has(2)); + REQUIRE(sparseVector2[0] == 1); + REQUIRE(sparseVector2[1] == 2); + REQUIRE(sparseVector2[2] == 3); + } + } + + WHEN("Move constructed") + { + SparseVector sparseVector2(std::move(sparseVector1)); + THEN("The moved-into vector has the elements") + { + REQUIRE(sparseVector2.Has(0)); + REQUIRE(sparseVector2.Has(1)); + REQUIRE(sparseVector2.Has(2)); + REQUIRE(sparseVector2[0] == 1); + REQUIRE(sparseVector2[1] == 2); + REQUIRE(sparseVector2[2] == 3); + } + } + + WHEN("Copy assigned") + { + SparseVector sparseVector2; + sparseVector2 = sparseVector1; + THEN("The assigned vector has the elements") + { + REQUIRE(sparseVector2.Has(0)); + REQUIRE(sparseVector2.Has(1)); + REQUIRE(sparseVector2.Has(2)); + REQUIRE(sparseVector2[0] == 1); + REQUIRE(sparseVector2[1] == 2); + REQUIRE(sparseVector2[2] == 3); + } + } + + WHEN("Move assigned") + { + SparseVector sparseVector2; + sparseVector2 = std::move(sparseVector1); + THEN("The assigned vector has the elements") + { + REQUIRE(sparseVector2.Has(0)); + REQUIRE(sparseVector2.Has(1)); + REQUIRE(sparseVector2.Has(2)); + REQUIRE(sparseVector2[0] == 1); + REQUIRE(sparseVector2[1] == 2); + REQUIRE(sparseVector2[2] == 3); + } + } + } + } + + SCENARIO("SparseVector - Clear and Erase") + { + GIVEN("A SparseVector with values at 0, 1, 2") + { + SparseVector sparseVector; + sparseVector.Emplace(0, 1); + sparseVector.Emplace(1, 2); + sparseVector.Emplace(2, 3); + + WHEN("Clear() is called") + { + sparseVector.Clear(); + THEN("No elements remain") + { + REQUIRE_FALSE(sparseVector.Has(0)); + REQUIRE_FALSE(sparseVector.Has(1)); + REQUIRE_FALSE(sparseVector.Has(2)); + } + } + + WHEN("Erase(1) is called") + { + sparseVector.Erase(1); + THEN("Only index 1 is removed") + { + REQUIRE(sparseVector.Has(0)); + REQUIRE_FALSE(sparseVector.Has(1)); + REQUIRE(sparseVector.Has(2)); + REQUIRE(sparseVector[0] == 1); + REQUIRE(sparseVector[2] == 3); + } + } + } + } + + SCENARIO("SparseVector - iteration") + { + GIVEN("A SparseVector with sequential values 1, 2, 3") + { + SparseVector sparseVector; + sparseVector.Emplace(0, 1); + sparseVector.Emplace(1, 2); + sparseVector.Emplace(2, 3); + + WHEN("Iterated with range-for (non-const)") + { + int i = 0; + for (auto& value : sparseVector) + { + REQUIRE(value == i + 1); + i++; + } + THEN("All three values were visited in order") { CHECK(i == 3); } + } + + WHEN("Iterated with range-for (const)") + { + int i = 0; + for (const auto& value : sparseVector) + { + REQUIRE(value == i + 1); + i++; + } + THEN("All three values were visited in order") { CHECK(i == 3); } + } + } + } +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/ThreadPool.cpp b/Src/Tests/ThreadPool.cpp index 26bf8e5..7291303 100644 --- a/Src/Tests/ThreadPool.cpp +++ b/Src/Tests/ThreadPool.cpp @@ -13,571 +13,593 @@ #include -#include +#include using cct::ThreadPool; using namespace std::chrono_literals; -TEST(ThreadPoolInitialization, DefaultConstruction) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - initialization") { - ThreadPool pool; - EXPECT_GT(pool.GetWorkerCount(), 0u); -} - -TEST(ThreadPoolInitialization, ExplicitThreadCount) -{ - ThreadPool pool(4); - EXPECT_EQ(pool.GetWorkerCount(), 4u); -} - -TEST(ThreadPoolInitialization, SingleThreadPool) -{ - ThreadPool pool(1); - EXPECT_EQ(pool.GetWorkerCount(), 1u); -} - -TEST(ThreadPoolAddTask, SingleTaskExecution) -{ - ThreadPool pool(4); - std::atomic executed{false}; - - pool.AddTask( - [&executed]() - { executed.store(true, std::memory_order_relaxed); }); - - ASSERT_TRUE(pool.WaitFor(1000ms)); - EXPECT_TRUE(executed.load()); -} - -TEST(ThreadPoolAddTask, MultipleTasksExecution) -{ - ThreadPool pool(4); - std::atomic counter{0}; - constexpr int numTasks = 100; - - for (int i = 0; i < numTasks; ++i) + GIVEN("A default-constructed ThreadPool") { - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); + ThreadPool pool; + THEN("Worker count is greater than 0") { CHECK(pool.GetWorkerCount() > 0u); } } - ASSERT_TRUE(pool.WaitFor(5000ms)); - EXPECT_EQ(counter.load(), numTasks); -} - -TEST(ThreadPoolAddTask, TaskWithSharedState) -{ - ThreadPool pool(4); - std::atomic sum{0}; - constexpr int numTasks = 50; - - for (int i = 1; i <= numTasks; ++i) - { - pool.AddTask([&sum, i]() - { sum.fetch_add(i, std::memory_order_relaxed); }); - } - - ASSERT_TRUE(pool.WaitFor(5000ms)); - EXPECT_EQ(sum.load(), (numTasks * (numTasks + 1)) / 2); -} - -TEST(ThreadPoolSubmit, ReturningInt) -{ - ThreadPool pool(4); - auto future = pool.Submit([]() - { return 42; }); - - ASSERT_TRUE(pool.WaitFor(1000ms)); - EXPECT_EQ(future.get(), 42); -} - -TEST(ThreadPoolSubmit, ReturningString) -{ - ThreadPool pool(4); - auto future = pool.Submit([]() - { return std::string("Hello, ThreadPool!"); }); - - ASSERT_TRUE(pool.WaitFor(1000ms)); - EXPECT_EQ(future.get(), "Hello, ThreadPool!"); -} - -TEST(ThreadPoolSubmit, WithComputation) -{ - ThreadPool pool(4); - auto future = pool.Submit([]() - { - int sum = 0; - for (int i = 1; i <= 100; ++i) - sum += i; - return sum; }); - - ASSERT_TRUE(pool.WaitFor(1000ms)); - EXPECT_EQ(future.get(), 5050); -} - -TEST(ThreadPoolSubmit, MultipleSubmitCalls) -{ - ThreadPool pool(4); - std::vector> futures; - constexpr int numTasks = 20; - - for (int i = 0; i < numTasks; ++i) + GIVEN("A ThreadPool with 4 threads") { - futures.push_back(pool.Submit([i]() - { return i * i; })); + ThreadPool pool(4); + THEN("Worker count is 4") { CHECK(pool.GetWorkerCount() == 4u); } } - ASSERT_TRUE(pool.WaitFor(5000ms)); - - for (int i = 0; i < numTasks; ++i) + GIVEN("A ThreadPool with 1 thread") { - EXPECT_EQ(futures[i].get(), i * i); + ThreadPool pool(1); + THEN("Worker count is 1") { CHECK(pool.GetWorkerCount() == 1u); } } } -TEST(ThreadPoolExceptionHandling, TaskThrowingException) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - AddTask") { - ThreadPool pool(4); - auto future = - pool.Submit([]() -> int - { throw std::runtime_error("Test exception"); }); - - EXPECT_TRUE(pool.WaitFor(1000ms)); - EXPECT_THROW(future.get(), std::runtime_error); -} - -TEST(ThreadPoolExceptionHandling, MultipleTasksWithExceptions) -{ - ThreadPool pool(4); - std::vector> futures; - - for (int i = 0; i < 10; ++i) + GIVEN("A ThreadPool with 4 workers") { - futures.push_back(pool.Submit([i]() -> int - { - if (i % 2 == 0) - throw std::runtime_error("Even number"); - return i; })); - } - - ASSERT_TRUE(pool.WaitFor(5000ms)); - - for (int i = 0; i < 10; ++i) - { - if (i % 2 == 0) - EXPECT_THROW(futures[i].get(), std::runtime_error); - else - EXPECT_EQ(futures[i].get(), i); - } -} - -TEST(ThreadPoolExceptionHandling, PoolContinuesAfterException) -{ - ThreadPool pool(4); - auto future1 = - pool.Submit([]() -> int - { throw std::runtime_error("First exception"); }); - - ASSERT_TRUE(pool.WaitFor(1000ms)); - EXPECT_THROW(future1.get(), std::runtime_error); - - auto future2 = pool.Submit([]() - { return 42; }); + ThreadPool pool(4); - ASSERT_TRUE(pool.WaitFor(1000ms)); - EXPECT_EQ(future2.get(), 42); -} + WHEN("A single task is submitted via AddTask") + { + std::atomic executed{false}; + pool.AddTask([&executed]() { executed.store(true, std::memory_order_relaxed); }); + + THEN("The task executes within timeout") + { + REQUIRE(pool.WaitFor(1000ms)); + CHECK(executed.load()); + } + } -TEST(ThreadPoolWait, WaitForWithImmediateCompletion) -{ - ThreadPool pool(4); - std::atomic counter{0}; + WHEN("100 tasks are submitted via AddTask") + { + std::atomic counter{0}; + constexpr int numTasks = 100; + for (int i = 0; i < numTasks; ++i) + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + + THEN("All tasks execute within timeout") + { + REQUIRE(pool.WaitFor(5000ms)); + CHECK(counter.load() == numTasks); + } + } - for (int i = 0; i < 10; ++i) - { - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); + WHEN("50 tasks each add their index to a shared sum") + { + std::atomic sum{0}; + constexpr int numTasks = 50; + for (int i = 1; i <= numTasks; ++i) + pool.AddTask([&sum, i]() { sum.fetch_add(i, std::memory_order_relaxed); }); + + THEN("The sum is n*(n+1)/2") + { + REQUIRE(pool.WaitFor(5000ms)); + CHECK(sum.load() == (numTasks * (numTasks + 1)) / 2); + } + } } - - ASSERT_TRUE(pool.WaitFor(5000ms)); - EXPECT_EQ(counter.load(), 10); } -TEST(ThreadPoolWait, WaitForWithTimeout) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - Submit") { - ThreadPool pool(4); - - pool.AddTask([]() - { std::this_thread::sleep_for(500ms); }); - - EXPECT_FALSE(pool.WaitFor(100ms)); - EXPECT_TRUE(pool.WaitFor(1000ms)); -} - -TEST(ThreadPoolWait, WaitWithDeadline) -{ - ThreadPool pool(4); - - pool.AddTask([]() - { std::this_thread::sleep_for(500ms); }); + GIVEN("A ThreadPool with 4 workers") + { + ThreadPool pool(4); - auto deadline = std::chrono::steady_clock::now() + 200ms; - EXPECT_FALSE(pool.Wait(deadline)); + WHEN("A task returning int 42 is submitted") + { + auto future = pool.Submit([]() { return 42; }); + THEN("The future returns 42") + { + REQUIRE(pool.WaitFor(1000ms)); + CHECK(future.get() == 42); + } + } - deadline = std::chrono::steady_clock::now() + 1000ms; - EXPECT_TRUE(pool.Wait(deadline)); -} + WHEN("A task returning a string is submitted") + { + auto future = pool.Submit([]() { return std::string("Hello, ThreadPool!"); }); + THEN("The future returns the correct string") + { + REQUIRE(pool.WaitFor(1000ms)); + CHECK(future.get() == "Hello, ThreadPool!"); + } + } -TEST(ThreadPoolWait, MultipleWaitCalls) -{ - ThreadPool pool(4); - std::atomic counter{0}; + WHEN("A task computing a sum 1..100 is submitted") + { + auto future = pool.Submit([]() { + int sum = 0; + for (int i = 1; i <= 100; ++i) + sum += i; + return sum; + }); + THEN("The future returns 5050") + { + REQUIRE(pool.WaitFor(1000ms)); + CHECK(future.get() == 5050); + } + } - for (int i = 0; i < 50; ++i) - { - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); + WHEN("20 tasks each returning i*i are submitted") + { + std::vector> futures; + constexpr int numTasks = 20; + for (int i = 0; i < numTasks; ++i) + futures.push_back(pool.Submit([i]() { return i * i; })); + + THEN("Each future returns the correct squared value") + { + REQUIRE(pool.WaitFor(5000ms)); + for (int i = 0; i < numTasks; ++i) + CHECK(futures[i].get() == i * i); + } + } } - - ASSERT_TRUE(pool.WaitFor(5000ms)); - EXPECT_EQ(counter.load(), 50); - EXPECT_TRUE(pool.WaitFor(100ms)); } -TEST(ThreadPoolStop, RequestStopWithEmptyQueue) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - exception handling") { - ThreadPool pool(4); - std::atomic counter{0}; - - for (int i = 0; i < 10; ++i) + GIVEN("A ThreadPool with 4 workers") { - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); - } - - ASSERT_TRUE(pool.WaitFor(5000ms)); - pool.RequestStop(); + ThreadPool pool(4); - EXPECT_EQ(counter.load(), 10); -} + WHEN("A task throws std::runtime_error") + { + auto future = pool.Submit([]() -> int { throw std::runtime_error("Test exception"); }); + THEN("WaitFor succeeds and future.get() rethrows the exception") + { + CHECK(pool.WaitFor(1000ms)); + CHECK_THROWS_AS(future.get(), std::runtime_error); + } + } -TEST(ThreadPoolStop, RequestStopIsIdempotent) -{ - ThreadPool pool(4); + WHEN("10 tasks alternate between throwing and returning") + { + std::vector> futures; + for (int i = 0; i < 10; ++i) + { + futures.push_back(pool.Submit([i]() -> int { + if (i % 2 == 0) + throw std::runtime_error("Even number"); + return i; + })); + } + + THEN("Even futures throw, odd futures return their index") + { + REQUIRE(pool.WaitFor(5000ms)); + for (int i = 0; i < 10; ++i) + { + if (i % 2 == 0) + CHECK_THROWS_AS(futures[i].get(), std::runtime_error); + else + CHECK(futures[i].get() == i); + } + } + } - pool.RequestStop(); - pool.RequestStop(); - pool.RequestStop(); + WHEN("A first task throws, then a second succeeds") + { + auto future1 = pool.Submit([]() -> int { throw std::runtime_error("First exception"); }); + REQUIRE(pool.WaitFor(1000ms)); + CHECK_THROWS_AS(future1.get(), std::runtime_error); + + auto future2 = pool.Submit([]() { return 42; }); + THEN("The pool continues to work and returns 42") + { + REQUIRE(pool.WaitFor(1000ms)); + CHECK(future2.get() == 42); + } + } + } } -TEST(ThreadPoolStop, NoNewTasksAfterRequestStop) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - WaitFor / Wait") { - ThreadPool pool(4); - std::atomic counter{0}; - - pool.RequestStop(); + GIVEN("A ThreadPool with 4 workers") + { + ThreadPool pool(4); - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); + WHEN("10 tasks are submitted and WaitFor is called") + { + std::atomic counter{0}; + for (int i = 0; i < 10; ++i) + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + + THEN("All tasks complete and WaitFor returns true") + { + REQUIRE(pool.WaitFor(5000ms)); + CHECK(counter.load() == 10); + } + } - std::this_thread::sleep_for(100ms); - EXPECT_EQ(counter.load(), 0); -} + WHEN("A slow task (500ms) is submitted") + { + pool.AddTask([]() { std::this_thread::sleep_for(500ms); }); -TEST(ThreadPoolStop, SubmitAfterRequestStopFailsGracefully) -{ - ThreadPool pool(4); - pool.RequestStop(); + THEN("WaitFor(100ms) returns false but WaitFor(1000ms) returns true") + { + CHECK_FALSE(pool.WaitFor(100ms)); + CHECK(pool.WaitFor(1000ms)); + } + } - auto future = pool.Submit([]() - { return 42; }); + WHEN("A slow task (500ms) is submitted and Wait() with deadlines is used") + { + pool.AddTask([]() { std::this_thread::sleep_for(500ms); }); + + THEN("Wait with short deadline returns false; with long deadline returns true") + { + auto deadline1 = std::chrono::steady_clock::now() + 200ms; + CHECK_FALSE(pool.Wait(deadline1)); + auto deadline2 = std::chrono::steady_clock::now() + 1000ms; + CHECK(pool.Wait(deadline2)); + } + } - EXPECT_TRUE(future.valid()); + WHEN("50 tasks are submitted, WaitFor completes, then WaitFor is called again") + { + std::atomic counter{0}; + for (int i = 0; i < 50; ++i) + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + + THEN("All 50 tasks execute and the second WaitFor also returns true") + { + REQUIRE(pool.WaitFor(5000ms)); + CHECK(counter.load() == 50); + CHECK(pool.WaitFor(100ms)); + } + } + } } -TEST(ThreadPoolDestruction, DestructionWithEmptyQueue) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - RequestStop") { - std::atomic counter{0}; - + GIVEN("A ThreadPool with 4 workers that has completed all tasks") { ThreadPool pool(4); - + std::atomic counter{0}; for (int i = 0; i < 10; ++i) + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + REQUIRE(pool.WaitFor(5000ms)); + + WHEN("RequestStop is called") { - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); + pool.RequestStop(); + THEN("All 10 tasks have executed") { CHECK(counter.load() == 10); } } - - ASSERT_TRUE(pool.WaitFor(5000ms)); } - EXPECT_EQ(counter.load(), 10); -} - -TEST(ThreadPoolDestruction, DestructionWithPendingTasks) -{ - std::atomic counter{0}; - + GIVEN("A ThreadPool with 4 workers") { ThreadPool pool(4); - for (int i = 0; i < 100; ++i) + WHEN("RequestStop is called three times") { - pool.AddTask([&counter]() - { - std::this_thread::sleep_for(10ms); - counter.fetch_add(1, std::memory_order_relaxed); }); + THEN("No crash (idempotent)") + { + pool.RequestStop(); + pool.RequestStop(); + pool.RequestStop(); + } } - } - - EXPECT_GE(counter.load(), 0); -} -TEST(ThreadPoolConcurrentOperations, ConcurrentAddTaskFromMultipleThreads) -{ - ThreadPool pool(8); - std::atomic counter{0}; - constexpr int numThreads = 10; - constexpr int tasksPerThread = 100; + WHEN("RequestStop is called before adding a task") + { + pool.RequestStop(); + std::atomic counter{0}; + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + std::this_thread::sleep_for(100ms); + THEN("The task does not execute") { CHECK(counter.load() == 0); } + } - std::vector threads; - for (int t = 0; t < numThreads; ++t) - { - threads.emplace_back([&pool, &counter]() - { - for (int i = 0; i < tasksPerThread; ++i) { - pool.AddTask( - [&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); - } }); + WHEN("RequestStop is called, then Submit is called") + { + pool.RequestStop(); + auto future = pool.Submit([]() { return 42; }); + THEN("The future is valid") { CHECK(future.valid()); } + } } - - for (auto& thread : threads) - thread.join(); - - ASSERT_TRUE(pool.WaitFor(10000ms)); - EXPECT_EQ(counter.load(), numThreads * tasksPerThread); } -TEST(ThreadPoolConcurrentOperations, ConcurrentSubmitFromMultipleThreads) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - destruction") { - ThreadPool pool(8); - constexpr int numThreads = 10; - constexpr int tasksPerThread = 50; - - std::vector threads; - std::vector>> allFutures(numThreads); - - for (int t = 0; t < numThreads; ++t) + WHEN("A ThreadPool is destroyed after all tasks complete") { - threads.emplace_back([&pool, &allFutures, t]() - { - for (int i = 0; i < tasksPerThread; ++i) { - allFutures[t].push_back(pool.Submit([i]() { return i; })); - } }); + std::atomic counter{0}; + { + ThreadPool pool(4); + for (int i = 0; i < 10; ++i) + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + REQUIRE(pool.WaitFor(5000ms)); + } + THEN("All 10 tasks executed before destruction") { CHECK(counter.load() == 10); } } - for (auto& thread : threads) - thread.join(); - - ASSERT_TRUE(pool.WaitFor(10000ms)); - - for (int t = 0; t < numThreads; ++t) + WHEN("A ThreadPool is destroyed with pending tasks") { - for (int i = 0; i < tasksPerThread; ++i) + std::atomic counter{0}; { - EXPECT_EQ(allFutures[t][i].get(), i); + ThreadPool pool(4); + for (int i = 0; i < 100; ++i) + { + pool.AddTask([&counter]() { + std::this_thread::sleep_for(10ms); + counter.fetch_add(1, std::memory_order_relaxed); + }); + } } + THEN("Counter is non-negative (some tasks may not have run)") { CHECK(counter.load() >= 0); } } } -TEST(ThreadPoolConcurrentOperations, ConcurrentWaitFromMultipleThreads) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - concurrent operations") { - ThreadPool pool(8); - std::atomic counter{0}; - - for (int i = 0; i < 100; ++i) - { - pool.AddTask([&counter]() - { - std::this_thread::sleep_for(10ms); - counter.fetch_add(1, std::memory_order_relaxed); }); - } - - std::vector threads; - std::atomic waitSuccessCount{0}; - - for (int t = 0; t < 5; ++t) + GIVEN("A ThreadPool with 8 workers") { - threads.emplace_back([&pool, &waitSuccessCount]() - { - if (pool.WaitFor(10000ms)) - waitSuccessCount.fetch_add(1, std::memory_order_relaxed); }); - } - - for (auto& thread : threads) - thread.join(); - - EXPECT_EQ(counter.load(), 100); - EXPECT_EQ(waitSuccessCount.load(), 5); -} + ThreadPool pool(8); -TEST(ThreadPoolEdgeCases, TasksThatAddMoreTasks) -{ - ThreadPool pool(4); - std::atomic counter{0}; - - pool.AddTask([&pool, &counter]() - { - counter.fetch_add(1, std::memory_order_relaxed); + WHEN("10 threads each submit 100 AddTask calls concurrently") + { + std::atomic counter{0}; + constexpr int numThreads = 10; + constexpr int tasksPerThread = 100; + + std::vector threads; + for (int t = 0; t < numThreads; ++t) + { + threads.emplace_back([&pool, &counter]() { + for (int i = 0; i < tasksPerThread; ++i) + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + }); + } + for (auto& thread : threads) + thread.join(); + + THEN("All tasks execute") + { + REQUIRE(pool.WaitFor(10000ms)); + CHECK(counter.load() == numThreads * tasksPerThread); + } + } - pool.AddTask( - [&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); }); + WHEN("10 threads each submit 50 Submit calls concurrently") + { + constexpr int numThreads = 10; + constexpr int tasksPerThread = 50; + + std::vector threads; + std::vector>> allFutures(numThreads); + + for (int t = 0; t < numThreads; ++t) + { + threads.emplace_back([&pool, &allFutures, t]() { + for (int i = 0; i < tasksPerThread; ++i) + allFutures[t].push_back(pool.Submit([i]() { return i; })); + }); + } + for (auto& thread : threads) + thread.join(); + + THEN("Each future returns the correct value") + { + REQUIRE(pool.WaitFor(10000ms)); + for (int t = 0; t < numThreads; ++t) + for (int i = 0; i < tasksPerThread; ++i) + CHECK(allFutures[t][i].get() == i); + } + } - ASSERT_TRUE(pool.WaitFor(5000ms)); - EXPECT_EQ(counter.load(), 2); + WHEN("5 threads call WaitFor concurrently while 100 tasks run") + { + std::atomic counter{0}; + for (int i = 0; i < 100; ++i) + { + pool.AddTask([&counter]() { + std::this_thread::sleep_for(10ms); + counter.fetch_add(1, std::memory_order_relaxed); + }); + } + + std::vector threads; + std::atomic waitSuccessCount{0}; + for (int t = 0; t < 5; ++t) + { + threads.emplace_back([&pool, &waitSuccessCount]() { + if (pool.WaitFor(10000ms)) + waitSuccessCount.fetch_add(1, std::memory_order_relaxed); + }); + } + for (auto& thread : threads) + thread.join(); + + THEN("All 100 tasks ran and all 5 WaitFor calls succeeded") + { + CHECK(counter.load() == 100); + CHECK(waitSuccessCount.load() == 5); + } + } + } } -TEST(ThreadPoolEdgeCases, LargeNumberOfThreads) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - edge cases") { - ThreadPool pool(100); - std::atomic counter{0}; - - for (int i = 0; i < 1000; ++i) + GIVEN("A ThreadPool with 4 workers") { - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); - } + ThreadPool pool(4); - ASSERT_TRUE(pool.WaitFor(10000ms)); - EXPECT_EQ(counter.load(), 1000); -} + WHEN("A task adds another task from within itself") + { + std::atomic counter{0}; + pool.AddTask([&pool, &counter]() { + counter.fetch_add(1, std::memory_order_relaxed); + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + }); + + THEN("Both the original and the nested task execute") + { + REQUIRE(pool.WaitFor(5000ms)); + CHECK(counter.load() == 2); + } + } -TEST(ThreadPoolEdgeCases, TasksWithVaryingDurations) -{ - ThreadPool pool(4); - std::atomic counter{0}; + WHEN("WaitFor is called on an empty pool") + { + THEN("It returns immediately with true") { CHECK(pool.WaitFor(100ms)); } + } - for (int i = 0; i < 20; ++i) - { - pool.AddTask([&counter, i]() - { - if (i % 2 == 0) - std::this_thread::sleep_for(10ms); - else - std::this_thread::sleep_for(50ms); - counter.fetch_add(1, std::memory_order_relaxed); }); + WHEN("RequestStop is called immediately after construction, then a task is added") + { + std::atomic counter{0}; + pool.RequestStop(); + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + std::this_thread::sleep_for(100ms); + THEN("The task does not execute") { CHECK(counter.load() == 0); } + } } - ASSERT_TRUE(pool.WaitFor(10000ms)); - EXPECT_EQ(counter.load(), 20); -} - -TEST(ThreadPoolEdgeCases, EmptyPoolBehavior) -{ - ThreadPool pool(4); - EXPECT_TRUE(pool.WaitFor(100ms)); -} - -TEST(ThreadPoolEdgeCases, ImmediateRequestStopAfterConstruction) -{ - ThreadPool pool(4); - pool.RequestStop(); + GIVEN("A ThreadPool with 100 workers") + { + ThreadPool pool(100); - std::atomic counter{0}; - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); + WHEN("1000 tasks are submitted") + { + std::atomic counter{0}; + for (int i = 0; i < 1000; ++i) + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + + THEN("All 1000 tasks execute") + { + REQUIRE(pool.WaitFor(10000ms)); + CHECK(counter.load() == 1000); + } + } + } - std::this_thread::sleep_for(100ms); - EXPECT_EQ(counter.load(), 0); -} + GIVEN("A ThreadPool with 4 workers and 20 tasks with varying durations") + { + ThreadPool pool(4); + std::atomic counter{0}; -TEST(ThreadPoolStress, HighVolumeTaskProcessing) -{ - ThreadPool pool(8); - std::atomic counter{0}; - constexpr int numTasks = 10000; + for (int i = 0; i < 20; ++i) + { + pool.AddTask([&counter, i]() { + if (i % 2 == 0) + std::this_thread::sleep_for(10ms); + else + std::this_thread::sleep_for(50ms); + counter.fetch_add(1, std::memory_order_relaxed); + }); + } - for (int i = 0; i < numTasks; ++i) - { - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); + THEN("All 20 tasks complete") + { + REQUIRE(pool.WaitFor(10000ms)); + CHECK(counter.load() == 20); + } } - - ASSERT_TRUE(pool.WaitFor(30000ms)); - EXPECT_EQ(counter.load(), numTasks); } -TEST(ThreadPoolStress, MixedAddTaskAndSubmit) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - stress tests") { - ThreadPool pool(8); - std::atomic addTaskCounter{0}; - std::vector> futures; - constexpr int numOperations = 1000; - - for (int i = 0; i < numOperations; ++i) + GIVEN("A ThreadPool with 8 workers") { - if (i % 2 == 0) + ThreadPool pool(8); + + WHEN("10000 tasks are submitted via AddTask") { - pool.AddTask([&addTaskCounter]() - { addTaskCounter.fetch_add(1, std::memory_order_relaxed); }); + std::atomic counter{0}; + constexpr int numTasks = 10000; + for (int i = 0; i < numTasks; ++i) + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + + THEN("All tasks complete") + { + REQUIRE(pool.WaitFor(30000ms)); + CHECK(counter.load() == numTasks); + } } - else + + WHEN("1000 operations alternate between AddTask and Submit") { - futures.push_back(pool.Submit([i]() - { return i; })); + std::atomic addTaskCounter{0}; + std::vector> futures; + constexpr int numOperations = 1000; + + for (int i = 0; i < numOperations; ++i) + { + if (i % 2 == 0) + pool.AddTask([&addTaskCounter]() { addTaskCounter.fetch_add(1, std::memory_order_relaxed); }); + else + futures.push_back(pool.Submit([i]() { return i; })); + } + + THEN("AddTask counter is 500 and Submit futures return correct values") + { + REQUIRE(pool.WaitFor(30000ms)); + CHECK(addTaskCounter.load() == numOperations / 2); + for (size_t i = 0; i < futures.size(); ++i) + CHECK(futures[i].get() == static_cast(i * 2 + 1)); + } } } - - ASSERT_TRUE(pool.WaitFor(30000ms)); - EXPECT_EQ(addTaskCounter.load(), numOperations / 2); - - for (size_t i = 0; i < futures.size(); ++i) - { - EXPECT_EQ(futures[i].get(), static_cast(i * 2 + 1)); - } } -TEST(ThreadPoolThreadSafety, NoDataRacesWithSharedAtomic) +// --------------------------------------------------------------------------- +SCENARIO("ThreadPool - thread safety") { - ThreadPool pool(4); - std::atomic counter{0}; - constexpr int numIncrements = 10000; - - for (int i = 0; i < numIncrements; ++i) + GIVEN("A ThreadPool with 4 workers") { - pool.AddTask( - [&counter]() - { counter.fetch_add(1, std::memory_order_relaxed); }); - } - - ASSERT_TRUE(pool.WaitFor(30000ms)); - EXPECT_EQ(counter.load(), numIncrements); -} + ThreadPool pool(4); -TEST(ThreadPoolThreadSafety, GetWorkerCountIsThreadSafe) -{ - ThreadPool pool(4); - std::vector threads; + WHEN("10000 increments on a shared atomic are submitted") + { + std::atomic counter{0}; + constexpr int numIncrements = 10000; + for (int i = 0; i < numIncrements; ++i) + pool.AddTask([&counter]() { counter.fetch_add(1, std::memory_order_relaxed); }); + + THEN("The final count is exact") + { + REQUIRE(pool.WaitFor(30000ms)); + CHECK(counter.load() == numIncrements); + } + } - for (int t = 0; t < 10; ++t) - { - threads.emplace_back([&pool]() - { - for (int i = 0; i < 100; ++i) { - volatile size_t count = pool.GetWorkerCount(); - (void)count; - } }); + WHEN("10 threads each call GetWorkerCount() 100 times concurrently") + { + std::vector threads; + for (int t = 0; t < 10; ++t) + { + threads.emplace_back([&pool]() { + for (int i = 0; i < 100; ++i) + { + volatile size_t count = pool.GetWorkerCount(); + (void)count; + } + }); + } + for (auto& thread : threads) + thread.join(); + THEN("No crash") { CHECK(true); } + } } - - for (auto& thread : threads) - thread.join(); } diff --git a/Src/Tests/Transform.cpp b/Src/Tests/Transform.cpp index 2e99912..714cc9d 100644 --- a/Src/Tests/Transform.cpp +++ b/Src/Tests/Transform.cpp @@ -2,18 +2,25 @@ // Created by arthur on 23/02/2023. // -#include +#include #include "Concerto/Core/Math/Transform/Transform.hpp" namespace CCT_ANONYMOUS_NAMESPACE { using namespace cct; - TEST(Transform, Constructor) + SCENARIO("Transform - construction") { - Transform transform(Vector3f(0.f, 0.f, 0.f), Quaternionf(0.f, 0.f, 0.f, 0.f), Vector3f(0.f, 0.f, 0.f)); - ASSERT_EQ(transform.GetLocation(), Vector3f(0.f, 0.f, 0.f)); - ASSERT_EQ(transform.GetRotation(), Quaternionf(0.f, 0.f, 0.f, 0.f)); - ASSERT_EQ(transform.GetScale(), Vector3f(0.f, 0.f, 0.f)); + GIVEN("A Transform constructed with zero location, rotation, and scale") + { + Transform transform(Vector3f(0.f, 0.f, 0.f), Quaternionf(0.f, 0.f, 0.f, 0.f), Vector3f(0.f, 0.f, 0.f)); + + THEN("GetLocation, GetRotation, and GetScale return the expected values") + { + REQUIRE(transform.GetLocation() == Vector3f(0.f, 0.f, 0.f)); + REQUIRE(transform.GetRotation() == Quaternionf(0.f, 0.f, 0.f, 0.f)); + REQUIRE(transform.GetScale() == Vector3f(0.f, 0.f, 0.f)); + } + } } -}// namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/TypeInfo.cpp b/Src/Tests/TypeInfo.cpp index 4d0505e..42fc3b5 100644 --- a/Src/Tests/TypeInfo.cpp +++ b/Src/Tests/TypeInfo.cpp @@ -2,7 +2,7 @@ // Created by arthur on 24/12/2023. // -#include +#include #include #include @@ -10,33 +10,48 @@ enum class EnumClass{}; enum struct EnumStruct{}; class Class{}; struct Struct{}; + namespace CCT_ANONYMOUS_NAMESPACE { - TEST(TypeInfo, TypeName) + SCENARIO("TypeInfo - TypeName") { + GIVEN("Various types") { - constexpr auto typeName = cct::TypeName(); - ASSERT_EQ(typeName, "int"); - } - { - constexpr auto typeName = cct::TypeName>(); - ASSERT_EQ(typeName, "std::shared_ptr"); - } - { - constexpr auto typeName = cct::TypeName(); - ASSERT_EQ(typeName, "EnumClass"); - } - { - constexpr auto typeName = cct::TypeName(); - ASSERT_EQ(typeName, "EnumStruct"); - } - { - constexpr auto typeName = cct::TypeName(); - ASSERT_EQ(typeName, "Class"); - } - { - constexpr auto typeName = cct::TypeName(); - ASSERT_EQ(typeName, "Struct"); + THEN("TypeName returns \"int\"") + { + constexpr auto typeName = cct::TypeName(); + REQUIRE(typeName == "int"); + } + + THEN("TypeName> returns \"std::shared_ptr\"") + { + constexpr auto typeName = cct::TypeName>(); + REQUIRE(typeName == "std::shared_ptr"); + } + + THEN("TypeName returns \"EnumClass\"") + { + constexpr auto typeName = cct::TypeName(); + REQUIRE(typeName == "EnumClass"); + } + + THEN("TypeName returns \"EnumStruct\"") + { + constexpr auto typeName = cct::TypeName(); + REQUIRE(typeName == "EnumStruct"); + } + + THEN("TypeName returns \"Class\"") + { + constexpr auto typeName = cct::TypeName(); + REQUIRE(typeName == "Class"); + } + + THEN("TypeName returns \"Struct\"") + { + constexpr auto typeName = cct::TypeName(); + REQUIRE(typeName == "Struct"); + } } } -}// namespace CCT_ANONYMOUS_NAMESPACE \ No newline at end of file +} // namespace CCT_ANONYMOUS_NAMESPACE diff --git a/Src/Tests/main.cpp b/Src/Tests/main.cpp index 6ef047d..0b23a97 100644 --- a/Src/Tests/main.cpp +++ b/Src/Tests/main.cpp @@ -2,28 +2,10 @@ // Created by arthur on 17/06/2022. // -#include -#include +#define CATCH_CONFIG_RUNNER +#include -int main(int argc, char** argv) +int main(int argc, char const* const argv[]) { - cct::Logger::Info("Begin InitGoogleTest"); - ::testing::InitGoogleTest(&argc, argv); - cct::Logger::Info("End InitGoogleTest"); - cct::Logger::Info("Begin RUN_ALL_TESTS"); - try - { - const auto ret = RUN_ALL_TESTS(); - cct::Logger::Info("End RUN_ALL_TESTS returned: {}", ret); - return ret; - } - catch (const std::exception& e) - { - cct::Logger::Info("End RUN_ALL_TESTS throwed: {}", e.what()); - } - catch (...) - { - cct::Logger::Info("End RUN_ALL_TESTS throwed unknown exception"); - } - return 254; -} \ No newline at end of file + return Catch::Session().run(argc, argv); +} diff --git a/xmake.lua b/xmake.lua index f2edcb4..cb74118 100644 --- a/xmake.lua +++ b/xmake.lua @@ -102,7 +102,7 @@ if has_config("tests") then end if has_config("tests") then - add_requires("gtest") + add_requires("catch2") target("concerto-core-dummy") set_kind("shared") @@ -133,7 +133,7 @@ extern "C" { set_languages("cxx20") add_files("Src/Tests/*.cpp") - add_packages("gtest") + add_packages("catch2") if has_config("unitybuild") then add_rules("c++.unity_build", {batchsize = 12, uniqueid = "CONCERTO_UNITY_BUILD_ID"})