From b8b42d5681837504b0ff019a7adbfe4573e6e880 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Tue, 16 Jun 2026 04:38:38 -0400 Subject: [PATCH] Started to convert from catch2 to fuc2. --- tests/animation.cpp | 295 +++++++++++++++++---------------- tests/base.cpp | 17 +- tests/battle.cpp | 203 ++++++++++++----------- tests/camera.cpp | 17 +- tests/components.cpp | 76 +++++---- tests/config.cpp | 46 +++-- tests/dbc.cpp | 26 ++- tests/dinkyecs.cpp | 387 ++++++++++++++++++++++--------------------- tests/main.cpp | 25 +++ tests/meson.build | 16 +- 10 files changed, 615 insertions(+), 493 deletions(-) create mode 100644 tests/main.cpp diff --git a/tests/animation.cpp b/tests/animation.cpp index 69bf2c3..7b79f8d 100644 --- a/tests/animation.cpp +++ b/tests/animation.cpp @@ -1,4 +1,3 @@ -#include #include "graphics/textures.hpp" #include "algos/dinkyecs.hpp" #include "game/config.hpp" @@ -10,160 +9,176 @@ #include "graphics/animation.hpp" #include "game/sound.hpp" #include "game/components.hpp" +#include using namespace components; using namespace textures; using namespace std::chrono_literals; using namespace animation; +using namespace fuc2; -Animation load_animation(const string& name) { - auto anim = animation::load("assets/animation.json", "rat_king_boss"); - anim.set_form("attack"); +namespace animation_tests { + Animation load_animation(const string& name) { + auto anim = animation::load("assets/animation.json", "rat_king_boss"); + anim.set_form("attack"); - anim.transform.looped = false; + anim.transform.looped = false; - for(size_t i = 0; i < anim.sequence.durations.size(); i++) { - anim.sequence.durations[i] = Random::uniform(1, 5); - } - - return anim; -} - -void FAKE_RENDER() { - std::this_thread::sleep_for(Random::milliseconds(5, 32)); -} - -void PLAY_TEST(Animation &anim) { - REQUIRE(anim.transform.looped == false); - anim.play(); - - while(anim.playing) { - anim.update(); - FAKE_RENDER(); - } - - REQUIRE(anim.playing == false); -} - -/* - * Animation is a Sheet + Sequence + Transform. - * - * A Sheet is just a grid of images with a predefined size for each cell. Arbitrary sized cells not supported. - * - * A Sequence is a list of Sheet cells _in any order_. See https://github.com/yottahmd/ganim8-lib. Sequences have a timing element for the cells, possibly a list of durations or a single duration. - * - * A Transform is combinations of scale and/or position easing/motion, shader effects, and things like if it's looped or toggled. - * - * I like the ganim8 onLoop concept, just a callback that says what to do when the animation has looped. - */ -TEST_CASE("new animation system", "[animation-new]") { - textures::init(); - sound::init(); - sound::mute(true); - - auto anim = load_animation("rat_king_boss"); - PLAY_TEST(anim); - - // test that toggled works - anim.transform.toggled = true; - PLAY_TEST(anim); - REQUIRE(anim.sequence.current == anim.sequence.frames.size() - 1); - anim.transform.toggled = false; - - bool onLoop_ran = false; - anim.onLoop = [&](auto& seq, auto& tr) -> bool { - seq.current = 0; - onLoop_ran = true; - return tr.looped; - }; - - PLAY_TEST(anim); - REQUIRE(onLoop_ran == true); - - // only runs twice - anim.onLoop = [](auto& seq, auto& tr) -> bool { - if(seq.loop_count == 2) { - seq.current = 0; - return false; - } else { - seq.current = seq.current % seq.frame_count; - return true; + for(size_t i = 0; i < anim.sequence.durations.size(); i++) { + anim.sequence.durations[i] = Random::uniform(1, 5); } - }; - PLAY_TEST(anim); - REQUIRE(anim.sequence.loop_count == 2); -} - - -TEST_CASE("confirm frame sequencing works", "[animation-new]") { - textures::init(); - sound::init(); - sound::mute(true); - - auto anim = load_animation("rat_king_boss"); - - auto boss = textures::get_sprite("rat_king_boss"); - sf::IntRect init_rect{{0,0}, {anim.sheet.frame_width, anim.sheet.frame_height}}; - - anim.play(); - bool loop_ran = false; - - // this will check that it moved to the next frame - anim.onLoop = [&](auto& seq, auto& tr) -> bool { - seq.current = 0; - loop_ran = true; - return false; - }; - - anim.onFrame = [&](){ - anim.apply(*boss.sprite); - }; - - while(anim.playing) { - anim.update(); - FAKE_RENDER(); + return anim; } - REQUIRE(loop_ran == true); - REQUIRE(anim.playing == false); -} - -TEST_CASE("confirm transition changes work", "[animation-new]") { - textures::init(); - sound::init(); - sound::mute(true); - - auto sprite = *textures::get_sprite("rat_king_boss").sprite; - sf::Vector2f pos{100,100}; - sprite.setPosition(pos); - auto scale = sprite.getScale(); - auto anim = load_animation("rat_king_boss"); - - // also testing that onFrame being null means it's not run - REQUIRE(anim.onFrame == nullptr); - - anim.play(); - REQUIRE(anim.playing == true); - - while(anim.playing) { - anim.update(); - anim.motion(sprite, pos, scale); - FAKE_RENDER(); + void FAKE_RENDER() { + std::this_thread::sleep_for(Random::milliseconds(5, 32)); } - REQUIRE(anim.playing == false); - REQUIRE(pos == sf::Vector2f{100, 100}); - REQUIRE(scale != sf::Vector2f{0,0}); -} + void PLAY_TEST(Animation &anim) { + CHECK(anim.transform.looped == false); + anim.play(); -TEST_CASE("playing with delta time", "[animation-new]") { - animation::Timer timer; - timer.start(); + while(anim.playing) { + anim.update(); + FAKE_RENDER(); + } - for(int i = 0; i < 20; i++) { - FAKE_RENDER(); - auto [tick_count, alpha] = timer.commit(); - // fmt::println("tick: {}, alpha: {}", tick_count, alpha); + CHECK(anim.playing == false); } + + /* + * Animation is a Sheet + Sequence + Transform. + * + * A Sheet is just a grid of images with a predefined size for each cell. Arbitrary sized cells not supported. + * + * A Sequence is a list of Sheet cells _in any order_. See https://github.com/yottahmd/ganim8-lib. Sequences have a timing element for the cells, possibly a list of durations or a single duration. + * + * A Transform is combinations of scale and/or position easing/motion, shader effects, and things like if it's looped or toggled. + * + * I like the ganim8 onLoop concept, just a callback that says what to do when the animation has looped. + */ + void test_new_animation_system() { + textures::init(); + sound::init(); + sound::mute(true); + + auto anim = load_animation("rat_king_boss"); + PLAY_TEST(anim); + + // test that toggled works + anim.transform.toggled = true; + PLAY_TEST(anim); + CHECK(anim.sequence.current == anim.sequence.frames.size() - 1); + anim.transform.toggled = false; + + bool onLoop_ran = false; + anim.onLoop = [&](auto& seq, auto& tr) -> bool { + seq.current = 0; + onLoop_ran = true; + return tr.looped; + }; + + PLAY_TEST(anim); + CHECK(onLoop_ran == true); + + // only runs twice + anim.onLoop = [](auto& seq, auto& tr) -> bool { + if(seq.loop_count == 2) { + seq.current = 0; + return false; + } else { + seq.current = seq.current % seq.frame_count; + return true; + } + }; + + PLAY_TEST(anim); + CHECK(anim.sequence.loop_count == 2); + } + + + void test_confirm_frame_sequencing_works() { + textures::init(); + sound::init(); + sound::mute(true); + + auto anim = load_animation("rat_king_boss"); + + auto boss = textures::get_sprite("rat_king_boss"); + sf::IntRect init_rect{{0,0}, {anim.sheet.frame_width, anim.sheet.frame_height}}; + + anim.play(); + bool loop_ran = false; + + // this will check that it moved to the next frame + anim.onLoop = [&](auto& seq, auto& tr) -> bool { + seq.current = 0; + loop_ran = true; + return false; + }; + + anim.onFrame = [&](){ + anim.apply(*boss.sprite); + }; + + while(anim.playing) { + anim.update(); + FAKE_RENDER(); + } + + CHECK(loop_ran == true); + CHECK(anim.playing == false); + } + + void test_confirm_transition_changes_work() { + textures::init(); + sound::init(); + sound::mute(true); + + auto sprite = *textures::get_sprite("rat_king_boss").sprite; + sf::Vector2f pos{100,100}; + sprite.setPosition(pos); + auto scale = sprite.getScale(); + auto anim = load_animation("rat_king_boss"); + + // also testing that onFrame being null means it's not run + CHECK(anim.onFrame == nullptr); + + anim.play(); + CHECK(anim.playing == true); + + while(anim.playing) { + anim.update(); + anim.motion(sprite, pos, scale); + FAKE_RENDER(); + } + + CHECK(anim.playing == false); + CHECK(pos == sf::Vector2f{100, 100}); + CHECK(scale != sf::Vector2f{0,0}); + } + + void test_playing_with_delta_time() { + animation::Timer timer; + timer.start(); + + for(int i = 0; i < 20; i++) { + FAKE_RENDER(); + auto [tick_count, alpha] = timer.commit(); + // fmt::println("tick: {}, alpha: {}", tick_count, alpha); + } + } + + + fuc2::Set TESTS{ + .name="ai functionality", + .tests={ + TEST(test_new_animation_system), + TEST(test_confirm_frame_sequencing_works), + TEST(test_confirm_transition_changes_work), + TEST(test_playing_with_delta_time), + } + }; + } diff --git a/tests/base.cpp b/tests/base.cpp index 4097258..c93a6c6 100644 --- a/tests/base.cpp +++ b/tests/base.cpp @@ -1,9 +1,18 @@ -#include #include #include +#include -using namespace fmt; +using namespace fuc2; -TEST_CASE("base test", "[base]") { - REQUIRE(1 == 1); +namespace base_tests { + void test_base_test() { + CHECK(1 == 1); + } + + fuc2::Set TESTS{ + .name="base template", + .tests={ + TEST(test_base_test), + } + }; } diff --git a/tests/battle.cpp b/tests/battle.cpp index 2091c99..f5ebc4d 100644 --- a/tests/battle.cpp +++ b/tests/battle.cpp @@ -1,4 +1,3 @@ -#include #include #include #include "game/rituals.hpp" @@ -11,134 +10,146 @@ #include "game/components.hpp" #include "ai/ai.hpp" #include "graphics/palette.hpp" +#include using namespace combat; using namespace boss; using namespace components; +using namespace fuc2; -TEST_CASE("battle operations fantasy", "[fail]") { - ai::reset(); - ai::init("ai"); +namespace battle_tests { + void test_battle_operations_fantasy() { + ai::reset(); + ai::init("ai"); - auto ai_start = ai::load_state("Enemy::initial_state"); - auto ai_goal = ai::load_state("Enemy::final_state"); - auto host_start = ai::load_state("Host::initial_state"); - auto host_goal = ai::load_state("Host::final_state"); - BattleEngine battle; + auto ai_start = ai::load_state("Enemy::initial_state"); + auto ai_goal = ai::load_state("Enemy::final_state"); + auto host_start = ai::load_state("Host::initial_state"); + auto host_goal = ai::load_state("Host::final_state"); + BattleEngine battle; - DinkyECS::Entity host = 0; - ai::EntityAI host_ai("Host::actions", host_start, host_goal); - components::Combat host_combat{ - .hp=100, .max_hp=100, .ap_delta=6, .max_ap=12, .damage=20}; - battle.add_enemy({host, &host_ai, &host_combat, true}); + DinkyECS::Entity host = 0; + ai::EntityAI host_ai("Host::actions", host_start, host_goal); + components::Combat host_combat{ + .hp=100, .max_hp=100, .ap_delta=6, .max_ap=12, .damage=20}; + battle.add_enemy({host, &host_ai, &host_combat, true}); - DinkyECS::Entity axe_ranger = 1; - ai::EntityAI axe_ai("Enemy::actions", ai_start, ai_goal); - components::Combat axe_combat{ - .hp=20, .max_hp=20, .ap_delta=8, .max_ap=12, .damage=20}; - battle.add_enemy({axe_ranger, &axe_ai, &axe_combat}); + DinkyECS::Entity axe_ranger = 1; + ai::EntityAI axe_ai("Enemy::actions", ai_start, ai_goal); + components::Combat axe_combat{ + .hp=20, .max_hp=20, .ap_delta=8, .max_ap=12, .damage=20}; + battle.add_enemy({axe_ranger, &axe_ai, &axe_combat}); - DinkyECS::Entity rat = 2; - ai::EntityAI rat_ai("Enemy::actions", ai_start, ai_goal); - components::Combat rat_combat{ - .hp=10, .max_hp=10, .ap_delta=2, .max_ap=10, .damage=10}; - battle.add_enemy({rat, &rat_ai, &rat_combat}); + DinkyECS::Entity rat = 2; + ai::EntityAI rat_ai("Enemy::actions", ai_start, ai_goal); + components::Combat rat_combat{ + .hp=10, .max_hp=10, .ap_delta=2, .max_ap=10, .damage=10}; + battle.add_enemy({rat, &rat_ai, &rat_combat}); - battle.set_all("enemy_found", true); - battle.set_all("in_combat", true); - battle.set_all("tough_personality", true); - battle.set_all("health_good", true); + battle.set_all("enemy_found", true); + battle.set_all("in_combat", true); + battle.set_all("tough_personality", true); + battle.set_all("health_good", true); - battle.set(rat, "tough_personality", false); - battle.set(host, "have_healing", false); - battle.set(host, "tough_personality", false); + battle.set(rat, "tough_personality", false); + battle.set(host, "have_healing", false); + battle.set(host, "tough_personality", false); - int host_combat_loop = 0; - for(host_combat_loop = 0; host_combat_loop < 1000; host_combat_loop++) { - if(host_combat.is_dead()) break; - battle.set(host, "health_good", host_combat.hp > 20); + int host_combat_loop = 0; + for(host_combat_loop = 0; host_combat_loop < 1000; host_combat_loop++) { + if(host_combat.is_dead()) break; + battle.set(host, "health_good", host_combat.hp > 20); - battle.player_request("use_healing"); - battle.player_request("kill_enemy"); + battle.player_request("use_healing"); + battle.player_request("kill_enemy"); - battle.ap_refresh(); - battle.plan(); - int battle_count = 0; + battle.ap_refresh(); + battle.plan(); + int battle_count = 0; - for(int battle_count = 0; battle_count < 1000; battle_count++) { - auto act = battle.next(); - if(!act) break; + for(int battle_count = 0; battle_count < 1000; battle_count++) { + auto act = battle.next(); + if(!act) break; - auto& [enemy, wants_to, cost, enemy_state] = *act; + auto& [enemy, wants_to, cost, enemy_state] = *act; - // fmt::println(">>>>> entity: {} wants to {} cost={}; has {} HP; {} ap", - // enemy.entity, wants_to, - // cost, enemy.combat->hp, - // enemy.combat->ap); + // fmt::println(">>>>> entity: {} wants to {} cost={}; has {} HP; {} ap", + // enemy.entity, wants_to, + // cost, enemy.combat->hp, + // enemy.combat->ap); - switch(enemy_state) { - case BattleHostState::agree: - // fmt::println("HOST and PLAYER requests match {}, doing it.", wants_to); - break; - case BattleHostState::disagree: - // fmt::println("REBELIOUS ACT: {}", wants_to); - battle.clear_requests(); - REQUIRE(battle.$player_requests.size() == 0); - break; - case BattleHostState::not_host: - if(wants_to == "kill_enemy") { - enemy.combat->attack(host_combat); - } - break; - case BattleHostState::out_of_ap: - // fmt::println("ENEMY OUT OF AP"); - break; + switch(enemy_state) { + case BattleHostState::agree: + // fmt::println("HOST and PLAYER requests match {}, doing it.", wants_to); + break; + case BattleHostState::disagree: + // fmt::println("REBELIOUS ACT: {}", wants_to); + battle.clear_requests(); + CHECK(battle.$player_requests.size() == 0); + break; + case BattleHostState::not_host: + if(wants_to == "kill_enemy") { + enemy.combat->attack(host_combat); + } + break; + case BattleHostState::out_of_ap: + // fmt::println("ENEMY OUT OF AP"); + break; + } } + + CHECK(!battle.next()); + dbc::check(battle_count < 1000, "infinite battle loop"); } - REQUIRE(!battle.next()); - dbc::check(battle_count < 1000, "infinite battle loop"); + dbc::check(host_combat_loop < 1000, "infinite host combat loop, host won't die!"); } - dbc::check(host_combat_loop < 1000, "infinite host combat loop, host won't die!"); -} + void test_boss_systems_work() { + components::init(); + gui::Backend backend; + guecs::init(&backend); -TEST_CASE("boss/systems.cpp works", "[combat-battle]") { - components::init(); - gui::Backend backend; - guecs::init(&backend); + ai::reset(); + ai::init("ai"); + GameDB::init(); + cinematic::init(); + auto host = GameDB::the_player(); - ai::reset(); - ai::init("ai"); - GameDB::init(); - cinematic::init(); - auto host = GameDB::the_player(); + auto fight = System::create_bossfight(); + auto battle = System::create_battle(fight->$world, fight->$boss_id); + auto& host_combat = fight->$world->get(host); - auto fight = System::create_bossfight(); - auto battle = System::create_battle(fight->$world, fight->$boss_id); - auto& host_combat = fight->$world->get(host); + System::initialize_actor_ai(*fight->$world, fight->$boss_id); - System::initialize_actor_ai(*fight->$world, fight->$boss_id); + // NEED UPDATE STATE + battle.set_all("enemy_found", true); + battle.set_all("in_combat", true); + battle.set_all("tough_personality", true); + battle.set_all("health_good", true); - // NEED UPDATE STATE - battle.set_all("enemy_found", true); - battle.set_all("in_combat", true); - battle.set_all("tough_personality", true); - battle.set_all("health_good", true); + battle.ap_refresh(); - battle.ap_refresh(); + battle.player_request("kill_enemy"); + int pending_ap = battle.player_pending_ap(); - battle.player_request("kill_enemy"); - int pending_ap = battle.player_pending_ap(); + CHECK(pending_ap < host_combat.ap); - REQUIRE(pending_ap < host_combat.ap); + System::plan_battle(battle, fight->$world, fight->$boss_id); - System::plan_battle(battle, fight->$world, fight->$boss_id); + while(auto action = battle.next()) { + dbc::log("ACTION!"); + System::combat(*action, fight->$world, fight->$boss_id, 0); + } - while(auto action = battle.next()) { - dbc::log("ACTION!"); - System::combat(*action, fight->$world, fight->$boss_id, 0); + CHECK(host_combat.ap == pending_ap); } - REQUIRE(host_combat.ap == pending_ap); + fuc2::Set TESTS{ + .name="battle tests", + .tests={ + TEST(test_battle_operations_fantasy), + TEST(test_boss_systems_work), + } + }; } diff --git a/tests/camera.cpp b/tests/camera.cpp index dea5f16..9397283 100644 --- a/tests/camera.cpp +++ b/tests/camera.cpp @@ -1,6 +1,17 @@ -#include #include +#include -TEST_CASE("view based camera system", "[camera]") { - REQUIRE(1 == 1); +using namespace fuc2; + +namespace camera_tests { + void test_view_based_camera_system() { + CHECK(1 == 1); + } + + fuc2::Set TESTS{ + .name="camera tests (EMPTY!)", + .tests={ + TEST(test_view_based_camera_system), + } + }; } diff --git a/tests/components.cpp b/tests/components.cpp index 7a38fba..c8b84e6 100644 --- a/tests/components.cpp +++ b/tests/components.cpp @@ -1,4 +1,4 @@ -#include +#include #include "game/components.hpp" #include "algos/dinkyecs.hpp" #include "game/config.hpp" @@ -7,44 +7,56 @@ using namespace components; using namespace DinkyECS; -TEST_CASE("confirm component loading works", "[components]") { - std::vector test_list{ - "assets/enemies.json", "assets/items.json", "assets/devices.json"}; +using namespace fuc2; - components::init(); - DinkyECS::World world; +namespace component_tests { + void test_confirm_component_loading_works() { + std::vector test_list{ + "assets/enemies.json", "assets/items.json", "assets/devices.json"}; - for(auto test_data : test_list) { - auto config = settings::get(test_data); - auto data_list = config.json(); + components::init(); + DinkyECS::World world; - for(auto& [key, data] : data_list.items()) { - auto& components = data["components"]; - fmt::println("TEST COMPONENT: {} from file {}", key, test_data); - auto ent = world.entity(); - components::configure_entity(world, ent, components); - auto tile = components::get(components[0]); - REQUIRE(tile.display != L' '); - } - } -} + for(auto test_data : test_list) { + auto config = settings::get(test_data); + auto data_list = config.json(); -TEST_CASE("make sure json_mods works", "[components]") { - auto config = settings::get("bosses"); - // this confirms that loading something with an optional - // field works with the json conversions in json_mods.hpp - for(auto& comp_data : config["RAT_KING"]["components"]) { - if(comp_data["_type"] == "AnimatedScene") { - auto comp = components::convert(comp_data); + for(auto& [key, data] : data_list.items()) { + auto& components = data["components"]; + fmt::println("TEST COMPONENT: {} from file {}", key, test_data); + auto ent = world.entity(); + components::configure_entity(world, ent, components); + auto tile = components::get(components[0]); + CHECK(tile.display != L' '); + } } } - // this then confirms everything else about the json conversion - components::init(); + void test_make_sure_json_mods_works() { + auto config = settings::get("bosses"); + // this confirms that loading something with an optional + // field works with the json conversions in json_mods.hpp + for(auto& comp_data : config["RAT_KING"]["components"]) { + if(comp_data["_type"] == "AnimatedScene") { + auto comp = components::convert(comp_data); + } + } - DinkyECS::World world; - auto rat_king = world.entity(); + // this then confirms everything else about the json conversion + components::init(); - components::configure_entity(world, rat_king, config["RAT_KING"]["components"]); - auto boss = world.get(rat_king); + DinkyECS::World world; + auto rat_king = world.entity(); + + components::configure_entity(world, rat_king, config["RAT_KING"]["components"]); + auto boss = world.get(rat_king); + } + + fuc2::Set TESTS{ + .name="components", + .tests={ + TEST(test_make_sure_json_mods_works), + TEST(test_confirm_component_loading_works), + } + }; } diff --git a/tests/config.cpp b/tests/config.cpp index cb7875f..f237b92 100644 --- a/tests/config.cpp +++ b/tests/config.cpp @@ -1,28 +1,40 @@ -#include +#include #include "game/config.hpp" #include -TEST_CASE("confirm basic config loader ops", "[config]") { - settings::Config::set_base_dir("./"); - auto config = settings::get("devices"); - auto data_list = config.json(); - auto the_keys = config.keys(); +using namespace fuc2; - REQUIRE(the_keys.size() > 0); +namespace config_tests { + void test_confirm_basic_config_loader_ops() { + settings::Config::set_base_dir("./"); + auto config = settings::get("devices"); + auto data_list = config.json(); + auto the_keys = config.keys(); - for(auto& [key, data] : data_list.items()) { - auto wide1 = config.wstring(key, "name"); - auto& comps = data["components"]; + CHECK(the_keys.size() > 0); - for(auto& comp_data : comps) { - REQUIRE(comp_data.contains("_type")); + for(auto& [key, data] : data_list.items()) { + auto wide1 = config.wstring(key, "name"); + auto& comps = data["components"]; + + for(auto& comp_data : comps) { + CHECK(comp_data.contains("_type")); + } } + + auto indexed = settings::get("tests/config_test.json"); + auto& test_0 = indexed[0]; + CHECK(test_0["test"] == 0); + + auto& test_1 = indexed[1]; + CHECK(test_1["test"] == 1); } - auto indexed = settings::get("tests/config_test.json"); - auto& test_0 = indexed[0]; - REQUIRE(test_0["test"] == 0); + fuc2::Set TESTS{ + .name="config", + .tests={ + TEST(test_confirm_basic_config_loader_ops), + } + }; - auto& test_1 = indexed[1]; - REQUIRE(test_1["test"] == 1); } diff --git a/tests/dbc.cpp b/tests/dbc.cpp index 5c7263d..d8523ce 100644 --- a/tests/dbc.cpp +++ b/tests/dbc.cpp @@ -1,18 +1,28 @@ -#include +#include #include "dbc.hpp" using namespace dbc; -TEST_CASE("basic feature tests", "[dbc]") { - log("Logging a message."); +namespace dbc_tests { + void test_basic_dbc_features() { + log("Logging a message."); - pre("confirm positive cases work", 1 == 1); + pre("confirm positive cases work", 1 == 1); - pre("confirm positive lambda", [&]{ return 1 == 1;}); + pre("confirm positive lambda", [&]{ return 1 == 1;}); - post("confirm positive post", 1 == 1); + post("confirm positive post", 1 == 1); - post("confirm postitive post with lamdba", [&]{ return 1 == 1;}); + post("confirm postitive post with lamdba", [&]{ return 1 == 1;}); + + check(1 == 1, "one equals 1"); + } + + fuc2::Set TESTS{ + .name="dbc", + .tests={ + TEST(test_basic_dbc_features), + } + }; - check(1 == 1, "one equals 1"); } diff --git a/tests/dinkyecs.cpp b/tests/dinkyecs.cpp index a0fb1fe..44fefe4 100644 --- a/tests/dinkyecs.cpp +++ b/tests/dinkyecs.cpp @@ -1,4 +1,4 @@ -#include +#include #include "algos/dinkyecs.hpp" #include #include @@ -6,207 +6,224 @@ using namespace fmt; using DinkyECS::Entity; using std::string; +using namespace fuc2; -struct Point { - size_t x; - size_t y; -}; +namespace dinkyecs_tests { + struct Point { + size_t x; + size_t y; + }; -struct Player { - string name; - Entity eid; -}; + struct Player { + string name; + Entity eid; + }; -struct Position { - Point location; -}; + struct Position { + Point location; + }; -struct Motion { - int dx; - int dy; - bool random=false; -}; + struct Motion { + int dx; + int dy; + bool random=false; + }; -struct Velocity { - double x, y; -}; + struct Velocity { + double x, y; + }; -struct Gravity { - double level; -}; + struct Gravity { + double level; + }; -struct DaGUI { - int event; -}; + struct DaGUI { + int event; + }; -/* - * Using a function catches instances where I'm not copying - * the data into the world. - */ -void configure(DinkyECS::World &world, Entity &test) { - println("---Configuring the base system."); - Entity test2 = world.entity(); + /* + * Using a function catches instances where I'm not copying + * the data into the world. + */ + void configure(DinkyECS::World &world, Entity &test) { + println("---Configuring the base system."); + Entity test2 = world.entity(); - world.set(test, {10,20}); - world.set(test, {1,2}); + world.set(test, {10,20}); + world.set(test, {1,2}); - world.set(test2, {1,1}); - world.set(test2, {9,19}); + world.set(test2, {1,1}); + world.set(test2, {9,19}); - println("---- Setting up the player as a fact in the system."); + println("---- Setting up the player as a fact in the system."); - auto player_eid = world.entity(); - Player player_info{"Zed", player_eid}; - // just set some player info as a fact with the entity id - world.set_the(player_info); + auto player_eid = world.entity(); + Player player_info{"Zed", player_eid}; + // just set some player info as a fact with the entity id + world.set_the(player_info); - world.set(player_eid, {0,0}); - world.set(player_eid, {0,0}); + world.set(player_eid, {0,0}); + world.set(player_eid, {0,0}); - auto enemy = world.entity(); - world.set(enemy, {0,0}); - world.set(enemy, {0,0}); + auto enemy = world.entity(); + world.set(enemy, {0,0}); + world.set(enemy, {0,0}); - println("--- Creating facts (singletons)"); - world.set_the({0.9}); -} - -TEST_CASE("confirm ECS system works", "[ecs]") { - DinkyECS::World world; - Entity test = world.entity(); - - configure(world, test); - - Position &pos = world.get(test); - REQUIRE(pos.location.x == 10); - REQUIRE(pos.location.y == 20); - - Velocity &vel = world.get(test); - REQUIRE(vel.x == 1); - REQUIRE(vel.y == 2); - - world.query([](const auto &ent, auto &pos) { - REQUIRE(ent > 0); - REQUIRE(pos.location.x >= 0); - REQUIRE(pos.location.y >= 0); - }); - - world.query([](const auto &ent, auto &vel) { - REQUIRE(ent > 0); - REQUIRE(vel.x >= 0); - REQUIRE(vel.y >= 0); - }); - - println("--- Manually get the velocity in position system:"); - world.query([&](const auto &ent, auto &pos) { - Velocity &vel = world.get(ent); - - REQUIRE(ent > 0); - REQUIRE(pos.location.x >= 0); - REQUIRE(pos.location.y >= 0); - REQUIRE(ent > 0); - REQUIRE(vel.x >= 0); - REQUIRE(vel.y >= 0); - }); - - println("--- Query only entities with Position and Velocity:"); - world.query([&](const auto &ent, auto &pos, auto &vel) { - Gravity &grav = world.get_the(); - REQUIRE(grav.level <= 1.0f); - REQUIRE(grav.level > 0.5f); - REQUIRE(ent > 0); - REQUIRE(pos.location.x >= 0); - REQUIRE(pos.location.y >= 0); - REQUIRE(ent > 0); - REQUIRE(vel.x >= 0); - REQUIRE(vel.y >= 0); - }); - - // now remove Velocity - REQUIRE(world.has(test)); - world.remove(test); - REQUIRE_THROWS(world.get(test)); - REQUIRE(!world.has(test)); - - println("--- After remove test, should only result in test2:"); - world.query([&](const auto &ent, auto &pos, auto &vel) { - auto &in_position = world.get(ent); - auto &in_velocity = world.get(ent); - REQUIRE(pos.location.x >= 0); - REQUIRE(pos.location.y >= 0); - REQUIRE(in_position.location.x == pos.location.x); - REQUIRE(in_position.location.y == pos.location.y); - REQUIRE(in_velocity.x == vel.x); - REQUIRE(in_velocity.y == vel.y); - }); -} - -enum GUIEvent { - HIT, MISS -}; - -TEST_CASE("confirm that the event system works", "[ecs]") { - DinkyECS::World world; - DinkyECS::Entity player = world.entity(); - - // this confirms we can send these in a for-loop and get them out - int i = 0; - for(; i < 10; i++) { - world.send(GUIEvent::HIT, player, string{"hello"}); + println("--- Creating facts (singletons)"); + world.set_the({0.9}); } - // just count down and should get the same number - while(world.has_event()) { - auto [event, entity, data] = world.recv(); - REQUIRE(event == GUIEvent::HIT); - REQUIRE(entity == player); - auto &str_data = std::any_cast(data); - REQUIRE(string{"hello"} == str_data); - i--; + void test_confirm_ECS_system_works() { + DinkyECS::World world; + Entity test = world.entity(); + + configure(world, test); + + Position &pos = world.get(test); + CHECK(pos.location.x == 10); + CHECK(pos.location.y == 20); + + Velocity &vel = world.get(test); + CHECK(vel.x == 1); + CHECK(vel.y == 2); + + world.query([](const auto &ent, auto &pos) { + CHECK(ent > 0); + CHECK(pos.location.x >= 0); + CHECK(pos.location.y >= 0); + }); + + world.query([](const auto &ent, auto &vel) { + CHECK(ent > 0); + CHECK(vel.x >= 0); + CHECK(vel.y >= 0); + }); + + println("--- Manually get the velocity in position system:"); + world.query([&](const auto &ent, auto &pos) { + Velocity &vel = world.get(ent); + + CHECK(ent > 0); + CHECK(pos.location.x >= 0); + CHECK(pos.location.y >= 0); + CHECK(ent > 0); + CHECK(vel.x >= 0); + CHECK(vel.y >= 0); + }); + + println("--- Query only entities with Position and Velocity:"); + world.query([&](const auto &ent, auto &pos, auto &vel) { + Gravity &grav = world.get_the(); + CHECK(grav.level <= 1.0f); + CHECK(grav.level > 0.5f); + CHECK(ent > 0); + CHECK(pos.location.x >= 0); + CHECK(pos.location.y >= 0); + CHECK(ent > 0); + CHECK(vel.x >= 0); + CHECK(vel.y >= 0); + }); + + // now remove Velocity + CHECK(world.has(test)); + world.remove(test); + + BLOWS_UP([&]() { + world.get(test); + }); + + CHECK(!world.has(test)); + + println("--- After remove test, should only result in test2:"); + world.query([&](const auto &ent, auto &pos, auto &vel) { + auto &in_position = world.get(ent); + auto &in_velocity = world.get(ent); + CHECK(pos.location.x >= 0); + CHECK(pos.location.y >= 0); + CHECK(in_position.location.x == pos.location.x); + CHECK(in_position.location.y == pos.location.y); + CHECK(in_velocity.x == vel.x); + CHECK(in_velocity.y == vel.y); + }); } - REQUIRE(i == 0); -} - - -TEST_CASE("confirm copying and constants", "[ecs-constants]") { - DinkyECS::World world1; - - Player player_info{"Zed", world1.entity()}; - world1.set_the(player_info); - - world1.set(player_info.eid, {10,10}); - world1.make_constant(player_info.eid); - - DinkyECS::World world2; - world1.clone_into(world2); - - auto &test1 = world1.get(player_info.eid); - auto &test2 = world2.get(player_info.eid); - - REQUIRE(test2.location.x == test1.location.x); - REQUIRE(test2.location.y == test1.location.y); - - // check for accidental reference - test1.location.x = 100; - REQUIRE(test2.location.x != test1.location.x); - - // test the facts copy over - auto &player2 = world2.get_the(); - REQUIRE(player2.eid == player_info.eid); -} - -TEST_CASE("can destroy all entity", "[ecs-destroy]") { - DinkyECS::World world; - auto entity = world.entity(); - - world.set(entity, {10,10}); - world.set(entity, {1}); - world.set(entity, {0,0}); - - world.destroy(entity); - - REQUIRE(!world.has(entity)); - REQUIRE(!world.has(entity)); - REQUIRE(!world.has(entity)); + enum GUIEvent { + HIT, MISS + }; + + void test_confirm_that_the_event_system_works() { + DinkyECS::World world; + DinkyECS::Entity player = world.entity(); + + // this confirms we can send these in a for-loop and get them out + int i = 0; + for(; i < 10; i++) { + world.send(GUIEvent::HIT, player, string{"hello"}); + } + + // just count down and should get the same number + while(world.has_event()) { + auto [event, entity, data] = world.recv(); + CHECK(event == GUIEvent::HIT); + CHECK(entity == player); + auto &str_data = std::any_cast(data); + CHECK(string{"hello"} == str_data); + i--; + } + + CHECK(i == 0); + } + + + void test_confirm_copying_and_constants() { + DinkyECS::World world1; + + Player player_info{"Zed", world1.entity()}; + world1.set_the(player_info); + + world1.set(player_info.eid, {10,10}); + world1.make_constant(player_info.eid); + + DinkyECS::World world2; + world1.clone_into(world2); + + auto &test1 = world1.get(player_info.eid); + auto &test2 = world2.get(player_info.eid); + + CHECK(test2.location.x == test1.location.x); + CHECK(test2.location.y == test1.location.y); + + // check for accidental reference + test1.location.x = 100; + CHECK(test2.location.x != test1.location.x); + + // test the facts copy over + auto &player2 = world2.get_the(); + CHECK(player2.eid == player_info.eid); + } + + void test_can_destroy_all_entity() { + DinkyECS::World world; + auto entity = world.entity(); + + world.set(entity, {10,10}); + world.set(entity, {1}); + world.set(entity, {0,0}); + + world.destroy(entity); + + CHECK(!world.has(entity)); + CHECK(!world.has(entity)); + CHECK(!world.has(entity)); + } + + fuc2::Set TESTS{ + .name="dinkyecs", + .tests={ + TEST(test_confirm_ECS_system_works), + TEST(test_confirm_that_the_event_system_works), + TEST(test_confirm_copying_and_constants), + TEST(test_can_destroy_all_entity), + } + }; } diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..d3f60f9 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,25 @@ +#include + +using namespace fuc2; + +TEST_SET(ai_tests); +TEST_SET(animation_tests); +TEST_SET(base_tests); +TEST_SET(battle_tests); +TEST_SET(camera_tests); +TEST_SET(component_tests); +TEST_SET(config_tests); +TEST_SET(dbc_tests); +TEST_SET(dinkyecs_tests); + +int main(int argc, char* argv[]) { + run(ai_tests::TESTS); + run(animation_tests::TESTS); + run(base_tests::TESTS); + run(battle_tests::TESTS); + run(camera_tests::TESTS); + run(component_tests::TESTS); + run(config_tests::TESTS); + run(dbc_tests::TESTS); + run(dinkyecs_tests::TESTS); +} diff --git a/tests/meson.build b/tests/meson.build index 35a3d43..07d4a87 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,12 +1,4 @@ tests = files( - 'animation.cpp', - 'base.cpp', - 'battle.cpp', - 'camera.cpp', - 'components.cpp', - 'config.cpp', - 'dbc.cpp', - 'dinkyecs.cpp', 'event_router.cpp', 'fsm.cpp', 'inventory.cpp', @@ -27,6 +19,14 @@ tests = files( ) fuc2_tests = files( + 'dinkyecs.cpp', + 'dbc.cpp', 'ai.cpp', + 'animation.cpp', + 'base.cpp', + 'battle.cpp', + 'camera.cpp', + 'components.cpp', + 'config.cpp', 'main.cpp' )