From 2cb21211ef3f556502858a754a26bf3e5de4dd0e Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 22 Apr 2026 10:35:07 +0200 Subject: [PATCH] test(scenes): smoke del flux d'escenes i parser de debug.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cobreix les dues peces pures del flux: - parseStartScene / resolveStartScene: tots els valors vàlids, fallback a GAME per valor invàlid, YAML corrupte o fitxer inexistent. - createDefaultIfMissing: crea el fitxer amb start_scene=GAME si no existeix i és idempotent quan ja hi és. - isReservedScancode: F1/F12/Escape reservats, SPACE/fletxes no, i UNKNOWN sempre fals. La lògica de update/render dels placeholders no es testeja perquè arrossegaria Screen/Notifier/RenderInfo (tot l'stack SDL). Aquesta integració queda coberta pel smoke xvfb-run del binari. Stand-alone target `test_scene_flow` que només enllaça els .cpp pures necessaris, amb ctest afegit. --- CMakeLists.txt | 20 +++++ tests/test_scene_flow.cpp | 158 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 tests/test_scene_flow.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b729a2a..c89249b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,4 +278,24 @@ if(BUILD_TESTS) add_test(NAME entities_smoke COMMAND test_entities_smoke WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + + # Smoke del flux d'escenes i del parser de debug.yaml. Stand-alone: + # només enllaça els helpers purs (debug_config, placeholder_input, + # key_config) perquè la integració completa dels placeholders arrossegaria + # tot l'stack SDL (Screen/Notifier/RenderInfo), coberta pel smoke xvfb. + add_executable(test_scene_flow + tests/test_scene_flow.cpp + source/game/debug_config.cpp + source/game/scenes/placeholder_input.cpp + source/core/input/key_config.cpp + ) + target_include_directories(test_scene_flow PRIVATE + "${CMAKE_SOURCE_DIR}/source" + "${CMAKE_BINARY_DIR}" + ) + target_link_libraries(test_scene_flow PRIVATE SDL3::SDL3) + target_compile_options(test_scene_flow PRIVATE -Wall -Wextra -Wpedantic) + add_test(NAME scene_flow + COMMAND test_scene_flow + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) endif() diff --git a/tests/test_scene_flow.cpp b/tests/test_scene_flow.cpp new file mode 100644 index 0000000..618bb20 --- /dev/null +++ b/tests/test_scene_flow.cpp @@ -0,0 +1,158 @@ +// 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" + +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_2026_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_2026_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_2026_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_2026_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("data/input/keys.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; +}