-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- 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
Showing
8 changed files
with
344 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.