added more tests

This commit is contained in:
2026-03-11 14:16:26 +03:00
parent b8a578774c
commit 93fc93d4f6

View File

@@ -8,6 +8,29 @@
using namespace omath; using namespace omath;
using namespace omath::pathfinding; 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<float> v{static_cast<float>(i), 0.f, 0.f};
if (i + 1 < length)
nav.m_vertex_map[v] = {Vector3<float>{static_cast<float>(i + 1), 0.f, 0.f}};
else
nav.m_vertex_map[v] = {};
}
return nav;
}
// ---------------------------------------------------------------------------
// Basic reachability
// ---------------------------------------------------------------------------
TEST(AStarExtra, TrivialNeighbor) TEST(AStarExtra, TrivialNeighbor)
{ {
NavigationMesh nav; NavigationMesh nav;
@@ -78,7 +101,7 @@ TEST(AStarExtra, LongerPathAvoidsBlock)
constexpr Vector3<float> goal = idx(2, 1); constexpr Vector3<float> goal = idx(2, 1);
const auto path = Astar::find_path(start, goal, nav); const auto path = Astar::find_path(start, goal, nav);
ASSERT_FALSE(path.empty()); ASSERT_FALSE(path.empty());
EXPECT_EQ(path.front(), goal); // Astar convention: single-element or endpoint present EXPECT_EQ(path.front(), goal);
} }
TEST(AstarTests, TrivialDirectNeighborPath) TEST(AstarTests, TrivialDirectNeighborPath)
@@ -91,9 +114,6 @@ TEST(AstarTests, TrivialDirectNeighborPath)
nav.m_vertex_map.emplace(v2, std::vector<Vector3<float>>{v1}); nav.m_vertex_map.emplace(v2, std::vector<Vector3<float>>{v1});
const auto path = Astar::find_path(v1, v2, nav); 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); ASSERT_EQ(path.size(), 1u);
EXPECT_EQ(path.front(), v2); 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, 2.f, 0.f}] = {{0.f, 3.f, 0.f}};
mesh.m_vertex_map[{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); std::ignore = omath::pathfinding::Astar::find_path({}, {0.f, 3.f, 0.f}, mesh);
} }
// ---------------------------------------------------------------------------
// Directed edges
// ---------------------------------------------------------------------------
TEST(AstarTests, DirectedEdge_ForwardPathExists)
{
// A -> B only; path from A to B should succeed
NavigationMesh nav;
constexpr Vector3<float> a{0.f, 0.f, 0.f};
constexpr Vector3<float> 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<float> a{0.f, 0.f, 0.f};
constexpr Vector3<float> 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<float> v1{0.f, 0.f, 0.f};
constexpr Vector3<float> 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<float> 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<float> v1{0.f, 0.f, 0.f};
constexpr Vector3<float> 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<float> 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<float> a{0.f, 0.f, 0.f};
constexpr Vector3<float> b{1.f, 0.f, 0.f};
constexpr Vector3<float> 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<float> a{0.f, 0.f, 0.f};
constexpr Vector3<float> 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<float> start{0.f, 0.f, 0.f};
const Vector3<float> goal{static_cast<float>(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<float> start{0.f, 0.f, 0.f};
const Vector3<float> 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<float> a{0.f, 0.f, 0.f};
constexpr Vector3<float> b{1.f, 0.f, 0.f};
constexpr Vector3<float> 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);
}