// Smoke tests dels helpers purs del flux d'escenes: parser de debug.yaml i // classificació de tecles reservades. Stand-alone, sense frameworks externs. // // Tests coberts: // T1 parseStartScene — "LOGO"/"TITLE"/"INTRO"/"GAME" donen l'enum correcte. // T2 parseStartScene — valor desconegut o fitxer buit → GAME (fallback). // T3 parseStartScene — YAML invàlid no llança, cau a GAME. // T4 resolveStartScene — fitxer inexistent → GAME sense crash. // T5 resolveStartScene — fitxer real amb valor vàlid és respectat. // T6 resolveStartScene — fitxer real amb valor invàlid → GAME. // T7 isReservedScancode — F1/F12/Escape reservats després de KeyConfig::load. // T8 isReservedScancode — SPACE i fletxes no són reservades. // T9 isReservedScancode — SDL_SCANCODE_UNKNOWN sempre fals. // // La lògica d'update/render dels placeholders (transició per timeout o // keypress) no es testeja ací perquè `update()` crida GlobalInputs, que // arrossega Screen/Notifier/RenderInfo i tot l'stack SDL. Aquesta integració // la cobreix el smoke run del binari sota xvfb-run. // // Execució: // make build && cd build && ctest --output-on-failure // O bé: // ./build/test_scene_flow #include #include #include #include #include #include #include "core/input/key_config.hpp" #include "game/debug_config.hpp" #include "game/scenes/placeholder_input.hpp" #include "utils/defaults.hpp" namespace { int g_failures = 0; void check(bool cond, const char* msg, const char* file, int line) { if (!cond) { std::cerr << " FAIL: " << msg << " (" << file << ":" << line << ")\n"; ++g_failures; } } #define CHECK(cond) check((cond), #cond, __FILE__, __LINE__) void writeTemp(const std::string& path, const std::string& content) { std::ofstream file(path); file << content; } void testParseStartSceneValid() { std::cout << "T1 parseStartScene valid\n"; CHECK(DebugConfig::parseStartScene("start_scene: LOGO") == DebugConfig::StartScene::LOGO); CHECK(DebugConfig::parseStartScene("start_scene: TITLE") == DebugConfig::StartScene::TITLE); CHECK(DebugConfig::parseStartScene("start_scene: INTRO") == DebugConfig::StartScene::INTRO); CHECK(DebugConfig::parseStartScene("start_scene: GAME") == DebugConfig::StartScene::GAME); } void testParseStartSceneFallback() { std::cout << "T2 parseStartScene fallback\n"; CHECK(DebugConfig::parseStartScene("") == DebugConfig::StartScene::GAME); CHECK(DebugConfig::parseStartScene("start_scene: FOO") == DebugConfig::StartScene::GAME); CHECK(DebugConfig::parseStartScene("other_field: 42") == DebugConfig::StartScene::GAME); } void testParseStartSceneInvalidYaml() { std::cout << "T3 parseStartScene YAML invàlid\n"; // Text amb caràcters que fan petar el parser: indentació incoherent + // claus desbalancejades. Si el nostre try/catch no està bé, això // propagaria una excepció i trencaria el test. CHECK(DebugConfig::parseStartScene("{{{unclosed: [") == DebugConfig::StartScene::GAME); } void testResolveStartSceneMissing() { std::cout << "T4 resolveStartScene fitxer inexistent\n"; CHECK(DebugConfig::resolveStartScene("/tmp/aee_arcade_nonexistent_XYZ.yaml") == DebugConfig::StartScene::GAME); } void testResolveStartSceneValid() { std::cout << "T5 resolveStartScene fitxer vàlid\n"; const auto PATH = std::filesystem::temp_directory_path() / "aee_arcade_test_debug_valid.yaml"; writeTemp(PATH.string(), "start_scene: TITLE\n"); CHECK(DebugConfig::resolveStartScene(PATH.string()) == DebugConfig::StartScene::TITLE); std::filesystem::remove(PATH); } void testResolveStartSceneInvalid() { std::cout << "T6 resolveStartScene valor invàlid\n"; const auto PATH = std::filesystem::temp_directory_path() / "aee_arcade_test_debug_invalid.yaml"; writeTemp(PATH.string(), "start_scene: WUT\n"); CHECK(DebugConfig::resolveStartScene(PATH.string()) == DebugConfig::StartScene::GAME); std::filesystem::remove(PATH); } void testCreateDefaultIfMissing() { std::cout << "T7 createDefaultIfMissing\n"; const auto PATH = std::filesystem::temp_directory_path() / "aee_arcade_test_debug_create.yaml"; std::filesystem::remove(PATH); CHECK(!std::filesystem::exists(PATH)); DebugConfig::createDefaultIfMissing(PATH.string()); CHECK(std::filesystem::exists(PATH)); CHECK(DebugConfig::resolveStartScene(PATH.string()) == DebugConfig::StartScene::GAME); // Segona invocació: ha de ser idempotent (no reescriu si ja existeix). writeTemp(PATH.string(), "start_scene: LOGO\n"); DebugConfig::createDefaultIfMissing(PATH.string()); CHECK(DebugConfig::resolveStartScene(PATH.string()) == DebugConfig::StartScene::LOGO); std::filesystem::remove(PATH); } void testIsReservedScancodeAfterLoad() { std::cout << "T8 isReservedScancode després de KeyConfig::load\n"; KeyConfig::get().load(Defaults::Paths::INPUT_YAML); CHECK(Placeholder::isReservedScancode(SDL_SCANCODE_F1)); CHECK(Placeholder::isReservedScancode(SDL_SCANCODE_F12)); CHECK(Placeholder::isReservedScancode(SDL_SCANCODE_ESCAPE)); } void testIsReservedScancodeNonReserved() { std::cout << "T9 isReservedScancode per tecles no reservades\n"; CHECK(!Placeholder::isReservedScancode(SDL_SCANCODE_SPACE)); CHECK(!Placeholder::isReservedScancode(SDL_SCANCODE_RETURN)); CHECK(!Placeholder::isReservedScancode(SDL_SCANCODE_UP)); CHECK(!Placeholder::isReservedScancode(SDL_SCANCODE_DOWN)); CHECK(!Placeholder::isReservedScancode(SDL_SCANCODE_LEFT)); CHECK(!Placeholder::isReservedScancode(SDL_SCANCODE_RIGHT)); } void testIsReservedScancodeUnknown() { std::cout << "T10 isReservedScancode amb UNKNOWN\n"; CHECK(!Placeholder::isReservedScancode(SDL_SCANCODE_UNKNOWN)); } } // namespace auto main() -> int { testParseStartSceneValid(); testParseStartSceneFallback(); testParseStartSceneInvalidYaml(); testResolveStartSceneMissing(); testResolveStartSceneValid(); testResolveStartSceneInvalid(); testCreateDefaultIfMissing(); testIsReservedScancodeAfterLoad(); testIsReservedScancodeNonReserved(); testIsReservedScancodeUnknown(); if (g_failures == 0) { std::cout << "OK: tots els tests de scene flow passen\n"; return 0; } std::cerr << g_failures << " tests han fallat\n"; return 1; }