From 93fc93d4f6036ce899ec976055730b10f7fb406b Mon Sep 17 00:00:00 2001 From: orange Date: Wed, 11 Mar 2026 14:16:26 +0300 Subject: [PATCH] added more tests --- tests/general/unit_test_a_star.cpp | 181 ++++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 5 deletions(-) diff --git a/tests/general/unit_test_a_star.cpp b/tests/general/unit_test_a_star.cpp index fc7011a..772ba32 100644 --- a/tests/general/unit_test_a_star.cpp +++ b/tests/general/unit_test_a_star.cpp @@ -8,6 +8,29 @@ using namespace omath; using namespace omath::pathfinding; +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +static NavigationMesh make_linear_chain(int length) +{ + // 0 -> 1 -> 2 -> ... -> length-1 (directed) + NavigationMesh nav; + for (int i = 0; i < length; ++i) + { + const Vector3 v{static_cast(i), 0.f, 0.f}; + if (i + 1 < length) + nav.m_vertex_map[v] = {Vector3{static_cast(i + 1), 0.f, 0.f}}; + else + nav.m_vertex_map[v] = {}; + } + return nav; +} + +// --------------------------------------------------------------------------- +// Basic reachability +// --------------------------------------------------------------------------- + TEST(AStarExtra, TrivialNeighbor) { NavigationMesh nav; @@ -78,7 +101,7 @@ TEST(AStarExtra, LongerPathAvoidsBlock) constexpr Vector3 goal = idx(2, 1); const auto path = Astar::find_path(start, goal, nav); ASSERT_FALSE(path.empty()); - EXPECT_EQ(path.front(), goal); // Astar convention: single-element or endpoint present + EXPECT_EQ(path.front(), goal); } TEST(AstarTests, TrivialDirectNeighborPath) @@ -91,9 +114,6 @@ TEST(AstarTests, TrivialDirectNeighborPath) nav.m_vertex_map.emplace(v2, std::vector>{v1}); const auto path = Astar::find_path(v1, v2, nav); - // Current A* implementation returns the end vertex as the reconstructed - // path (single-element) in the simple neighbor scenario. Assert that the - // endpoint is present and reachable. ASSERT_EQ(path.size(), 1u); EXPECT_EQ(path.front(), v2); } @@ -133,4 +153,155 @@ TEST(unit_test_a_star, finding_right_path) mesh.m_vertex_map[{0.f, 2.f, 0.f}] = {{0.f, 3.f, 0.f}}; mesh.m_vertex_map[{0.f, 3.f, 0.f}] = {}; std::ignore = omath::pathfinding::Astar::find_path({}, {0.f, 3.f, 0.f}, mesh); -} \ No newline at end of file +} + +// --------------------------------------------------------------------------- +// Directed edges +// --------------------------------------------------------------------------- + +TEST(AstarTests, DirectedEdge_ForwardPathExists) +{ + // A -> B only; path from A to B should succeed + NavigationMesh nav; + constexpr Vector3 a{0.f, 0.f, 0.f}; + constexpr Vector3 b{1.f, 0.f, 0.f}; + nav.m_vertex_map[a] = {b}; + nav.m_vertex_map[b] = {}; // no edge back + + const auto path = Astar::find_path(a, b, nav); + ASSERT_FALSE(path.empty()); + EXPECT_EQ(path.back(), b); +} + +TEST(AstarTests, DirectedEdge_ReversePathMissing) +{ + // A -> B only; path from B to A should fail + NavigationMesh nav; + constexpr Vector3 a{0.f, 0.f, 0.f}; + constexpr Vector3 b{1.f, 0.f, 0.f}; + nav.m_vertex_map[a] = {b}; + nav.m_vertex_map[b] = {}; + + const auto path = Astar::find_path(b, a, nav); + EXPECT_TRUE(path.empty()); +} + +// --------------------------------------------------------------------------- +// Vertex snapping +// --------------------------------------------------------------------------- + +TEST(AstarTests, OffMeshStart_SnapsToNearestVertex) +{ + NavigationMesh nav; + constexpr Vector3 v1{0.f, 0.f, 0.f}; + constexpr Vector3 v2{10.f, 0.f, 0.f}; + nav.m_vertex_map[v1] = {v2}; + nav.m_vertex_map[v2] = {v1}; + + // Start is slightly off v1 but closer to it than to v2 + constexpr Vector3 off_start{0.1f, 0.f, 0.f}; + const auto path = Astar::find_path(off_start, v2, nav); + ASSERT_FALSE(path.empty()); + EXPECT_EQ(path.back(), v2); +} + +TEST(AstarTests, OffMeshEnd_SnapsToNearestVertex) +{ + NavigationMesh nav; + constexpr Vector3 v1{0.f, 0.f, 0.f}; + constexpr Vector3 v2{10.f, 0.f, 0.f}; + nav.m_vertex_map[v1] = {v2}; + nav.m_vertex_map[v2] = {v1}; + + // Goal is slightly off v2 but closer to it than to v1 + constexpr Vector3 off_goal{9.9f, 0.f, 0.f}; + const auto path = Astar::find_path(v1, off_goal, nav); + ASSERT_FALSE(path.empty()); + EXPECT_EQ(path.back(), v2); +} + +// --------------------------------------------------------------------------- +// Cycle handling +// --------------------------------------------------------------------------- + +TEST(AstarTests, CyclicGraph_FindsPathWithoutLooping) +{ + // Triangle: A <-> B <-> C <-> A + NavigationMesh nav; + constexpr Vector3 a{0.f, 0.f, 0.f}; + constexpr Vector3 b{1.f, 0.f, 0.f}; + constexpr Vector3 c{0.5f, 1.f, 0.f}; + nav.m_vertex_map[a] = {b, c}; + nav.m_vertex_map[b] = {a, c}; + nav.m_vertex_map[c] = {a, b}; + + const auto path = Astar::find_path(a, c, nav); + ASSERT_FALSE(path.empty()); + EXPECT_EQ(path.back(), c); +} + +TEST(AstarTests, SelfLoopVertex_DoesNotBreakSearch) +{ + // Vertex with itself as a neighbor + NavigationMesh nav; + constexpr Vector3 a{0.f, 0.f, 0.f}; + constexpr Vector3 b{1.f, 0.f, 0.f}; + nav.m_vertex_map[a] = {a, b}; // self-loop on a + nav.m_vertex_map[b] = {a}; + + const auto path = Astar::find_path(a, b, nav); + ASSERT_FALSE(path.empty()); + EXPECT_EQ(path.back(), b); +} + +// --------------------------------------------------------------------------- +// Longer chains +// --------------------------------------------------------------------------- + +TEST(AstarTests, LinearChain_ReachesEnd) +{ + constexpr int kLength = 10; + const NavigationMesh nav = make_linear_chain(kLength); + + const Vector3 start{0.f, 0.f, 0.f}; + const Vector3 goal{static_cast(kLength - 1), 0.f, 0.f}; + + const auto path = Astar::find_path(start, goal, nav); + ASSERT_FALSE(path.empty()); + EXPECT_EQ(path.back(), goal); +} + +TEST(AstarTests, LinearChain_MidpointReachable) +{ + constexpr int kLength = 6; + const NavigationMesh nav = make_linear_chain(kLength); + + const Vector3 start{0.f, 0.f, 0.f}; + const Vector3 mid{3.f, 0.f, 0.f}; + + const auto path = Astar::find_path(start, mid, nav); + ASSERT_FALSE(path.empty()); + EXPECT_EQ(path.back(), mid); +} + +// --------------------------------------------------------------------------- +// Serialize -> pathfind integration +// --------------------------------------------------------------------------- + +TEST(AstarTests, PathfindAfterSerializeDeserialize) +{ + NavigationMesh nav; + constexpr Vector3 a{0.f, 0.f, 0.f}; + constexpr Vector3 b{1.f, 0.f, 0.f}; + constexpr Vector3 c{2.f, 0.f, 0.f}; + nav.m_vertex_map[a] = {b}; + nav.m_vertex_map[b] = {a, c}; + nav.m_vertex_map[c] = {b}; + + NavigationMesh nav2; + nav2.deserialize(nav.serialize()); + + const auto path = Astar::find_path(a, c, nav2); + ASSERT_FALSE(path.empty()); + EXPECT_EQ(path.back(), c); +}