mirror of
https://github.com/Astatin3/meteorbot-old.git
synced 2026-06-09 08:38:07 -06:00
710 lines
24 KiB
C++
710 lines
24 KiB
C++
#include "TestManager.hpp"
|
|
#include "MinecraftServer.hpp"
|
|
|
|
#include <catch2/catch_test_case_info.hpp>
|
|
|
|
#include <protocolCraft/Types/NBT/NBT.hpp>
|
|
|
|
#include <botcraft/Network/NetworkManager.hpp>
|
|
#include <botcraft/Game/Entities/EntityManager.hpp>
|
|
#include <botcraft/Game/Entities/LocalPlayer.hpp>
|
|
#include <botcraft/Utilities/Logger.hpp>
|
|
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <regex>
|
|
|
|
std::string ReplaceCharacters(const std::string& in, const std::vector<std::pair<char, std::string>>& replacements = { {'"', "\\\""}, {'\n', "\\n"} });
|
|
|
|
TestManager::TestManager()
|
|
{
|
|
current_offset = {spacing_x, 2, 2 * spacing_z };
|
|
current_test_index = 0;
|
|
bot_index = 0;
|
|
}
|
|
|
|
TestManager::~TestManager()
|
|
{
|
|
|
|
}
|
|
|
|
TestManager& TestManager::GetInstance()
|
|
{
|
|
static TestManager instance;
|
|
return instance;
|
|
}
|
|
|
|
const Botcraft::Position& TestManager::GetCurrentOffset() const
|
|
{
|
|
return current_offset;
|
|
}
|
|
|
|
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
|
void TestManager::SetBlock(const std::string& name, const Botcraft::Position& pos, const std::map<std::string, std::string>& blockstates, const std::map<std::string, std::string>& metadata, const bool escape_metadata) const
|
|
#else
|
|
void TestManager::SetBlock(const std::string& name, const Botcraft::Position& pos, const int block_variant, const std::map<std::string, std::string>& metadata, const bool escape_metadata) const
|
|
#endif
|
|
{
|
|
MakeSureLoaded(pos);
|
|
std::stringstream command;
|
|
command
|
|
<< "setblock" << " "
|
|
<< pos.x << " "
|
|
<< pos.y << " "
|
|
<< pos.z << " "
|
|
<< ((name.size() > 10 && name.substr(0, 10) == "minecraft:") ? "" : "minecraft:") << name;
|
|
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
|
if (!blockstates.empty())
|
|
{
|
|
command << "[";
|
|
int index = 0;
|
|
for (const auto& [k, v] : blockstates)
|
|
{
|
|
command << k << "=";
|
|
if (v.find(' ') != std::string::npos ||
|
|
v.find(',') != std::string::npos ||
|
|
v.find(':') != std::string::npos)
|
|
{
|
|
// Make sure the whole string is between quotes and all internal quotes are escaped
|
|
command << "\"" << ReplaceCharacters(v) << "\"";
|
|
}
|
|
else
|
|
{
|
|
command << v;
|
|
}
|
|
command << (index == blockstates.size() - 1 ? "" : ",");
|
|
index += 1;
|
|
}
|
|
command << "]";
|
|
}
|
|
#else
|
|
command << " "
|
|
<< block_variant << " "
|
|
<< "replace" << " ";
|
|
#endif
|
|
if (!metadata.empty())
|
|
{
|
|
command << "{";
|
|
int index = 0;
|
|
for (const auto& [k, v] : metadata)
|
|
{
|
|
command << k << ":";
|
|
if (escape_metadata &&
|
|
(v.find(' ') != std::string::npos ||
|
|
v.find(',') != std::string::npos ||
|
|
v.find(':') != std::string::npos)
|
|
)
|
|
{
|
|
// Make sure the whole string is between quotes and all internal quotes are escaped
|
|
command << "\"" << ReplaceCharacters(v) << "\"";
|
|
}
|
|
else
|
|
{
|
|
command << v;
|
|
}
|
|
command << (index == metadata.size() - 1 ? "" : ",");
|
|
index += 1;
|
|
}
|
|
command << "}";
|
|
}
|
|
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
|
command << " replace";
|
|
#endif
|
|
MinecraftServer::GetInstance().SendLine(command.str());
|
|
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
|
MinecraftServer::GetInstance().WaitLine(
|
|
".*: Changed the block at "
|
|
+ std::to_string(pos.x) + ", "
|
|
+ std::to_string(pos.y) + ", "
|
|
+ std::to_string(pos.z) + ".*", 5000);
|
|
#else
|
|
MinecraftServer::GetInstance().WaitLine(".*: Block placed.*", 5000);
|
|
#endif
|
|
}
|
|
|
|
void TestManager::CreateBook(const Botcraft::Position& pos, const std::vector<std::string>& pages, const std::string& facing, const std::string& title, const std::string& author, const std::vector<std::string>& description)
|
|
{
|
|
int facing_id = 0;
|
|
if (facing == "south")
|
|
{
|
|
facing_id = 0;
|
|
}
|
|
else if (facing == "west")
|
|
{
|
|
facing_id = 1;
|
|
}
|
|
else if (facing == "north")
|
|
{
|
|
facing_id = 2;
|
|
}
|
|
else if (facing == "east")
|
|
{
|
|
facing_id = 3;
|
|
}
|
|
|
|
std::stringstream command;
|
|
|
|
command
|
|
#if PROTOCOL_VERSION < 477 /* < 1.14 */
|
|
<< "summon" << " "
|
|
<< "minecraft:item_frame" << " "
|
|
#else
|
|
<< "setblock" << " "
|
|
#endif
|
|
<< pos.x << " "
|
|
<< pos.y << " "
|
|
<< pos.z << " "
|
|
#if PROTOCOL_VERSION < 477 /* < 1.14 */
|
|
<< "{Facing:" << facing_id << ","
|
|
<< "Item:{"
|
|
#else
|
|
<< "minecraft:lectern"
|
|
<< "[facing=" << facing << ",has_book=true]"
|
|
<< "{"
|
|
<< "Book:{"
|
|
#endif
|
|
<< "id:\"written_book\"" << ","
|
|
<< "Count:1" << ","
|
|
<< "tag:{"
|
|
<< "pages:[";
|
|
for (size_t i = 0; i < pages.size(); ++i)
|
|
{
|
|
command
|
|
<< "\"{"
|
|
<< "\\\"text\\\"" << ":" << "\\\"" << ReplaceCharacters(pages[i], { {'\n', "\\\\n"}, {'"', "\\\\\\\""} }) << "\\\""
|
|
<< "}\"" << ((i < pages.size() - 1) ? "," : "");
|
|
}
|
|
command << "]";
|
|
if (!title.empty())
|
|
{
|
|
command << ","
|
|
<< "title" << ":" << "\"" << ReplaceCharacters(title) << "\"";
|
|
}
|
|
if (!author.empty())
|
|
{
|
|
command << ","
|
|
<< "author" << ":" << "\"" << ReplaceCharacters(author) << "\"";
|
|
}
|
|
if (!description.empty())
|
|
{
|
|
command << "," << "display" << ":"
|
|
<< "{Lore:[";
|
|
for (size_t i = 0; i < description.size(); ++i)
|
|
{
|
|
command
|
|
<< "\""
|
|
#if PROTOCOL_VERSION < 735 /* < 1.16 */
|
|
// Just a list of strings is working before 1.16(?)
|
|
<< ReplaceCharacters(description[i])
|
|
#else
|
|
// After, a JSON text element is required
|
|
<< "{" << "\\\"text\\\"" << ":" << "\\\"" << ReplaceCharacters(description[i], { { '\n', "\\\\n"}, { '"', "\\\\\\\"" } }) << "\\\"" << "}"
|
|
#endif
|
|
<< "\"" << ((i < description.size() - 1) ? "," : "");
|
|
}
|
|
command << "]}";
|
|
}
|
|
command
|
|
<< "}" // tag
|
|
<< "}" // Item
|
|
<< "}"; // Main
|
|
|
|
MinecraftServer::GetInstance().SendLine(command.str());
|
|
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */ && PROTOCOL_VERSION < 477 /* < 1.14 */
|
|
MinecraftServer::GetInstance().WaitLine(".*?: Summoned new Item Frame.*", 5000);
|
|
#elif PROTOCOL_VERSION == 340 /* 1.12.2 */
|
|
MinecraftServer::GetInstance().WaitLine(".*?: Object successfully summoned.*", 5000);
|
|
#else
|
|
MinecraftServer::GetInstance().WaitLine(
|
|
".*: Changed the block at "
|
|
+ std::to_string(pos.x) + ", "
|
|
+ std::to_string(pos.y) + ", "
|
|
+ std::to_string(pos.z) + ".*", 5000);
|
|
#endif
|
|
}
|
|
|
|
void TestManager::Teleport(const std::string& name, const Botcraft::Vector3<double>& pos, const float yaw, const float pitch) const
|
|
{
|
|
std::stringstream command;
|
|
command
|
|
<< "teleport" << " "
|
|
<< name << " "
|
|
<< pos.x << " "
|
|
<< pos.y << " "
|
|
<< pos.z << " "
|
|
<< yaw << " "
|
|
<< pitch;
|
|
MinecraftServer::GetInstance().SendLine(command.str());
|
|
MinecraftServer::GetInstance().WaitLine(".*?Teleported " + name + " to.*", 5000);
|
|
}
|
|
|
|
Botcraft::Position TestManager::GetStructureSize(const std::string& filename) const
|
|
{
|
|
std::string no_space_filename = ReplaceCharacters(filename, { {' ', "_"} });
|
|
// If there is a # in the file name, only use what's before (useful to have multiple tests sharing the same structure file)
|
|
size_t split_index = no_space_filename.find('#');
|
|
if (split_index != std::string::npos)
|
|
{
|
|
no_space_filename = no_space_filename.substr(0, split_index);
|
|
}
|
|
const std::filesystem::path filepath = MinecraftServer::GetInstance().GetStructurePath() / (no_space_filename + ".nbt");
|
|
|
|
if (!std::filesystem::exists(filepath))
|
|
{
|
|
return GetStructureSize("_default");
|
|
}
|
|
std::ifstream file(filepath.string(), std::ios::in | std::ios::binary);
|
|
file.unsetf(std::ios::skipws);
|
|
|
|
ProtocolCraft::NBT::Value nbt;
|
|
file >> nbt;
|
|
file.close();
|
|
|
|
// TODO: deal with recursive structures (structures containing structure blocks) ?
|
|
return nbt["size"].as_list_of<int>();
|
|
}
|
|
|
|
void TestManager::CreateTPSign(const Botcraft::Position& src, const Botcraft::Vector3<double>& dst, const std::vector<std::string>& texts, const std::string& facing, const TestSucess success) const
|
|
{
|
|
Botcraft::Position offset_target;
|
|
int block_rotation = 0;
|
|
if (facing == "north")
|
|
{
|
|
block_rotation = 0;
|
|
offset_target = dst + Botcraft::Position(0, 0, -1);
|
|
}
|
|
else if (facing == "south")
|
|
{
|
|
block_rotation = 8;
|
|
offset_target = dst + Botcraft::Position(0, 0, 1);
|
|
}
|
|
else if (facing == "east")
|
|
{
|
|
block_rotation = 12;
|
|
offset_target = dst + Botcraft::Position(-1, 0, 0);
|
|
}
|
|
else if (facing == "west")
|
|
{
|
|
block_rotation = 4;
|
|
offset_target = dst + Botcraft::Position(1, 0, 0);
|
|
}
|
|
std::string text_color;
|
|
switch (success)
|
|
{
|
|
case TestSucess::None:
|
|
text_color = "black";
|
|
break;
|
|
case TestSucess::Success:
|
|
text_color = "dark_green";
|
|
break;
|
|
case TestSucess::Failure:
|
|
text_color = "red";
|
|
break;
|
|
case TestSucess::ExpectedFailure:
|
|
text_color = "gold";
|
|
break;
|
|
}
|
|
#if PROTOCOL_VERSION < 763 /* < 1.20 */
|
|
std::map<std::string, std::string> lines;
|
|
#else
|
|
std::vector<std::string> lines;
|
|
lines.reserve(4);
|
|
#endif
|
|
for (size_t i = 0; i < std::min(texts.size(), static_cast<size_t>(4)); ++i)
|
|
{
|
|
std::stringstream line;
|
|
line
|
|
<< "{"
|
|
<< "\"text\":\"" << texts[i] << "\"" << ",";
|
|
if (i == 0)
|
|
{
|
|
line
|
|
<< "\"underlined\"" << ":" << "false" << ","
|
|
<< "\"color\"" << ":" << "\"" << "black" << "\"" << ","
|
|
<< "\"clickEvent\"" << ":" << "{"
|
|
<< "\"action\"" << ":" << "\"run_command\"" << ","
|
|
<< "\"value\"" << ":" << "\""
|
|
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
|
<< "teleport @s" << " " << offset_target.x << " " << offset_target.y << " " << offset_target.z
|
|
<< " " << "facing" << " " << dst.x << " " << dst.y << " " << dst.z
|
|
#else
|
|
<< "teleport @s" << " " << dst.x << " " << dst.y << " " << dst.z
|
|
#endif
|
|
<< "\""
|
|
<< "}";
|
|
}
|
|
else
|
|
{
|
|
line
|
|
<< "\"underlined\"" << ":" << "true" << ","
|
|
<< "\"color\"" << ":" << "\"" << text_color << "\"";
|
|
}
|
|
line << "}";
|
|
#if PROTOCOL_VERSION < 763 /* < 1.20 */
|
|
lines.insert({ "Text" + std::to_string(i + 1), line.str() });
|
|
#else
|
|
lines.push_back(line.str());
|
|
#endif
|
|
}
|
|
// There is a bug in version 1.14 (and prereleases) that requires all 4 lines
|
|
// to be specified or the server can't read the text. See: https://bugs.mojang.com/browse/MC-144316
|
|
#if PROTOCOL_VERSION > 459 /* > 1.13.2 */ && PROTOCOL_VERSION < 478 /* < 1.14.1 */
|
|
for (size_t i = texts.size(); i < 4ULL; ++i)
|
|
{
|
|
lines.insert({ "Text" + std::to_string(i + 1), "{\"text\":\"\"}" });
|
|
}
|
|
#endif
|
|
#if PROTOCOL_VERSION > 762 /* > 1.19.4 */ // In 1.20 texts are sent as a list with exactly 4 elements
|
|
std::stringstream text_content;
|
|
text_content << "{\"messages\":[";
|
|
for (size_t i = 0; i < 4ULL; ++i)
|
|
{
|
|
if (i < lines.size())
|
|
{
|
|
text_content << "\"" << ReplaceCharacters(lines[i]) << "\"";
|
|
}
|
|
else
|
|
{
|
|
text_content << "\"" << ReplaceCharacters("{\"text\":\"\"}") << "\"";
|
|
}
|
|
if (i != 3ULL)
|
|
{
|
|
text_content << ",";
|
|
}
|
|
}
|
|
text_content << "]}";
|
|
const std::string text_content_str = text_content.str();
|
|
#endif
|
|
|
|
SetBlock(
|
|
#if PROTOCOL_VERSION < 393 /* < 1.13 */
|
|
"standing_sign",
|
|
#elif PROTOCOL_VERSION < 477 /* < 1.14 */
|
|
"sign",
|
|
#else
|
|
"oak_sign",
|
|
#endif
|
|
src,
|
|
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
|
{
|
|
{ "rotation", std::to_string(block_rotation) }
|
|
},
|
|
#else
|
|
block_rotation,
|
|
#endif
|
|
#if PROTOCOL_VERSION < 763 /* < 1.20 */
|
|
lines
|
|
#else
|
|
{
|
|
{ "is_waxed", "true" },
|
|
{ "front_text", text_content_str },
|
|
{ "back_text", text_content_str },
|
|
},
|
|
false
|
|
#endif
|
|
);
|
|
}
|
|
|
|
void TestManager::LoadStructure(const std::string& filename, const Botcraft::Position& pos, const Botcraft::Position& load_offset) const
|
|
{
|
|
std::string no_space_filename = ReplaceCharacters(filename, { {' ', "_"} });
|
|
// If there is a # in the file name, only use what's before (useful to have multiple tests sharing the same structure file)
|
|
size_t split_index = no_space_filename.find('#');
|
|
if (split_index != std::string::npos)
|
|
{
|
|
no_space_filename = no_space_filename.substr(0, split_index);
|
|
}
|
|
const std::string& loaded = std::filesystem::exists(MinecraftServer::GetInstance().GetStructurePath() / (no_space_filename + ".nbt")) ?
|
|
no_space_filename :
|
|
"_default";
|
|
|
|
SetBlock(
|
|
"structure_block",
|
|
pos,
|
|
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
|
{},
|
|
#else
|
|
0,
|
|
#endif
|
|
{
|
|
{"mode", "LOAD"},
|
|
{"name", loaded},
|
|
{"posX", std::to_string(load_offset.x)},
|
|
{"posY", std::to_string(load_offset.y)},
|
|
{"posZ", std::to_string(load_offset.z)},
|
|
{"showboundingbox", "1"}
|
|
}
|
|
);
|
|
SetBlock("redstone_block", pos + Botcraft::Position(0, 1, 0));
|
|
}
|
|
|
|
void TestManager::MakeSureLoaded(const Botcraft::Position& pos) const
|
|
{
|
|
std::shared_ptr<Botcraft::World> world = chunk_loader->GetWorld();
|
|
if (chunk_loader->GetWorld()->IsLoaded(pos))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Teleport(chunk_loader_name, pos);
|
|
|
|
// Wait for bot to load center and corner view_distance blocks
|
|
const int chunk_x = static_cast<int>(std::floor(pos.x / static_cast<double>(Botcraft::CHUNK_WIDTH)));
|
|
const int chunk_z = static_cast<int>(std::floor(pos.z / static_cast<double>(Botcraft::CHUNK_WIDTH)));
|
|
// -1 because sometimes corner chunks are not sent
|
|
const int view_distance = MinecraftServer::options.view_distance - 1;
|
|
std::vector<std::pair<int, int>> wait_loaded = {
|
|
{chunk_x * Botcraft::CHUNK_WIDTH, chunk_z * Botcraft::CHUNK_WIDTH},
|
|
{(chunk_x + view_distance) * Botcraft::CHUNK_WIDTH - 1, (chunk_z + view_distance) * Botcraft::CHUNK_WIDTH - 1},
|
|
{(chunk_x - view_distance) * Botcraft::CHUNK_WIDTH, (chunk_z + view_distance) * Botcraft::CHUNK_WIDTH - 1},
|
|
{(chunk_x + view_distance) * Botcraft::CHUNK_WIDTH - 1, (chunk_z - view_distance) * Botcraft::CHUNK_WIDTH},
|
|
{(chunk_x - view_distance) * Botcraft::CHUNK_WIDTH, (chunk_z - view_distance) * Botcraft::CHUNK_WIDTH}
|
|
};
|
|
|
|
if (!Botcraft::Utilities::WaitForCondition([&]()
|
|
{
|
|
for (size_t i = 0; i < wait_loaded.size(); ++i)
|
|
{
|
|
if (!world->IsLoaded(Botcraft::Position(wait_loaded[i].first, pos.y, wait_loaded[i].second)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}, 15000))
|
|
{
|
|
LOG_ERROR("Timeout waiting " << chunk_loader_name << " to load surroundings from " << chunk_loader->GetLocalPlayer()->GetPosition());
|
|
for (size_t i = 0; i < wait_loaded.size(); ++i)
|
|
{
|
|
const Botcraft::Position pos(wait_loaded[i].first, pos.y, wait_loaded[i].second);
|
|
LOG_ERROR(pos << " is" << (world->IsLoaded(pos) ? "" : " not") << " loaded");
|
|
}
|
|
std::stringstream loaded;
|
|
loaded << "\n";
|
|
for (const auto& c : *world->GetChunks())
|
|
{
|
|
loaded << c.first.first << "\t" << c.first.second << ";";
|
|
}
|
|
LOG_ERROR("Loaded chunks: " << loaded.str());
|
|
throw std::runtime_error("Timeout waiting " + chunk_loader_name + " to load surroundings");
|
|
}
|
|
}
|
|
|
|
void TestManager::SetGameMode(const std::string& name, const Botcraft::GameType gamemode) const
|
|
{
|
|
std::string gamemode_string;
|
|
switch (gamemode)
|
|
{
|
|
case Botcraft::GameType::Survival:
|
|
gamemode_string = "survival";
|
|
break;
|
|
case Botcraft::GameType::Creative:
|
|
gamemode_string = "creative";
|
|
break;
|
|
case Botcraft::GameType::Adventure:
|
|
gamemode_string = "adventure";
|
|
break;
|
|
case Botcraft::GameType::Spectator:
|
|
gamemode_string = "spectator";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
std::stringstream command;
|
|
command
|
|
<< "gamemode" << " "
|
|
<< gamemode_string << " "
|
|
<< name;
|
|
MinecraftServer::GetInstance().SendLine(command.str());
|
|
MinecraftServer::GetInstance().WaitLine(".*? Set " + name + "'s game mode to.*", 5000);
|
|
}
|
|
|
|
|
|
void TestManager::testRunStarting(Catch::TestRunInfo const& test_run_info)
|
|
{
|
|
// Make sure the server is running and ready before the first test run
|
|
MinecraftServer::GetInstance().Initialize();
|
|
// Retrieve header size
|
|
header_size = GetStructureSize("_header_running");
|
|
chunk_loader = GetBot(chunk_loader_name, Botcraft::GameType::Spectator);
|
|
}
|
|
|
|
void TestManager::testCaseStarting(Catch::TestCaseInfo const& test_info)
|
|
{
|
|
// Retrieve test structure size
|
|
current_test_size = GetStructureSize(test_info.name);
|
|
LoadStructure("_header_running", Botcraft::Position(current_offset.x, 0, spacing_z - header_size.z));
|
|
current_offset.z = 2 * spacing_z;
|
|
}
|
|
|
|
void TestManager::testCasePartialStarting(Catch::TestCaseInfo const& test_info, uint64_t part_number)
|
|
{
|
|
// Load header
|
|
current_header_position = current_offset - Botcraft::Position(0, 2, 0);
|
|
LoadStructure("_header_running", current_header_position);
|
|
current_offset.z += header_size.z;
|
|
// Load test structure
|
|
LoadStructure(test_info.name, current_offset + Botcraft::Position(0, -1, 0), Botcraft::Position(0, 1, 0));
|
|
current_test_case_failures.clear();
|
|
section_stack = { std::filesystem::path(test_info.lineInfo.file).stem().string() };
|
|
}
|
|
|
|
void TestManager::sectionStarting(Catch::SectionInfo const& section_info)
|
|
{
|
|
section_stack.push_back(section_info.name);
|
|
}
|
|
|
|
void TestManager::assertionEnded(Catch::AssertionStats const& assertion_stats)
|
|
{
|
|
if (!assertion_stats.assertionResult.succeeded())
|
|
{
|
|
const std::filesystem::path file_path = std::filesystem::relative(assertion_stats.assertionResult.getSourceInfo().file, BASE_SOURCE_DIR);
|
|
current_test_case_failures.push_back(
|
|
ReplaceCharacters(file_path.string(), {{'\\', "/"}}) + ":" +
|
|
std::to_string(assertion_stats.assertionResult.getSourceInfo().line) + "\n\n" +
|
|
assertion_stats.assertionResult.getExpressionInMacro() + "\n\n"
|
|
"with expansion:" + "\n" +
|
|
assertion_stats.assertionResult.getExpandedExpression()
|
|
);
|
|
}
|
|
}
|
|
|
|
void TestManager::testCasePartialEnded(Catch::TestCaseStats const& test_case_stats, uint64_t part_number)
|
|
{
|
|
const bool passed = test_case_stats.totals.assertions.allPassed();
|
|
// Replace header with proper test result
|
|
LoadStructure(passed ? "_header_success" : (test_case_stats.testInfo->okToFail() ? "_header_expected_fail" : "_header_fail"), current_header_position);
|
|
// Create TP sign for the partial that just ended
|
|
CreateTPSign(
|
|
Botcraft::Position(-2 * (current_test_index + 1), 2, -2 * part_number - 5),
|
|
Botcraft::Vector3(current_offset.x, 2, current_offset.z - 1),
|
|
section_stack, "north", passed ? TestSucess::Success : (test_case_stats.testInfo->okToFail() ? TestSucess::ExpectedFailure : TestSucess::Failure)
|
|
);
|
|
// Create back to spawn sign for the section that just ended
|
|
CreateTPSign(
|
|
Botcraft::Position(current_offset.x, 2, current_offset.z - 1),
|
|
Botcraft::Vector3(-2 * (current_test_index + 1), 2, -2 * static_cast<int>(part_number) - 5),
|
|
section_stack, "south", passed ? TestSucess::Success : (test_case_stats.testInfo->okToFail() ? TestSucess::ExpectedFailure : TestSucess::Failure)
|
|
);
|
|
if (!passed)
|
|
{
|
|
CreateBook(
|
|
Botcraft::Position(current_offset.x + 1, 2, current_offset.z - header_size.z),
|
|
current_test_case_failures,
|
|
"north",
|
|
test_case_stats.testInfo->name + "#" + std::to_string(part_number),
|
|
"Botcraft Test Framework",
|
|
section_stack
|
|
);
|
|
}
|
|
current_offset.z += current_test_size.z + spacing_z;
|
|
|
|
// Kill all items that could exist on the floor
|
|
MinecraftServer::GetInstance().SendLine("kill @e[type=item]");
|
|
#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
|
|
// In 1.12.2 server sends one line per entity killed so we can't wait without knowing
|
|
// how many there were. Just assume the command worked
|
|
MinecraftServer::GetInstance().WaitLine(".*?: (?:Killed|No entity was found).*", 5000);
|
|
#endif
|
|
}
|
|
|
|
void TestManager::testCaseEnded(Catch::TestCaseStats const& test_case_stats)
|
|
{
|
|
const bool passed = test_case_stats.totals.assertions.allPassed();
|
|
LoadStructure(passed ? "_header_success" : (test_case_stats.testInfo->okToFail() ? "_header_expected_fail" : "_header_fail"), Botcraft::Position(current_offset.x, 0, spacing_z - header_size.z));
|
|
// Create Sign to TP to current test
|
|
CreateTPSign(
|
|
Botcraft::Position(-2 * (current_test_index + 1), 2, -2),
|
|
Botcraft::Vector3(current_offset.x, 2, spacing_z - 1),
|
|
{ std::filesystem::path(test_case_stats.testInfo->lineInfo.file).stem().string(), test_case_stats.testInfo->name },
|
|
"north", passed ? TestSucess::Success : (test_case_stats.testInfo->okToFail() ? TestSucess::ExpectedFailure : TestSucess::Failure)
|
|
);
|
|
// Create sign to TP to TP back to spawn
|
|
CreateTPSign(
|
|
Botcraft::Position(current_offset.x, 2, spacing_z - 1),
|
|
Botcraft::Vector3(-2 * (current_test_index + 1), 2, -2),
|
|
{ std::filesystem::path(test_case_stats.testInfo->lineInfo.file).stem().string(), test_case_stats.testInfo->name },
|
|
"south", passed ? TestSucess::Success : (test_case_stats.testInfo->okToFail() ? TestSucess::ExpectedFailure : TestSucess::Failure)
|
|
);
|
|
current_test_index += 1;
|
|
current_offset.x += std::max(current_test_size.x, header_size.x) + spacing_x;
|
|
}
|
|
|
|
void TestManager::testRunEnded(Catch::TestRunStats const& test_run_info)
|
|
{
|
|
if (chunk_loader)
|
|
{
|
|
chunk_loader->Disconnect();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestManagerListener::testRunStarting(Catch::TestRunInfo const& test_run_info)
|
|
{
|
|
TestManager::GetInstance().testRunStarting(test_run_info);
|
|
}
|
|
|
|
void TestManagerListener::testCaseStarting(Catch::TestCaseInfo const& test_info)
|
|
{
|
|
TestManager::GetInstance().testCaseStarting(test_info);
|
|
}
|
|
|
|
void TestManagerListener::testCasePartialStarting(Catch::TestCaseInfo const& test_info, uint64_t part_number)
|
|
{
|
|
TestManager::GetInstance().testCasePartialStarting(test_info, part_number);
|
|
}
|
|
|
|
void TestManagerListener::sectionStarting(Catch::SectionInfo const& section_info)
|
|
{
|
|
TestManager::GetInstance().sectionStarting(section_info);
|
|
}
|
|
|
|
void TestManagerListener::assertionEnded(Catch::AssertionStats const& assertion_stats)
|
|
{
|
|
TestManager::GetInstance().assertionEnded(assertion_stats);
|
|
}
|
|
|
|
void TestManagerListener::testCasePartialEnded(Catch::TestCaseStats const& test_case_stats, uint64_t part_number)
|
|
{
|
|
TestManager::GetInstance().testCasePartialEnded(test_case_stats, part_number);
|
|
}
|
|
|
|
void TestManagerListener::testCaseEnded(Catch::TestCaseStats const& test_case_stats)
|
|
{
|
|
TestManager::GetInstance().testCaseEnded(test_case_stats);
|
|
}
|
|
|
|
void TestManagerListener::testRunEnded(Catch::TestRunStats const& test_run_info)
|
|
{
|
|
TestManager::GetInstance().testRunEnded(test_run_info);
|
|
}
|
|
CATCH_REGISTER_LISTENER(TestManagerListener)
|
|
|
|
std::string ReplaceCharacters(const std::string& in, const std::vector<std::pair<char, std::string>>& replacements)
|
|
{
|
|
std::string output;
|
|
output.reserve(in.size());
|
|
|
|
for (size_t i = 0; i < in.size(); ++i)
|
|
{
|
|
bool found = false;
|
|
for (size_t j = 0; j < replacements.size(); ++j)
|
|
{
|
|
if (replacements[j].first == in[i])
|
|
{
|
|
output += replacements[j].second;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
output += in[i];
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|