Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

path_test: Improve failure messages #7611

Merged
merged 1 commit into from
Dec 30, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 97 additions & 28 deletions test/path_test.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
#include <gtest/gtest.h>

#include "engine/path.h"

// The following headers are included to access globals used in functions that have not been isolated yet.
#include <algorithm>
#include <span>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "engine/direction.hpp"
#include "levels/dun_tile.hpp"
#include "levels/gendung.h"
#include "objects.h"
#include "utils/algorithm/container.hpp"

namespace devilution {

extern int TestPathGetHeuristicCost(Point startPosition, Point destinationPosition);

namespace {

using ::testing::ElementsAreArray;

TEST(PathTest, Heuristics)
{
constexpr Point source { 25, 32 };
Expand Down Expand Up @@ -101,40 +111,97 @@ TEST(PathTest, CanStepTest)
dPiece[1][1] = 0;
}

void CheckPath(Point startPosition, Point destinationPosition, std::vector<int8_t> expectedSteps)
// These symbols are in terms of coordinates (not in terms of on-screen direction).
// -1, -1 is top-left.
enum class Dir {
None,
Up,
Left,
Right,
Down,
UpLeft,
UpRight,
DownRight,
DownLeft
};
std::array<std::string_view, 9> DirSymbols = { "∅", "↑", "←", "→", "↓", "↖", "↗", "↘", "↙" };

std::ostream &operator<<(std::ostream &os, Dir dir)
{
static int8_t pathSteps[MaxPathLength];
auto pathLength = FindPath([](Point) { return true; }, startPosition, destinationPosition, pathSteps);
return os << DirSymbols[static_cast<size_t>(dir)];
}

EXPECT_EQ(pathLength, expectedSteps.size()) << "Wrong path length for a path from " << startPosition << " to " << destinationPosition;
// Die early if the wrong path length is returned as we don't want to read oob in expectedSteps
ASSERT_LE(pathLength, expectedSteps.size()) << "Path is longer than expected.";
std::vector<Dir> ToSyms(std::span<const std::string> strings)
{
std::vector<Dir> result;
result.reserve(strings.size());
for (const std::string &str : strings)
result.emplace_back(static_cast<Dir>(std::distance(DirSymbols.begin(), c_find(DirSymbols, str))));
return result;
}

for (int i = 0; i < pathLength; i++) {
EXPECT_EQ(pathSteps[i], expectedSteps[i]) << "Path step " << i << " differs from expectation for a path from "
<< startPosition << " to " << destinationPosition; // this shouldn't be a requirement but...
std::vector<Dir> ToSyms(std::span<const int8_t> indices)
{
std::vector<Dir> result;
result.reserve(indices.size());
for (const int8_t idx : indices)
result.emplace_back(static_cast<Dir>(idx));
return result;
}

// Path directions are all jacked up compared to the Direction enum. Most consumers have their own mapping definition
// startPosition += Direction { path[i] - 1 };
}
// Given that we can't really make any assumptions about how the path is actually used.
// EXPECT_EQ(startPosition, destinationPosition) << "Path doesn't lead to destination";
void CheckPath(Point startPosition, Point destinationPosition, std::vector<std::string> expectedSteps)
{
int8_t pathSteps[MaxPathLength];
auto pathLength = FindPath([](Point) { return true; }, startPosition, destinationPosition, pathSteps);
EXPECT_THAT(ToSyms(std::span<const int8_t>(pathSteps, pathLength)), ElementsAreArray(ToSyms(expectedSteps)))
<< "Path steps differs from expectation for a path from "
<< startPosition << " to " << destinationPosition;
}

TEST(PathTest, FindPath)
TEST(PathTest, FindPathToSelf)
{
CheckPath({ 8, 8 }, { 8, 8 }, {});
}

// Traveling in cardinal directions is the only way to get a first step in a cardinal direction
CheckPath({ 8, 8 }, { 8, 6 }, { 1, 1 });
CheckPath({ 8, 8 }, { 6, 8 }, { 2, 2 });
CheckPath({ 8, 8 }, { 10, 8 }, { 3, 3 });
CheckPath({ 8, 8 }, { 8, 10 }, { 4, 4 });
TEST(PathTest, FindPathTwoStepsUp)
{
CheckPath({ 8, 8 }, { 8, 6 }, { "↑", "↑" });
}

// Otherwise pathing biases along diagonals and the diagonal steps will always be first
CheckPath({ 8, 8 }, { 5, 6 }, { 5, 5, 2 });
CheckPath({ 8, 8 }, { 4, 4 }, { 5, 5, 5, 5 });
CheckPath({ 8, 8 }, { 12, 20 }, { 7, 7, 7, 7, 4, 4, 4, 4, 4, 4, 4, 4 });
TEST(PathTest, FindPathTwoStepsLeft)
{
CheckPath({ 8, 8 }, { 6, 8 }, { "←", "←" });
}

TEST(PathTest, FindPathTwoStepsRight)
{
CheckPath({ 8, 8 }, { 10, 8 }, { "→", "→" });
}

TEST(PathTest, FindPathTwoStepsDown)
{
CheckPath({ 8, 8 }, { 8, 10 }, { "↓", "↓" });
}

TEST(PathTest, FindPathDiagonalsFirst3Left2Up)
{
// Pathing biases along diagonals and the diagonal steps will always be first
CheckPath({ 8, 8 }, { 5, 6 }, { "↖", "↖", "←" });
}

TEST(PathTest, FindPathDiagonalsFirst4Left4Up)
{
CheckPath({ 8, 8 }, { 4, 4 }, { "↖", "↖", "↖", "↖" });
}

TEST(PathTest, FindPathDiagonalsFirst2Right4Down)
{
CheckPath({ 8, 8 }, { 10, 12 }, { "↘", "↘", "↓", "↓" });
}

TEST(PathTest, FindPathDiagonalsFirst4Right12Down)
{
CheckPath({ 8, 8 }, { 12, 20 }, { "↘", "↘", "↘", "↘", "↓", "↓", "↓", "↓", "↓", "↓", "↓", "↓" });
}

TEST(PathTest, LongPaths)
Expand All @@ -144,7 +211,7 @@ TEST(PathTest, LongPaths)

// Longest possible path is currently 24 steps meaning tiles 24 units away are reachable
Point startingPosition { 56, 56 };
CheckPath(startingPosition, startingPosition + Displacement { 24, 24 }, std::vector<int8_t>(24, 7));
CheckPath(startingPosition, startingPosition + Displacement { 24, 24 }, std::vector<std::string>(24, "↘"));

// But trying to navigate 25 units fails
CheckPath(startingPosition, startingPosition + Displacement { 25, 25 }, {});
Expand Down Expand Up @@ -285,4 +352,6 @@ TEST(PathTest, FindClosest)
EXPECT_EQ(*nearPosition, (Point { 50, 50 } + Displacement { 0, 21 })) << "First candidate position with a minimum radius should be at {0, +y}";
}
}

} // namespace
} // namespace devilution
Loading