From a69457a5e0cadac739836a59b43e562cd4bd08d1 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Thu, 20 Aug 2026 21:54:05 -0500 Subject: [PATCH 1/3] add StatsReader tests --- .../PokemonFRLG_BattleLevelUpReader.cpp | 41 +++++ .../PokemonFRLG_BattleLevelUpReader.h | 4 + .../PokemonFRLG_PartyLevelUpReader.cpp | 41 +++++ .../PokemonFRLG_PartyLevelUpReader.h | 4 + .../Inference/PokemonFRLG_StatsReader.cpp | 143 ++++++++++++++++++ .../Inference/PokemonFRLG_StatsReader.h | 4 + .../Inference/PokemonFRLG_TrainerIdReader.cpp | 49 ++++++ .../Inference/PokemonFRLG_TrainerIdReader.h | 4 + .../PokemonFRLG_WildEncounterReader.cpp | 62 +++++++- .../PokemonFRLG_WildEncounterReader.h | 4 + .../Source/PokemonFRLG/PokemonFRLG_Tests.cpp | 64 ++++++++ .../Source/PokemonFRLG/PokemonFRLG_Tests.h | 9 ++ 12 files changed, 428 insertions(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp index 8e8094f17b..9b233c75a0 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp @@ -18,7 +18,12 @@ #include "Pokemon/Pokemon_StatsCalculation.h" #include "Pokemon/Inference/Pokemon_NameReader.h" #include "Pokemon/Inference/Pokemon_NatureReader.h" +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" #include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG/PokemonFRLG_Tests.h" +#include "Tests/TestUtils.h" #include "PokemonFRLG_DigitReader.h" #include "PokemonFRLG_BattleLevelUpReader.h" @@ -68,6 +73,42 @@ StatReads BattleLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32& } +class Test_BattleLevelUpReader : public UnitTest{ +public: + Test_BattleLevelUpReader(const std::string& image) + : UnitTest("PokemonFRLG::BattleLevelUpReader - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + BattleLevelUpReader reader; + StatReads stats = reader.read_stats(logger, image); + + return check_against_golden_file( + m_image, + {"hp", "attack", "defense", "spatk", "spdef", "speed"}, + { + std::to_string(stats.hp), + std::to_string(stats.attack), + std::to_string(stats.defense), + std::to_string(stats.spatk), + std::to_string(stats.spdef), + std::to_string(stats.speed), + } + ); + }; + +private: + std::string m_image; +}; + + +void add_tests_BattleLevelUpReader(UnitTestDatabase& database){ + // to do +} + + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h index a4faafa2c1..c7fefc0a8b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h @@ -10,6 +10,7 @@ #include #include #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Language.h" #include "Pokemon/Pokemon_StatsCalculation.h" @@ -46,6 +47,9 @@ class BattleLevelUpReader { }; + +void add_tests_BattleLevelUpReader(UnitTestDatabase& database); + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp index 4ad3fa9a21..2001119b5d 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp @@ -18,7 +18,12 @@ #include "Pokemon/Pokemon_StatsCalculation.h" #include "Pokemon/Inference/Pokemon_NameReader.h" #include "Pokemon/Inference/Pokemon_NatureReader.h" +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" #include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG/PokemonFRLG_Tests.h" +#include "Tests/TestUtils.h" #include "PokemonFRLG_DigitReader.h" #include "PokemonFRLG_PartyLevelUpReader.h" @@ -68,6 +73,42 @@ StatReads PartyLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32& f } +class Test_PartyLevelUpReader : public UnitTest{ +public: + Test_PartyLevelUpReader(const std::string& image) + : UnitTest("PokemonFRLG::PartyLevelUpReader - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + PartyLevelUpReader reader; + StatReads stats = reader.read_stats(logger, image); + + return check_against_golden_file( + m_image, + {"hp", "attack", "defense", "spatk", "spdef", "speed"}, + { + std::to_string(stats.hp), + std::to_string(stats.attack), + std::to_string(stats.defense), + std::to_string(stats.spatk), + std::to_string(stats.spdef), + std::to_string(stats.speed), + } + ); + }; + +private: + std::string m_image; +}; + + +void add_tests_PartyLevelUpReader(UnitTestDatabase& database){ + // to do +} + + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h index a9edfd344d..bf0b2c67eb 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h @@ -10,6 +10,7 @@ #include #include #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Language.h" #include "Pokemon/Pokemon_StatsCalculation.h" @@ -46,6 +47,9 @@ class PartyLevelUpReader { }; + +void add_tests_PartyLevelUpReader(UnitTestDatabase& database); + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp index 1f3081a396..d8ed941b44 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp @@ -19,6 +19,11 @@ #include "Pokemon/Inference/Pokemon_NameReader.h" #include "Pokemon/Inference/Pokemon_NatureReader.h" #include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "PokemonFRLG/PokemonFRLG_Tests.h" +#include "Tests/TestUtils.h" #include "PokemonFRLG_DigitReader.h" #include "PokemonFRLG_OcrPreprocessing.h" #include @@ -394,6 +399,144 @@ void StatsReader::read_page2( } } + +namespace{ + +std::string number_slug(const std::optional& value){ + return value.has_value() ? std::to_string(*value) : "none"; +} +std::string text_slug(const std::string& value){ + return value.empty() ? "none" : value; +} +std::string gender_slug(const std::optional& gender){ + if (!gender.has_value()){ + return "none"; + } + switch (*gender){ + case SummaryGender::Male: + return "male"; + case SummaryGender::Female: + return "female"; + case SummaryGender::Genderless: + return "genderless"; + } + return "none"; +} + +UnitTestResult language_from_filename(const std::string& image_path, Language& language){ + const std::vector words = parse_words(Filesystem::Path(image_path).stem().string()); + if (words.empty()){ + return "Error: filename must end with a language code."; + } + language = language_code_to_enum(words.back()); + if (language == Language::None || language == Language::EndOfList){ + return "Error: invalid language word in filename: " + words.back(); + } + return true; +} + +} + + +class Test_StatsReaderPage1 : public UnitTest{ +public: + Test_StatsReaderPage1(const std::string& image) + : UnitTest("PokemonFRLG::StatsReader - page1 - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + Language language = Language::None; + UnitTestResult parsed = language_from_filename(m_image, language); + if (parsed.result != UnitTestResult::PASSED){ + return parsed; + } + + ImageRGB32 image(m_image); + StatsReader reader; + PokemonFRLG_Stats stats; + reader.read_page1(logger, language, image, stats); + + return check_against_golden_file( + m_image, + {"name", "level", "gender", "nature"}, + { + text_slug(stats.name), + number_slug(stats.level), + gender_slug(stats.gender), + text_slug(stats.nature), + } + ); + }; + +private: + std::string m_image; +}; + + +class Test_StatsReaderPage2 : public UnitTest{ +public: + Test_StatsReaderPage2(const std::string& image) + : UnitTest("PokemonFRLG::StatsReader - page2 - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + Language language = Language::None; + UnitTestResult parsed = language_from_filename(m_image, language); + if (parsed.result != UnitTestResult::PASSED){ + return parsed; + } + + ImageRGB32 image(m_image); + StatsReader reader; + PokemonFRLG_Stats stats; + reader.read_page2(logger, language, image, stats); + + return check_against_golden_file( + m_image, + {"hp", "attack", "defense", "spatk", "spdef", "speed"}, + { + number_slug(stats.hp), + number_slug(stats.attack), + number_slug(stats.defense), + number_slug(stats.sp_attack), + number_slug(stats.sp_defense), + number_slug(stats.speed), + } + ); + }; + +private: + std::string m_image; +}; + + +void add_tests_StatsReader(UnitTestDatabase& database){ + database.add("PokemonFRLG/StatsReader/Page1/bulbasaur_1_eng.png"); + database.add("PokemonFRLG/StatsReader/Page1/bulbasaur_2_eng.png"); + database.add("PokemonFRLG/StatsReader/Page1/bulbasaur_3_eng.png"); + database.add("PokemonFRLG/StatsReader/Page1/bulbasaur_4_eng.png"); + database.add("PokemonFRLG/StatsReader/Page1/bulbasaur_1_fra.png"); + database.add("PokemonFRLG/StatsReader/Page1/bulbasaur_1_jpn.png"); + database.add("PokemonFRLG/StatsReader/Page1/abra_1_deu.png"); + database.add("PokemonFRLG/StatsReader/Page1/deoxys_1_jpn.png"); + database.add("PokemonFRLG/StatsReader/Page1/horsea_1_deu.png"); + database.add("PokemonFRLG/StatsReader/Page1/horsea_2_deu.png"); + database.add("PokemonFRLG/StatsReader/Page1/horsea_3_deu.png"); + database.add("PokemonFRLG/StatsReader/Page1/magikarp_1_spa.png"); + database.add("PokemonFRLG/StatsReader/Page1/moltres_1_eng.png"); + database.add("PokemonFRLG/StatsReader/Page1/pidgey_1_ita.png"); + database.add("PokemonFRLG/StatsReader/Page1/venonat_4k_eng.png"); + database.add("PokemonFRLG/StatsReader/Page2/abra_1_deu.png"); + database.add("PokemonFRLG/StatsReader/Page2/deoxys_1_jpn.png"); + database.add("PokemonFRLG/StatsReader/Page2/mewtwo_1_spa.png"); + database.add("PokemonFRLG/StatsReader/Page2/nidoranf_1_eng.jpg"); + database.add("PokemonFRLG/StatsReader/Page2/raikou_1_eng.png"); + database.add("PokemonFRLG/StatsReader/Page2/venonat_1_eng.jpg"); +} + + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h index 4a19265759..044ba88950 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h @@ -10,6 +10,7 @@ #include #include #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Language.h" @@ -120,6 +121,9 @@ class StatsReader { }; + +void add_tests_StatsReader(UnitTestDatabase& database); + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp index 97050c215b..f60d6ddbab 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp @@ -15,9 +15,15 @@ #include "CommonTools/Images/ImageManip.h" #include "CommonTools/OCR/OCR_NumberReader.h" #include "CommonTools/OCR/OCR_Routines.h" +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" #include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG/PokemonFRLG_Tests.h" +#include "Tests/TestUtils.h" #include "PokemonFRLG_DigitReader.h" #include +#include namespace PokemonAutomation { namespace NintendoSwitch { @@ -48,6 +54,49 @@ uint16_t TrainerIdReader::read_tid( return uint16_t(read_digits_waterfill_template(logger, tid_region)); } + +class Test_TrainerIdReader : public UnitTest{ +public: + Test_TrainerIdReader(const std::string& image) + : UnitTest("PokemonFRLG::TrainerIdReader - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + const std::vector words = + parse_words(Filesystem::Path(m_image).stem().string()); + if (words.size() < 2){ + return "Error: filename must be __."; + } + + const std::string& language_word = words[words.size() - 2]; + Language language = language_code_to_enum(language_word); + if (language == Language::None || language == Language::EndOfList){ + return "Error: invalid language word in filename: " + language_word; + } + int target_tid = 0; + if (!parse_int(words.back(), target_tid)){ + return "Error: filename must end with the trainer ID: " + words.back(); + } + + ImageRGB32 image(m_image); + TrainerIdReader reader; + int tid = reader.read_tid(logger, language, image); + + TEST_RESULT_COMPONENT_EQUAL_STR(tid, target_tid, "trainer id"); + return true; + }; + +private: + std::string m_image; +}; + + +void add_tests_TrainerIdReader(UnitTestDatabase& database){ +// database.add("PokemonFRLG/TrainerIdReader/trainer_card_eng_23456.png"); +} + + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h index c33fdbcbe1..1813453756 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h @@ -10,6 +10,7 @@ #include #include #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Language.h" @@ -40,6 +41,9 @@ class TrainerIdReader { ImageFloatBox m_box_tid_jpn; }; + +void add_tests_TrainerIdReader(UnitTestDatabase& database); + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp index 901c107664..06b44a111a 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp @@ -16,9 +16,17 @@ #include "CommonTools/OCR/OCR_NumberReader.h" #include "CommonTools/OCR/OCR_Routines.h" #include "Pokemon/Inference/Pokemon_NameReader.h" +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" #include "PokemonFRLG/PokemonFRLG_Settings.h" #include "PokemonFRLG_OcrPreprocessing.h" +#include "PokemonFRLG/PokemonFRLG_Tests.h" +#include "PokemonFRLG/Programs/RngManipulation/PokemonFRLG_EncountersDatabase.h" +#include "Tests/TestUtils.h" +#include "PokemonFRLG_DigitReader.h" #include +#include namespace PokemonAutomation { namespace NintendoSwitch { @@ -56,10 +64,19 @@ PokemonFRLG_WildEncounter WildEncounterReader::read_encounter( // font, then let the matcher try each threshold in turn. ImageRGB32 name_ready = preprocess_for_ocr(name_box); - auto name_result = Pokemon::PokemonNameReader(subset).read_substring( + OCR::StringMatchResult name_result; + if (subset.size() == 0){ + name_result = Pokemon::PokemonNameReader::instance().read_substring( logger, language, name_ready, DARK_TEXT_FILTERS(), 0.01, 0.50, max_log10p); + }else{ + name_result = Pokemon::PokemonNameReader(subset).read_substring( + logger, language, name_ready, + DARK_TEXT_FILTERS(), + 0.01, 0.50, max_log10p); + } + if (!name_result.results.empty()){ encounter.name = name_result.results.begin()->second.token; }else{ @@ -73,6 +90,49 @@ PokemonFRLG_WildEncounter WildEncounterReader::read_encounter( return encounter; } + +class Test_WildEncounterReader : public UnitTest{ +public: + Test_WildEncounterReader(const std::string& image) + : UnitTest("PokemonFRLG::WildEncounterReader - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + const std::vector words = + parse_words(Filesystem::Path(m_image).stem().string()); + if (words.size() < 5){ + return "Error: filename must be " + "____."; + } + + std::string language_word = words[words.size() - 2]; + const std::string& target_species = words[words.size() - 1]; + + Language language = language_code_to_enum(language_word); + if (language == Language::None || language == Language::EndOfList){ + return "Error: invalid language word in filename: " + language_word; + } + + ImageRGB32 image(m_image); + WildEncounterReader reader; + PokemonFRLG_WildEncounter encounter = + reader.read_encounter(logger, language, image, {}); + + TEST_RESULT_COMPONENT_EQUAL_STR(encounter.name, target_species, "species"); + return true; + }; + +private: + std::string m_image; +}; + + +void add_tests_WildEncounterReader(UnitTestDatabase& database){ +// database.add("PokemonFRLG/WildEncounterReader/route1_eng_pidgey.png"); +} + + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h index 43561ace4f..a660a5de8a 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h @@ -10,6 +10,7 @@ #include #include #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Language.h" @@ -53,6 +54,9 @@ class WildEncounterReader { // ImageFloatBox m_box_level; }; + +void add_tests_WildEncounterReader(UnitTestDatabase& database); + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.cpp index d8ce98c4d1..9ff14172b1 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.cpp @@ -4,13 +4,24 @@ * */ +#include "Common/Cpp/Filesystem/Filesystem.h" #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonFramework/StaticGlobals.h" #include "Inference/Dialogs/PokemonFRLG_BattleDialogs.h" #include "Inference/Dialogs/PokemonFRLG_DialogDetector.h" #include "Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.h" +#include "Inference/PokemonFRLG_BattleLevelUpReader.h" +#include "Inference/PokemonFRLG_PartyLevelUpReader.h" #include "Inference/PokemonFRLG_ShinySymbolDetector.h" +#include "Inference/PokemonFRLG_StatsReader.h" +#include "Inference/PokemonFRLG_TrainerIdReader.h" +#include "Inference/PokemonFRLG_WildEncounterReader.h" +#include "Tests/TestUtils.h" #include "PokemonFRLG_Tests.h" +#include +#include + namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ @@ -23,6 +34,59 @@ void add_tests(UnitTestDatabase& database){ add_tests_PrizeSelectDetector(database); add_tests_AdvanceBattleDialogDetector(database); add_tests_BattleMenuDetector(database); + add_tests_StatsReader(database); + add_tests_WildEncounterReader(database); + add_tests_TrainerIdReader(database); + add_tests_PartyLevelUpReader(database); + add_tests_BattleLevelUpReader(database); +} + + +UnitTestResult check_against_golden_file( + const std::string& image_path, + const std::vector& labels, + const std::vector& values +){ + if (labels.size() != values.size()){ + return "Error: check_against_golden_file() called with " + + std::to_string(labels.size()) + " labels for " + + std::to_string(values.size()) + " values."; + } + for (size_t c = 0; c < values.size(); c++){ + if (values[c].empty() || values[c].find_first_of(" \t\r\n") != std::string::npos){ + return "Error: value for \"" + labels[c] + "\" is empty or contains whitespace. " + "The golden file cannot represent it."; + } + } + + Filesystem::Path file_path(image_path); + Filesystem::Path golden_path = + file_path.parent_path() / ("_" + file_path.stem().string() + ".txt"); + + if (STATIC_GLOBALS.GENERATE_TEST_GOLDEN_FILES){ + std::ofstream output_file(golden_path.stdpath()); + if (!output_file.is_open()){ + return "Error: cannot open " + golden_path.string() + " for writing."; + } + for (const std::string& value : values){ + output_file << value << std::endl; + } + return true; + } + + std::vector targets; + if (!load_slug_list(golden_path.string(), targets)){ + return "Error: cannot load golden file " + golden_path.string() + "."; + } + if (targets.size() != values.size()){ + return "Error: golden file " + golden_path.string() + " has " + + std::to_string(targets.size()) + " values, expected " + + std::to_string(values.size()) + "."; + } + for (size_t c = 0; c < values.size(); c++){ + TEST_RESULT_COMPONENT_EQUAL_STR(values[c], targets[c], labels[c]); + } + return true; } diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.h index ef942df247..ea7f1861a8 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.h @@ -7,6 +7,8 @@ #ifndef PokemonAutomation_PokemonFRLG_Tests_H #define PokemonAutomation_PokemonFRLG_Tests_H +#include +#include #include "Common/Cpp/TestRunners/UnitTestDatabase.h" namespace PokemonAutomation{ @@ -17,6 +19,13 @@ namespace PokemonFRLG{ void add_tests(UnitTestDatabase& database); +// Shared helper for the readers that produce several values from one image. +UnitTestResult check_against_golden_file( + const std::string& image_path, + const std::vector& labels, + const std::vector& values +); + } From 9afba19df101dfa08264b878c12ce8fbaff80df2 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Thu, 20 Aug 2026 22:46:35 -0500 Subject: [PATCH 2/3] add WildEncounterReader tests --- .../PokemonFRLG_WildEncounterReader.cpp | 15 +++++--- .../PokemonFRLG_ReadEncounter.cpp | 36 +------------------ .../TestPrograms/PokemonFRLG_ReadEncounter.h | 9 ----- 3 files changed, 12 insertions(+), 48 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp index 06b44a111a..5f50276c4f 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp @@ -101,9 +101,8 @@ class Test_WildEncounterReader : public UnitTest{ virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ const std::vector words = parse_words(Filesystem::Path(m_image).stem().string()); - if (words.size() < 5){ - return "Error: filename must be " - "____."; + if (words.size() < 2){ + return "Error: filename must be __."; } std::string language_word = words[words.size() - 2]; @@ -129,7 +128,15 @@ class Test_WildEncounterReader : public UnitTest{ void add_tests_WildEncounterReader(UnitTestDatabase& database){ -// database.add("PokemonFRLG/WildEncounterReader/route1_eng_pidgey.png"); + database.add("PokemonFRLG/WildEncounterReader/eng_chansey.jpg"); + database.add("PokemonFRLG/WildEncounterReader/eng_machop.jpg"); + database.add("PokemonFRLG/WildEncounterReader/eng_kakuna.jpg"); + database.add("PokemonFRLG/WildEncounterReader/eng_ho-oh.jpg"); + database.add("PokemonFRLG/WildEncounterReader/eng_heracross.jpg"); + database.add("PokemonFRLG/WildEncounterReader/eng_dragonair.jpg"); + database.add("PokemonFRLG/WildEncounterReader/eng_articuno.jpg"); + database.add("PokemonFRLG/WildEncounterReader/fra_snorlax.jpg"); + database.add("PokemonFRLG/WildEncounterReader/dark_eng_chansey.png"); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.cpp index 61754c2bd8..4ef4df6fe1 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.cpp @@ -40,21 +40,8 @@ ReadEncounter::ReadEncounter() Pokemon::PokemonNameReader::instance().languages(), LockMode::LOCK_WHILE_RUNNING, true ) - , SUBSET( - "Possible Encounters:", - { - {Subset::route1, "route1", "Route 1 (Pidgey / Rattata)"}, - {Subset::route22, "route22", "Route 22 (Rattata / Spearow / Mankey"}, - {Subset::viridianforest, "viridianforest", "Viridian Forest (Caterpie / Metapod / Weedle / Kakuna / Pikachu)"}, - {Subset::rocktunnel, "rocktunnel", "Rock Tunnel (Geodude / Machop / Mankey / Onix / Zubat)"}, - {Subset::pokemontower, "pokemontower", "Pokemon Tower (Gastly, Haunter, Cubone)"}, - }, - LockMode::LOCK_WHILE_RUNNING, - Subset::route1 - ) { PA_ADD_OPTION(LANGUAGE); - PA_ADD_OPTION(SUBSET); } void ReadEncounter::program( @@ -65,34 +52,13 @@ void ReadEncounter::program( "Starting Read Encounter program..." ); - std::set subset; - switch (SUBSET){ - case Subset::route1: - subset = std::set{"pidgey","rattata"}; - break; - case Subset::route22: - subset = std::set{"rattata", "spearow", "mankey"}; - break; - case Subset::viridianforest: - subset = std::set{"caterpie", "metapod", "weedle", "kakuna", "pikachu"}; - break; - case Subset::rocktunnel: - subset = std::set{"geodude", "zubat", "mankey", "machop", "onix"}; - break; - case Subset::pokemontower: - subset = std::set{"gastly", "haunter", "cubone"}; - break; - default: - subset = std::set{}; - } - WildEncounterReader reader(COLOR_RED); VideoOverlaySet overlays(env.console.overlay()); reader.make_overlays(overlays); env.log("Reading name and level..."); VideoSnapshot screen = env.console.video().snapshot(); - PokemonFRLG_WildEncounter encounter = reader.read_encounter(env.logger(), LANGUAGE, screen, subset); + PokemonFRLG_WildEncounter encounter = reader.read_encounter(env.logger(), LANGUAGE, screen, {}); env.log("Name: " + encounter.name); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.h b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.h index 4892282058..2f75843f3d 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.h @@ -32,16 +32,7 @@ class ReadEncounter : public SingleSwitchProgramInstance{ private: - enum class Subset{ - route1, - route22, - viridianforest, - rocktunnel, - pokemontower - }; - OCR::LanguageOCROption LANGUAGE; - EnumDropdownOption SUBSET; }; From 6a0b40ea56af80856e54e815b45ce2477f6b112d Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Thu, 20 Aug 2026 22:54:07 -0500 Subject: [PATCH 3/3] add TrainerIdReader tests --- .../PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp index f60d6ddbab..281ee40587 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp @@ -93,7 +93,8 @@ class Test_TrainerIdReader : public UnitTest{ void add_tests_TrainerIdReader(UnitTestDatabase& database){ -// database.add("PokemonFRLG/TrainerIdReader/trainer_card_eng_23456.png"); + database.add("PokemonFRLG/TrainerIdReader/tom_eng_60895.jpg"); + database.add("PokemonFRLG/TrainerIdReader/nyash_jpn_45345.png"); }