Skip to content

Commit

Permalink
path_test: Improve failure messages
Browse files Browse the repository at this point in the history
  • Loading branch information
glebm committed Nov 24, 2024
1 parent 9e2180a commit f9a73a9
Showing 1 changed file with 97 additions and 28 deletions.
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

0 comments on commit f9a73a9

Please sign in to comment.