Skip to content

Commit

Permalink
Fixed items not distributed 🙄
Browse files Browse the repository at this point in the history
- Added another Test unit for distribution.
- Added small test for items distribution
- Improved Testing framework to support beforeAll, afterAll, beforeEach and afterEach.
  • Loading branch information
adya committed Jan 15, 2025
1 parent 73aa68f commit b896bea
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 149 deletions.
1 change: 1 addition & 0 deletions SPID/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ target_include_directories(
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/Testing
${SRELL_INCLUDE_DIRS}
${MERGEMAPPER_INCLUDE_DIRS}
${CLIB_UTIL_INCLUDE_DIRS}
Expand Down
142 changes: 142 additions & 0 deletions SPID/Testing/DistributionTests.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#pragma once
#include "DistributeManager.h"
#include "FormData.h"
#include "Testing.h"

namespace Distribute
{
using namespace Testing;

struct TestsHelper : ISingleton<TestsHelper>
{
static RE::Actor* GetActor()
{
auto actor = GetForm<RE::Actor>(0x198B0);
return actor;
}

static RE::TESBoundObject* GetItem()
{
auto item = GetForm<RE::TESBoundObject>(0x139B8); // Daedric mace
return item;
}

static void Distribute(RE::Actor* actor)
{
detail::distribute_on_load(actor, actor->GetActorBase());
}

static int GetItemCount(RE::Actor* actor, RE::TESBoundObject* item)
{
int itemsCount = actor->GetActorBase()->CountObjectsInContainer(item);
if (const auto invChanges = actor->GetInventoryChanges()) {
if (const auto entryLists = invChanges->entryList) {
for (const auto& entryList : *entryLists) {
if (entryList && entryList->object && entryList->object->formID == item->formID) {
itemsCount += entryList->countDelta;
}
}
}
}
return itemsCount;
}



static void ClearConfigs()
{
Forms::spells.GetForms().clear();
Forms::perks.GetForms().clear();
Forms::items.GetForms().clear();
Forms::shouts.GetForms().clear();
Forms::levSpells.GetForms().clear();
Forms::packages.GetForms().clear();
Forms::outfits.GetForms().clear();
Forms::keywords.GetForms().clear();
Forms::factions.GetForms().clear();
Forms::sleepOutfits.GetForms().clear();
Forms::skins.GetForms().clear();
}

static void SnapshotConfigs()
{
GetSingleton()->spells = Forms::spells;
GetSingleton()->perks = Forms::perks;
GetSingleton()->items = Forms::items;
GetSingleton()->shouts = Forms::shouts;
GetSingleton()->levSpells = Forms::levSpells;
GetSingleton()->packages = Forms::packages;
GetSingleton()->outfits = Forms::outfits;
GetSingleton()->keywords = Forms::keywords;
GetSingleton()->factions = Forms::factions;
GetSingleton()->sleepOutfits = Forms::sleepOutfits;
GetSingleton()->skins = Forms::skins;
}

static void RestoreConfigs()
{
Forms::spells = GetSingleton()->spells;
Forms::perks = GetSingleton()->perks;
Forms::items = GetSingleton()->items;
Forms::shouts = GetSingleton()->shouts;
Forms::levSpells = GetSingleton()->levSpells;
Forms::packages = GetSingleton()->packages;
Forms::outfits = GetSingleton()->outfits;
Forms::keywords = GetSingleton()->keywords;
Forms::factions = GetSingleton()->factions;
Forms::sleepOutfits = GetSingleton()->sleepOutfits;
Forms::skins = GetSingleton()->skins;
}

private:
Forms::Distributables<RE::SpellItem> spells{ RECORD::kSpell };
Forms::Distributables<RE::BGSPerk> perks{ RECORD::kPerk };
Forms::Distributables<RE::TESBoundObject> items{ RECORD::kItem };
Forms::Distributables<RE::TESShout> shouts{ RECORD::kShout };
Forms::Distributables<RE::TESLevSpell> levSpells{ RECORD::kLevSpell };
Forms::Distributables<RE::TESForm> packages{ RECORD::kPackage };
Forms::Distributables<RE::BGSOutfit> outfits{ RECORD::kOutfit };
Forms::Distributables<RE::BGSKeyword> keywords{ RECORD::kKeyword };
Forms::Distributables<RE::TESFaction> factions{ RECORD::kFaction };
Forms::Distributables<RE::BGSOutfit> sleepOutfits{ RECORD::kSleepOutfit };
Forms::Distributables<RE::TESObjectARMO> skins{ RECORD::kSkin };
};

namespace Testing
{
namespace Items
{
constexpr static const char* moduleName = "Distribute.Items";

BEFORE_ALL
{
TestsHelper::SnapshotConfigs();
}

AFTER_ALL
{
TestsHelper::RestoreConfigs();
}

BEFORE_EACH
{
TestsHelper::ClearConfigs();
}

TEST(AddItemToActor)
{
auto actor{ TestsHelper::GetActor() };
auto item{ TestsHelper::GetItem() };
FilterData filterData{ {}, {}, {}, {}, 100 };
RandomCount idxOrCount{ 1,1 };
bool isFinal{ false };
Path path{ "" };
// void EmplaceForm(bool isValid, Form*, const bool& isFinal, const IndexOrCount&, const FilterData&, const Path&);
Forms::items.EmplaceForm(true, item, isFinal, idxOrCount, filterData, path);
TestsHelper::Distribute(actor);
auto got = TestsHelper::GetItemCount(actor, item);
EXPECT(got == 1, fmt::format("Expected actor to have 1 item, but they have {}", got));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ namespace Outfits
{
constexpr static const char* moduleName = "OutfitManager.RegularDistribution.Dead";

CLEANUP
AFTER_ALL
{
TestsHelper::GetAlive(); // resurrect the actor that we used for tests, to reset their inventory
}
Expand Down Expand Up @@ -803,7 +803,7 @@ namespace Outfits
{
constexpr static const char* moduleName = "OutfitManager.DeathDistribution.Dead";

CLEANUP
AFTER_ALL
{
TestsHelper::GetAlive(); // resurrect the actor that we used for tests, to reset their inventory
}
Expand Down Expand Up @@ -1132,7 +1132,7 @@ namespace Outfits
{
constexpr static const char* moduleName = "OutfitManager.MixedDistribution.Dead";

CLEANUP
AFTER_ALL
{
TestsHelper::GetAlive(); // resurrect the actor that we used for tests, to reset their inventory
}
Expand Down Expand Up @@ -1197,7 +1197,7 @@ namespace Outfits
{
constexpr static const char* moduleName = "OutfitManager.Resurrection";

CLEANUP
AFTER_ALL
{
TestsHelper::GetAlive(); // resurrect the actor that we used for tests, to reset their inventory
}
Expand Down
191 changes: 191 additions & 0 deletions SPID/Testing/Testing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#pragma once

namespace Testing
{
class TestResult
{
public:
static TestResult Success(std::string testName = __builtin_FUNCTION()) { return { true, "passed", testName }; }
static TestResult Fail(std::string message = "", std::string testName = __builtin_FUNCTION()) { return { false, message, testName }; }

bool success;
std::string message;
std::string testName;
};
}

template <>
struct fmt::formatter<Testing::TestResult>
{
template <class ParseContext>
constexpr auto parse(ParseContext& ctx)
{
return ctx.begin();
}
template <class FormatContext>
constexpr auto format(const Testing::TestResult& result, FormatContext& a_ctx) const
{
if (result.success) {
return fmt::format_to(a_ctx.out(), "✅ {}", result.testName);
} else {
if (result.message.empty()) {
return fmt::format_to(a_ctx.out(), "❌ {}", result.testName);
}
return fmt::format_to(a_ctx.out(), "❌ {}: {}", result.testName, result.message);
}
}
};

namespace Testing
{
class Runner : public ISingleton<Runner>
{
private:
using Code = std::function<void()>;
using Test = std::function<TestResult()>;
using TestSuite = std::map<std::string, Test>;
using TestModule = std::map<std::string, TestSuite>;

TestModule tests;

std::map<std::string, Code> beforeAll;
std::map<std::string, Code> afterAll;

std::map<std::string, Code> beforeEach;
std::map<std::string, Code> afterEach;

static std::pair<int, int> RunModule(std::string moduleName, TestSuite& tests)
{
int total = 0;
int success = 0;

Code before = [] {}; // no-op
Code after = [] {}; // no-op

if (const auto& beforeEach = GetSingleton()->beforeEach.find(moduleName); beforeEach != GetSingleton()->beforeEach.end()) {
before = beforeEach->second;
}

if (const auto& afterEach = GetSingleton()->afterEach.find(moduleName); afterEach != GetSingleton()->afterEach.end()) {
after = afterEach->second;
}

logger::critical("Running {} tests:", moduleName);
for (auto& test : tests) {
before();
auto result = test.second();
after();
logger::critical("\t{}", result);
if (result.success) {
success++;
}
total++;
}

logger::critical("Completed {}: {}/{} tests passed", moduleName, success, total);
return { success, total };
}

public:
static bool RegisterTest(const char* moduleName, const char* testName, Test test)
{
auto runner = GetSingleton();
auto& module = runner->tests[moduleName];
return module.try_emplace(testName, test).second;
}

static bool RegisterAfterEach(const char* moduleName, Code code)
{
return GetSingleton()->afterEach.try_emplace(moduleName, code).second;
}

static bool RegisterBeforeEach(const char* moduleName, Code code)
{
return GetSingleton()->beforeEach.try_emplace(moduleName, code).second;
}

static bool RegisterAfterAll(const char* moduleName, Code code)
{
return GetSingleton()->afterAll.try_emplace(moduleName, code).second;
}

static bool RegisterBeforeAll(const char* moduleName, Code code)
{
return GetSingleton()->beforeAll.try_emplace(moduleName, code).second;
}

static void Run()
{
LOG_HEADER("SELF TESTING");
std::pair<int, int> counter = { 0, 0 };
spdlog::set_level(spdlog::level::critical); // silence all logging coming from the test.
for (auto& [moduleName, tests] : GetSingleton()->tests) {
if (const auto& before = GetSingleton()->beforeAll.find(moduleName); before != GetSingleton()->beforeAll.end()) {
before->second();
}
auto res = RunModule(moduleName, tests);
if (const auto& after = GetSingleton()->afterAll.find(moduleName); after != GetSingleton()->afterAll.end()) {
after->second();
}

counter.first += res.first;
counter.second += res.second;
}
spdlog::set_level(spdlog::level::info); // restore logging level
if (GetSingleton()->tests.size() > 1) {
logger::info("Completed all tests: {}/{} tests passed", counter.first, counter.second);
}

if (counter.first != counter.second) {
logger::info("No Skyrim for you! Fix Tests first! 😀");
abort();
}
}
};

inline void Run() { Runner::Run(); }

template <typename T>
inline T* GetForm(RE::FormID a_formID)
{
auto form = RE::TESForm::LookupByID<T>(a_formID);
assert(form);
return form;
}
}

#define BEFORE_EACH \
inline void beforeEachTests(); \
static bool beforeEachTests_registered = ::Testing::Runner::RegisterBeforeEach(moduleName, beforeEachTests); \
inline void beforeEachTests()

#define AFTER_EACH \
inline void afterEachTests(); \
static bool afterEachTests_registered = ::Testing::Runner::RegisterAfterEach(moduleName, afterEachTests); \
inline void afterEachTests()

#define BEFORE_ALL \
inline void beforeAllTests(); \
static bool beforeAllTests_registered = ::Testing::Runner::RegisterBeforeAll(moduleName, beforeAllTests); \
inline void beforeAllTests()

#define AFTER_ALL \
inline void afterAllTests(); \
static bool afterAllTests_registered = ::Testing::Runner::RegisterAfterAll(moduleName, afterAllTests); \
inline void afterAllTests()

#define TEST(name) \
inline ::Testing::TestResult test##name(); \
static bool test##name##_registered = ::Testing::Runner::RegisterTest(moduleName, #name, test##name); \
inline ::Testing::TestResult test##name##()

#define PASS return ::Testing::TestResult::Success();

#define FAIL(msg) return ::Testing::TestResult::Fail(msg);

#define ASSERT(expr, msg) \
if (!(expr)) \
return ::Testing::TestResult::Fail(msg);

#define EXPECT(expr, msg) \
return (expr) ? ::Testing::TestResult::Success() : ::Testing::TestResult::Fail(msg);
5 changes: 3 additions & 2 deletions SPID/cmake/headerlist.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ set(headers ${headers}
include/LookupForms.h
include/LookupNPC.h
include/OutfitManager.h
include/OutfitManagerTests.h
Testing/OutfitManagerTests.h
include/PCH.h
include/PCLevelMultManager.h
include/Parser.h
include/Testing.h
Testing/Testing.h
Testing/DistributionTests.h
)
Loading

0 comments on commit b896bea

Please sign in to comment.