diff options
Diffstat (limited to 'src/test')
30 files changed, 3872 insertions, 16 deletions
diff --git a/src/test/AssetTest.cpp b/src/test/AssetTest.cpp new file mode 100644 index 0000000..93fd6a9 --- /dev/null +++ b/src/test/AssetTest.cpp @@ -0,0 +1,21 @@ +#include <gtest/gtest.h> + +#include <crepe/api/Asset.h> +#include <crepe/api/Config.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +TEST(AssetTest, Existant) { ASSERT_NO_THROW(Asset{"asset/texture/img.png"}); } + +TEST(AssetTest, Nonexistant) { ASSERT_ANY_THROW(Asset{"asset/nonexistant"}); } + +TEST(AssetTest, Rootless) { + Config & cfg = Config::get_instance(); + cfg.asset.root_pattern.clear(); + + string arbitrary = "\\/this is / /../passed through as-is"; + Asset asset{arbitrary}; + ASSERT_EQ(arbitrary, asset.get_path()); +} diff --git a/src/test/AudioTest.cpp b/src/test/AudioTest.cpp new file mode 100644 index 0000000..415a12e --- /dev/null +++ b/src/test/AudioTest.cpp @@ -0,0 +1,161 @@ +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <crepe/api/AudioSource.h> +#include <crepe/api/GameObject.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/system/AudioSystem.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class AudioTest : public Test { +private: + class TestSoundContext : public SoundContext { + public: + MOCK_METHOD(SoundHandle, play, (Sound & resource), (override)); + MOCK_METHOD(void, stop, (const SoundHandle &), (override)); + MOCK_METHOD(void, set_volume, (const SoundHandle &, float), (override)); + MOCK_METHOD(void, set_loop, (const SoundHandle &, bool), (override)); + }; + + class TestAudioSystem : public AudioSystem { + public: + using AudioSystem::AudioSystem; + StrictMock<TestSoundContext> context; + virtual SoundContext & get_context() { return this->context; } + }; + +private: + Mediator mediator; + ComponentManager component_manager{mediator}; + ResourceManager resource_manager{mediator}; + +public: + TestAudioSystem system{mediator}; + TestSoundContext & context = system.context; + +private: + GameObject entity = component_manager.new_object("name"); + +public: + AudioSource & component = entity.add_component<AudioSource>("mwe/audio/bgm.ogg"); +}; + +TEST_F(AudioTest, Default) { + EXPECT_CALL(context, play(_)).Times(0); + EXPECT_CALL(context, stop(_)).Times(0); + EXPECT_CALL(context, set_volume(_, _)).Times(0); + EXPECT_CALL(context, set_loop(_, _)).Times(0); + system.fixed_update(); +} + +TEST_F(AudioTest, Play) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, play(_)).Times(0); + component.play(); + } + + { + InSequence seq; + + EXPECT_CALL(context, play(_)).Times(1); + system.fixed_update(); + } +} + +TEST_F(AudioTest, Stop) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, stop(_)).Times(0); + component.stop(); + } + + { + InSequence seq; + + EXPECT_CALL(context, stop(_)).Times(1); + system.fixed_update(); + } +} + +TEST_F(AudioTest, Volume) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, set_volume(_, _)).Times(0); + component.volume += 0.2; + } + + { + InSequence seq; + + EXPECT_CALL(context, set_volume(_, component.volume)).Times(1); + system.fixed_update(); + } +} + +TEST_F(AudioTest, Looping) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, set_loop(_, _)).Times(0); + component.loop = !component.loop; + } + + { + InSequence seq; + + EXPECT_CALL(context, set_loop(_, component.loop)).Times(1); + system.fixed_update(); + } +} + +TEST_F(AudioTest, StopOnDeactivate) { + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, stop(_)).Times(1); + component.active = false; + system.fixed_update(); + } +} + +TEST_F(AudioTest, PlayOnActive) { + component.active = false; + component.play_on_awake = true; + system.fixed_update(); + + { + InSequence seq; + + EXPECT_CALL(context, play(_)).Times(1); + component.active = true; + system.fixed_update(); + } +} + +TEST_F(AudioTest, PlayImmediately) { + component.play_on_awake = false; + component.play(); + + EXPECT_CALL(context, play(_)).Times(1); + + system.fixed_update(); +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 0d316d6..ea92d96 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,5 +1,28 @@ target_sources(test_main PUBLIC - dummy.cpp - # audio.cpp + main.cpp + CollisionTest.cpp + PhysicsTest.cpp + ScriptTest.cpp + ParticleTest.cpp + AudioTest.cpp + AssetTest.cpp + ResourceManagerTest.cpp + OptionalRefTest.cpp + RenderSystemTest.cpp + EventTest.cpp + ECSTest.cpp + SceneManagerTest.cpp + ValueBrokerTest.cpp + DBTest.cpp + Vector2Test.cpp + # LoopManagerTest.cpp + LoopTimerTest.cpp + InputTest.cpp + ScriptEventTest.cpp + ScriptSceneTest.cpp + Profiling.cpp + SaveManagerTest.cpp + ScriptSaveManagerTest.cpp + ScriptECSTest.cpp + ReplayManagerTest.cpp ) - diff --git a/src/test/CollisionTest.cpp b/src/test/CollisionTest.cpp new file mode 100644 index 0000000..73a964a --- /dev/null +++ b/src/test/CollisionTest.cpp @@ -0,0 +1,392 @@ +#include "api/BoxCollider.h" +#include "manager/Mediator.h" +#include <cmath> +#include <cstddef> +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Transform.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/Mediator.h> +#include <crepe/system/CollisionSystem.h> +#include <crepe/system/ScriptSystem.h> +#include <crepe/types.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class CollisionHandler : public Script { +public: + int box_id; + function<void(const CollisionEvent & ev)> test_fn = [](const CollisionEvent & ev) {}; + + CollisionHandler(int box_id) { this->box_id = box_id; } + + bool on_collision(const CollisionEvent & ev) { + //Log::logf("Box {} script on_collision()", box_id); + test_fn(ev); + return true; + } + + void init() { + subscribe<CollisionEvent>( + [this](const CollisionEvent & ev) -> bool { return this->on_collision(ev); }); + } + void update() { + // Retrieve component from the same GameObject this script is on + } +}; + +class CollisionTest : public Test { +public: + Mediator m; + EventManager event_mgr{m}; + ComponentManager mgr{m}; + CollisionSystem collision_sys{m}; + ScriptSystem script_sys{m}; + LoopTimerManager loop_timer{m}; + + GameObject world = mgr.new_object("world", "", {50, 50}); + GameObject game_object1 = mgr.new_object("object1", "", {50, 50}); + GameObject game_object2 = mgr.new_object("object2", "", {50, 30}); + + CollisionHandler * script_object1_ref = nullptr; + CollisionHandler * script_object2_ref = nullptr; + + void SetUp() override { + world.add_component<Rigidbody>(Rigidbody::Data{ + // TODO: remove unrelated properties: + .body_type = Rigidbody::BodyType::STATIC, + .offset = {0, 0}, + }); + // Create a box with an inner size of 10x10 units + world.add_component<BoxCollider>(vec2{100, 100}, vec2{0, -100}); // Top + world.add_component<BoxCollider>(vec2{100, 100}, vec2{0, 100}); // Bottom + world.add_component<BoxCollider>(vec2{100, 100}, vec2{-100, 0}); // Left + world.add_component<BoxCollider>(vec2{100, 100}, vec2{100, 0}); // right + + game_object1.add_component<Rigidbody>(Rigidbody::Data{ + .mass = 1, + .gravity_scale = 0.01, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = {0, 0}, + .constraints = {0, 0, 0}, + .elastisity_coefficient = 1, + .offset = {0, 0}, + .collision_layers = {0}, + }); + game_object1.add_component<BoxCollider>(vec2{10, 10}, vec2{0, 0}); + BehaviorScript & script_object1 + = game_object1.add_component<BehaviorScript>().set_script<CollisionHandler>(1); + script_object1_ref = static_cast<CollisionHandler *>(script_object1.script.get()); + ASSERT_NE(script_object1_ref, nullptr); + + game_object2.add_component<Rigidbody>(Rigidbody::Data{ + .mass = 1, + .gravity_scale = 0.01, + .body_type = Rigidbody::BodyType::DYNAMIC, + .linear_velocity = {0, 0}, + .constraints = {0, 0, 0}, + .elastisity_coefficient = 1, + .offset = {0, 0}, + .collision_layers = {0}, + }); + game_object2.add_component<BoxCollider>(vec2{10, 10}, vec2{0, 0}); + BehaviorScript & script_object2 + = game_object2.add_component<BehaviorScript>().set_script<CollisionHandler>(2); + script_object2_ref = static_cast<CollisionHandler *>(script_object2.script.get()); + ASSERT_NE(script_object2_ref, nullptr); + + // Ensure Script::init() is called on all BehaviorScript instances + script_sys.fixed_update(); + } +}; + +TEST_F(CollisionTest, collision_example) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + }; + EXPECT_FALSE(collision_happend); + collision_sys.fixed_update(); + EXPECT_FALSE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_both_no_velocity) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 10); + EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, 10); + EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 30}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_x_direction_no_velocity) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, -5); + EXPECT_EQ(ev.info.resolution.y, 0); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::X_DIRECTION); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, 5); + EXPECT_EQ(ev.info.resolution.y, 0); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::X_DIRECTION); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {45, 30}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_y_direction_no_velocity) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 0); + EXPECT_EQ(ev.info.resolution.y, -5); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::Y_DIRECTION); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, 0); + EXPECT_EQ(ev.info.resolution.y, 5); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::Y_DIRECTION); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 25}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_both) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 10); + EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, 10); + EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 30}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.linear_velocity = {10, 10}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_x_direction) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, -5); + EXPECT_EQ(ev.info.resolution.y, 5); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::X_DIRECTION); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, 5); + EXPECT_EQ(ev.info.resolution.y, -5); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::X_DIRECTION); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {45, 30}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.linear_velocity = {10, 10}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_y_direction) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 5); + EXPECT_EQ(ev.info.resolution.y, -5); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::Y_DIRECTION); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 2); + EXPECT_EQ(ev.info.resolution.x, -5); + EXPECT_EQ(ev.info.resolution.y, 5); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::Y_DIRECTION); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 25}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.linear_velocity = {10, 10}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_both) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 10); + EXPECT_EQ(ev.info.resolution.y, 10); + EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + // is static should not be called + FAIL(); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 30}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_x_direction) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, -5); + EXPECT_EQ(ev.info.resolution.y, 5); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::X_DIRECTION); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + // is static should not be called + FAIL(); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {45, 30}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_y_direction) { + bool collision_happend = false; + script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.resolution.x, 5); + EXPECT_EQ(ev.info.resolution.y, -5); + EXPECT_EQ(ev.info.resolution_direction, + crepe::CollisionSystem::Direction::Y_DIRECTION); + }; + script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { + // is static should not be called + FAIL(); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {50, 25}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_multiple) { //todo check visually + bool collision_happend = false; + float offset_value = 0; + float resolution = 0; + script_object1_ref->test_fn = [&](const CollisionEvent & ev) { + collision_happend = true; + EXPECT_EQ(ev.info.this_collider.game_object_id, 1); + EXPECT_EQ(ev.info.this_collider.offset.x, offset_value); + EXPECT_EQ(ev.info.resolution.x, resolution); + }; + script_object2_ref->test_fn = [&](const CollisionEvent & ev) { + // is static should not be called + FAIL(); + }; + EXPECT_FALSE(collision_happend); + Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); + tf.position = {45, 30}; + Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); + rg1.data.linear_velocity = {10, 10}; + Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); + rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; + BoxCollider & bxc = this->mgr.get_components_by_id<BoxCollider>(1).front().get(); + bxc.offset = {5, 0}; + this->game_object1.add_component<BoxCollider>(vec2{-5, 0}, vec2{10, 10}); + offset_value = 5; + resolution = 10; + collision_sys.fixed_update(); + offset_value = -5; + resolution = 10; + tf.position = {55, 30}; + collision_sys.fixed_update(); + EXPECT_TRUE(collision_happend); +} diff --git a/src/test/DBTest.cpp b/src/test/DBTest.cpp new file mode 100644 index 0000000..7f2c339 --- /dev/null +++ b/src/test/DBTest.cpp @@ -0,0 +1,37 @@ +#include <gtest/gtest.h> + +#include <crepe/facade/DB.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class DBTest : public Test { +public: + DB db; +}; + +TEST_F(DBTest, ReadWrite) { + db.set("foo", "bar"); + EXPECT_EQ(db.get("foo"), "bar"); +} + +TEST_F(DBTest, Nonexistant) { + EXPECT_THROW(db.get("foo"), std::out_of_range); + db.set("foo", "bar"); + EXPECT_NO_THROW(db.get("foo")); +} + +TEST_F(DBTest, Has) { + EXPECT_EQ(db.has("foo"), false); + db.set("foo", "bar"); + EXPECT_EQ(db.has("foo"), true); +} + +TEST_F(DBTest, MultipleKeys) { + db.set("foo", "foo"); + db.set("bar", "bar"); + + EXPECT_EQ(db.get("foo"), "foo"); + EXPECT_EQ(db.get("bar"), "bar"); +} diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp new file mode 100644 index 0000000..8f86a91 --- /dev/null +++ b/src/test/ECSTest.cpp @@ -0,0 +1,482 @@ +#include <gtest/gtest.h> + +#define protected public +#define private public + +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> +#include <crepe/manager/ComponentManager.h> + +using namespace std; +using namespace crepe; + +class ECSTest : public ::testing::Test { + Mediator m; + +public: + ComponentManager mgr{m}; + + class TestComponent : public Component { + using Component::Component; + }; +}; + +TEST_F(ECSTest, createGameObject) { + GameObject obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().name, "body"); + EXPECT_EQ(metadata[0].get().tag, "person"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); + EXPECT_EQ(transform[0].get().rotation, 0); + EXPECT_EQ(transform[0].get().scale, 1); +} + +TEST_F(ECSTest, deleteAllGameObjects) { + GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + + mgr.delete_all_components(); + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 0); + EXPECT_EQ(transform.size(), 0); + + GameObject obj2 = mgr.new_object("body2", "person2", vec2{1, 0}, 5, 1); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "body2"); + EXPECT_EQ(metadata[0].get().tag, "person2"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + + EXPECT_EQ(transform[0].get().game_object_id, 0); + EXPECT_EQ(transform[0].get().position.x, 1); + EXPECT_EQ(transform[0].get().position.y, 0); + EXPECT_EQ(transform[0].get().rotation, 5); + EXPECT_EQ(transform[0].get().scale, 1); +} + +TEST_F(ECSTest, deleteGameObject) { + GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + + mgr.delete_all_components_of_id(0); + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().game_object_id, 1); + EXPECT_EQ(metadata[0].get().name, "body"); + EXPECT_EQ(metadata[0].get().tag, "person"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + + EXPECT_EQ(transform[0].get().game_object_id, 1); + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); + EXPECT_EQ(transform[0].get().rotation, 0); + EXPECT_EQ(transform[0].get().scale, 1); +} + +TEST_F(ECSTest, manyGameObjects) { + for (int i = 0; i < 5000; i++) { + GameObject obj = mgr.new_object("body", "person", vec2{0, 0}, 0, i); + } + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 5000); + EXPECT_EQ(transform.size(), 5000); + for (int i = 0; i < 5000; i++) { + EXPECT_EQ(metadata[i].get().game_object_id, i); + EXPECT_EQ(metadata[i].get().name, "body"); + EXPECT_EQ(metadata[i].get().tag, "person"); + EXPECT_EQ(metadata[i].get().parent, -1); + EXPECT_EQ(metadata[i].get().children.size(), 0); + + EXPECT_EQ(transform[i].get().game_object_id, i); + EXPECT_EQ(transform[i].get().position.x, 0); + EXPECT_EQ(transform[i].get().position.y, 0); + EXPECT_EQ(transform[i].get().rotation, 0); + EXPECT_EQ(transform[i].get().scale, i); + } + + mgr.delete_components<Metadata>(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 0); + EXPECT_EQ(transform.size(), 5000); + + for (int i = 0; i < 10000 - 5000; i++) { + string tag = "person" + to_string(i); + GameObject obj = mgr.new_object("body", tag, vec2{0, 0}, i, 0); + } + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 10000 - 5000); + EXPECT_EQ(transform.size(), 10000); + + for (int i = 0; i < 10000 - 5000; i++) { + EXPECT_EQ(metadata[i].get().game_object_id, i + 5000); + EXPECT_EQ(metadata[i].get().name, "body"); + EXPECT_EQ(metadata[i].get().tag, "person" + to_string(i)); + EXPECT_EQ(metadata[i].get().parent, -1); + EXPECT_EQ(metadata[i].get().children.size(), 0); + + EXPECT_EQ(transform[i].get().game_object_id, i); + EXPECT_EQ(transform[i].get().position.x, 0); + EXPECT_EQ(transform[i].get().position.y, 0); + EXPECT_EQ(transform[i].get().rotation, 0); + EXPECT_EQ(transform[i].get().scale, i); + } + + mgr.delete_all_components(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 0); + EXPECT_EQ(transform.size(), 0); + + for (int i = 0; i < 10000; i++) { + string name = "body" + to_string(i); + GameObject obj = mgr.new_object(name, "person", vec2{0, 0}, 0, 0); + } + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 10000); + EXPECT_EQ(transform.size(), 10000); + + for (int i = 0; i < 10000; i++) { + EXPECT_EQ(metadata[i].get().game_object_id, i); + EXPECT_EQ(metadata[i].get().name, "body" + to_string(i)); + EXPECT_EQ(metadata[i].get().tag, "person"); + EXPECT_EQ(metadata[i].get().parent, -1); + EXPECT_EQ(metadata[i].get().children.size(), 0); + + EXPECT_EQ(transform[i].get().game_object_id, i); + EXPECT_EQ(transform[i].get().position.x, 0); + EXPECT_EQ(transform[i].get().position.y, 0); + EXPECT_EQ(transform[i].get().rotation, 0); + EXPECT_EQ(transform[i].get().scale, 0); + } +} + +TEST_F(ECSTest, getComponentsByID) { + GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_id<Metadata>(0); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_id<Transform>(1); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "body"); + EXPECT_EQ(metadata[0].get().tag, "person"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + + EXPECT_EQ(transform[0].get().game_object_id, 1); + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); + EXPECT_EQ(transform[0].get().rotation, 0); + EXPECT_EQ(transform[0].get().scale, 1); +} + +TEST_F(ECSTest, tooMuchComponents) { + try { + GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + obj0.add_component<Transform>(vec2{10, 10}, 0, 1); + } catch (const exception & e) { + EXPECT_EQ(e.what(), + string("Exceeded maximum number of instances for this component type")); + } + + try { + GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + obj1.add_component<Metadata>("body", "person"); + } catch (const exception & e) { + EXPECT_EQ(e.what(), + string("Exceeded maximum number of instances for this component type")); + } + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + + EXPECT_EQ(metadata.size(), 2); + EXPECT_EQ(metadata[0].get().name, "body"); + EXPECT_EQ(metadata[1].get().name, "body"); +} + +TEST_F(ECSTest, partentChild) { + { + GameObject body = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + GameObject right_leg = mgr.new_object("rightLeg", "person", vec2{1, 1}, 0, 1); + GameObject left_leg = mgr.new_object("leftLeg", "person", vec2{1, 1}, 0, 1); + GameObject right_foot = mgr.new_object("rightFoot", "person", vec2{2, 2}, 0, 1); + GameObject left_foot = mgr.new_object("leftFoot", "person", vec2{2, 2}, 0, 1); + + // Set the parent of each GameObject + right_foot.set_parent(right_leg); + left_foot.set_parent(left_leg); + right_leg.set_parent(body); + left_leg.set_parent(body); + } + + // Get the Metadata and Transform components of each GameObject + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + + // Check IDs + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[1].get().game_object_id, 1); + EXPECT_EQ(metadata[2].get().game_object_id, 2); + EXPECT_EQ(metadata[3].get().game_object_id, 3); + EXPECT_EQ(metadata[4].get().game_object_id, 4); + + // Check the parent-child relationships + EXPECT_EQ(metadata[0].get().name, "body"); + EXPECT_EQ(metadata[1].get().name, "rightLeg"); + EXPECT_EQ(metadata[2].get().name, "leftLeg"); + EXPECT_EQ(metadata[3].get().name, "rightFoot"); + EXPECT_EQ(metadata[4].get().name, "leftFoot"); + + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[1].get().parent, 0); + EXPECT_EQ(metadata[2].get().parent, 0); + EXPECT_EQ(metadata[3].get().parent, 1); + EXPECT_EQ(metadata[4].get().parent, 2); + + EXPECT_EQ(metadata[0].get().children.size(), 2); + EXPECT_EQ(metadata[1].get().children.size(), 1); + EXPECT_EQ(metadata[2].get().children.size(), 1); + EXPECT_EQ(metadata[3].get().children.size(), 0); + EXPECT_EQ(metadata[4].get().children.size(), 0); + + EXPECT_EQ(metadata[0].get().children[0], 1); + EXPECT_EQ(metadata[0].get().children[1], 2); + EXPECT_EQ(metadata[1].get().children[0], 3); + EXPECT_EQ(metadata[2].get().children[0], 4); +} + +TEST_F(ECSTest, persistent) { + GameObject obj0 = mgr.new_object("obj0", "obj0", vec2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("obj1", "obj1", vec2{0, 0}, 0, 1); + obj1.set_persistent(); + GameObject obj2 = mgr.new_object("obj2", "obj2", vec2{0, 0}, 0, 1); + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 3); + EXPECT_EQ(transform.size(), 3); + + mgr.delete_components_by_id<Metadata>(1); + mgr.delete_components<Metadata>(); + mgr.delete_all_components_of_id(1); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 3); + + mgr.delete_all_components(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().game_object_id, 1); + EXPECT_EQ(metadata[0].get().name, "obj1"); + EXPECT_EQ(metadata[0].get().tag, "obj1"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + + EXPECT_EQ(transform[0].get().game_object_id, 1); + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); + + GameObject obj3 = mgr.new_object("obj3", "obj3", vec2{0, 0}, 0, 5); + GameObject obj4 = mgr.new_object("obj4", "obj4", vec2{0, 0}, 0, 5); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 3); + EXPECT_EQ(transform.size(), 3); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "obj3"); + + EXPECT_EQ(metadata[1].get().game_object_id, 1); + EXPECT_EQ(metadata[1].get().name, "obj1"); + + EXPECT_EQ(metadata[2].get().game_object_id, 2); + EXPECT_EQ(metadata[2].get().name, "obj4"); + + EXPECT_EQ(transform[0].get().game_object_id, 0); + EXPECT_EQ(transform[0].get().scale, 5); + + EXPECT_EQ(transform[1].get().game_object_id, 1); + EXPECT_EQ(transform[1].get().scale, 1); + + EXPECT_EQ(transform[2].get().game_object_id, 2); + EXPECT_EQ(transform[2].get().scale, 5); +} + +TEST_F(ECSTest, resetPersistent) { + GameObject obj0 = mgr.new_object("obj0", "obj0", vec2{0, 0}, 0, 1); + GameObject obj1 = mgr.new_object("obj1", "obj1", vec2{0, 0}, 0, 1); + obj1.set_persistent(); + GameObject obj2 = mgr.new_object("obj2", "obj2", vec2{0, 0}, 0, 1); + + vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 3); + EXPECT_EQ(transform.size(), 3); + + mgr.delete_all_components(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + vector<reference_wrapper<Metadata>> metadata_id = mgr.get_components_by_id<Metadata>(1); + + EXPECT_EQ(metadata_id.size(), 1); + EXPECT_EQ(metadata_id[0].get().game_object_id, 1); + EXPECT_EQ(metadata_id[0].get().name, "obj1"); + + mgr.set_persistent(1, false); + mgr.delete_all_components(); + + metadata = mgr.get_components_by_type<Metadata>(); + transform = mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 0); + EXPECT_EQ(transform.size(), 0); +} + +TEST_F(ECSTest, IDByName) { + GameObject foo = mgr.new_object("foo"); + GameObject bar = mgr.new_object("bar"); + + { + auto objects = mgr.get_objects_by_name(""); + EXPECT_EQ(objects.size(), 0); + } + + { + auto objects = mgr.get_objects_by_name("foo"); + EXPECT_EQ(objects.size(), 1); + EXPECT_TRUE(objects.contains(foo.id)); + } +} + +TEST_F(ECSTest, IDByTag) { + GameObject foo = mgr.new_object("foo", "common tag"); + GameObject bar = mgr.new_object("bar", "common tag"); + + { + auto objects = mgr.get_objects_by_tag(""); + EXPECT_EQ(objects.size(), 0); + } + + { + auto objects = mgr.get_objects_by_tag("common tag"); + EXPECT_EQ(objects.size(), 2); + EXPECT_TRUE(objects.contains(foo.id)); + EXPECT_TRUE(objects.contains(bar.id)); + } +} + +TEST_F(ECSTest, ComponentsByName) { + GameObject foo = mgr.new_object("foo"); + foo.add_component<TestComponent>(); + GameObject bar = mgr.new_object("bar"); + bar.add_component<TestComponent>(); + bar.add_component<TestComponent>(); + + { + auto objects = mgr.get_components_by_name<TestComponent>(""); + EXPECT_EQ(objects.size(), 0); + } + + { + auto objects = mgr.get_components_by_name<TestComponent>("foo"); + EXPECT_EQ(objects.size(), 1); + } + + { + auto objects = mgr.get_components_by_name<TestComponent>("bar"); + EXPECT_EQ(objects.size(), 2); + } +} + +TEST_F(ECSTest, ComponentsByTag) { + GameObject foo = mgr.new_object("foo", "common tag"); + foo.add_component<TestComponent>(); + GameObject bar = mgr.new_object("bar", "common tag"); + bar.add_component<TestComponent>(); + bar.add_component<TestComponent>(); + + { + auto objects = mgr.get_components_by_tag<TestComponent>(""); + EXPECT_EQ(objects.size(), 0); + } + + { + auto objects = mgr.get_components_by_tag<TestComponent>("common tag"); + EXPECT_EQ(objects.size(), 3); + } +} + +TEST_F(ECSTest, Snapshot) { + GameObject foo = mgr.new_object("foo"); + + foo.transform.position = {1, 1}; + + ComponentManager::Snapshot snapshot = mgr.save(); + + foo.transform.position = {0, 0}; + + mgr.restore(snapshot); + + EXPECT_EQ(foo.transform.position, (vec2{1, 1})); +} diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp new file mode 100644 index 0000000..f8be3fe --- /dev/null +++ b/src/test/EventTest.cpp @@ -0,0 +1,228 @@ +#include <crepe/api/Event.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/Mediator.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class EventManagerTest : public ::testing::Test { +protected: + Mediator mediator; + EventManager event_mgr{mediator}; + void SetUp() override { + // Clear any existing subscriptions or events before each test + event_mgr.clear(); + } + + void TearDown() override { + // Ensure cleanup after each test + event_mgr.clear(); + } +}; +TEST_F(EventManagerTest, EventSubscription) { + EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { return true; }; + + // Subscribe to KeyPressEvent + event_mgr.subscribe<KeyPressEvent>(key_handler, 1); + + // Verify subscription (not directly verifiable; test by triggering event) + + event_mgr.trigger_event<KeyPressEvent>( + KeyPressEvent{ + .repeat = true, + .key = Keycode::A, + }, + 1); + event_mgr.trigger_event<KeyPressEvent>( + KeyPressEvent{ + .repeat = true, + .key = Keycode::A, + + }, + EventManager::CHANNEL_ALL); +} +TEST_F(EventManagerTest, EventManagerTest_trigger_all_channels) { + bool triggered = false; + + EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { + triggered = true; + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; + }; + event_mgr.subscribe<MouseClickEvent>(mouse_handler, EventManager::CHANNEL_ALL); + + MouseClickEvent click_event{.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}; + event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + + EXPECT_TRUE(triggered); +} +TEST_F(EventManagerTest, EventManagerTest_trigger_one_channel) { + bool triggered = false; + int test_channel = 1; + EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { + triggered = true; + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; + }; + event_mgr.subscribe<MouseClickEvent>(mouse_handler, test_channel); + + MouseClickEvent click_event{.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}; + event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + + EXPECT_FALSE(triggered); + event_mgr.trigger_event<MouseClickEvent>(click_event, test_channel); +} + +TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { + + // Flags to track handler calls + bool triggered_true = false; + bool triggered_false = false; + + // Handlers + EventHandler<MouseClickEvent> mouse_handler_true = [&](const MouseClickEvent & e) { + triggered_true = true; + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return true; // Stops propagation + }; + + EventHandler<MouseClickEvent> mouse_handler_false = [&](const MouseClickEvent & e) { + triggered_false = true; + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + + // Test event + MouseClickEvent click_event{.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}; + event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); + event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); + + // Trigger event + event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + + // Check that only the true handler was triggered + EXPECT_TRUE(triggered_true); + EXPECT_FALSE(triggered_false); + + // Reset and clear + triggered_true = false; + triggered_false = false; + event_mgr.clear(); + event_mgr.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); + event_mgr.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); + + // Trigger event again + event_mgr.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + + // Check that both handlers were triggered + EXPECT_TRUE(triggered_true); + EXPECT_TRUE(triggered_false); +} + +TEST_F(EventManagerTest, EventManagerTest_queue_dispatch) { + bool triggered1 = false; + bool triggered2 = false; + int test_channel = 1; + EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { + triggered1 = true; + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { + triggered2 = true; + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + event_mgr.subscribe<MouseClickEvent>(mouse_handler1); + event_mgr.subscribe<MouseClickEvent>(mouse_handler2, test_channel); + + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}); + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}, + test_channel); + event_mgr.dispatch_events(); + EXPECT_TRUE(triggered1); + EXPECT_TRUE(triggered2); +} + +TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { + + // Flags to track if handlers are triggered + bool triggered1 = false; + bool triggered2 = false; + + // Define EventHandlers + EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { + triggered1 = true; + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + + EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { + triggered2 = true; + EXPECT_EQ(e.mouse_pos.x, 100); + EXPECT_EQ(e.mouse_pos.y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + // Subscribe handlers + subscription_t handler1_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler1); + subscription_t handler2_id = event_mgr.subscribe<MouseClickEvent>(mouse_handler2); + + // Queue events + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}); + + // Dispatch events - both handlers should be triggered + event_mgr.dispatch_events(); + EXPECT_TRUE(triggered1); // Handler 1 should be triggered + EXPECT_TRUE(triggered2); // Handler 2 should be triggered + + // Reset flags + triggered1 = false; + triggered2 = false; + + // Unsubscribe handler1 + event_mgr.unsubscribe(handler1_id); + + // Queue the same event again + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}); + + // Dispatch events - only handler 2 should be triggered, handler 1 should NOT + event_mgr.dispatch_events(); + EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered + EXPECT_TRUE(triggered2); // Handler 2 should be triggered + + // Reset flags + triggered2 = false; + + // Unsubscribe handler2 + event_mgr.unsubscribe(handler2_id); + + // Queue the event again + event_mgr.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_pos = {100, 200}, .button = MouseButton::LEFT_MOUSE}); + + // Dispatch events - no handler should be triggered + event_mgr.dispatch_events(); + EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered + EXPECT_FALSE(triggered2); // Handler 2 should NOT be triggered +} diff --git a/src/test/InputTest.cpp b/src/test/InputTest.cpp new file mode 100644 index 0000000..f5ebaf0 --- /dev/null +++ b/src/test/InputTest.cpp @@ -0,0 +1,279 @@ +#include <gtest/gtest.h> + +#include <crepe/manager/ResourceManager.h> +#include <crepe/system/RenderSystem.h> +#define protected public +#define private public + +#include "api/KeyCodes.h" +#include "manager/ComponentManager.h" +#include "manager/EventManager.h" +#include "manager/Mediator.h" +#include "system/InputSystem.h" +#include <SDL2/SDL.h> +#include <SDL2/SDL_keycode.h> +#include <crepe/api/Button.h> +#include <crepe/api/Camera.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> +#include <crepe/facade/SDLContext.h> +#include <gmock/gmock.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class InputTest : public ::testing::Test { +public: + Mediator mediator; + ComponentManager mgr{mediator}; + SDLContext sdl_context{mediator}; + + InputSystem input_system{mediator}; + ResourceManager resman{mediator}; + RenderSystem render{mediator}; + EventManager event_manager{mediator}; + //GameObject camera; + +protected: + void SetUp() override { + GameObject obj = mgr.new_object("camera", "camera", vec2{0, 0}, 0, 1); + auto & camera + = obj.add_component<Camera>(ivec2{500, 500}, vec2{500, 500}, + Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); + render.frame_update(); + //mediator.event_manager = event_manager; + //mediator.component_manager = mgr; + //event_manager.clear(); + } + void TearDown() override {} + void simulate_mouse_click(int mouse_x, int mouse_y, Uint8 mouse_button) { + SDL_Event event; + + // Simulate Mouse Button Down event + SDL_zero(event); + event.type = SDL_MOUSEBUTTONDOWN; + event.button.x = mouse_x; + event.button.y = mouse_y; + event.button.button = mouse_button; + SDL_PushEvent(&event); + + // Simulate Mouse Button Up event + SDL_zero(event); + event.type = SDL_MOUSEBUTTONUP; + event.button.x = mouse_x; + event.button.y = mouse_y; + event.button.button = mouse_button; + SDL_PushEvent(&event); + } +}; + +TEST_F(InputTest, MouseDown) { + bool mouse_triggered = false; + EventHandler<MousePressEvent> on_mouse_down = [&](const MousePressEvent & event) { + mouse_triggered = true; + //middle of the screen = 0,0 + EXPECT_EQ(event.mouse_pos.x, 0); + EXPECT_EQ(event.mouse_pos.y, 0); + EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE); + return false; + }; + event_manager.subscribe<MousePressEvent>(on_mouse_down); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_MOUSEBUTTONDOWN; + // middle of the screen of a 500*500 camera = 250*250 + event.button.x = 250; + event.button.y = 250; + event.button.button = SDL_BUTTON_LEFT; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(mouse_triggered); +} + +TEST_F(InputTest, MouseUp) { + bool function_triggered = false; + EventHandler<MouseReleaseEvent> on_mouse_release = [&](const MouseReleaseEvent & e) { + function_triggered = true; + EXPECT_EQ(e.mouse_pos.x, 0); + EXPECT_EQ(e.mouse_pos.y, 0); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; + }; + event_manager.subscribe<MouseReleaseEvent>(on_mouse_release); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_MOUSEBUTTONUP; + event.button.x = 250; + event.button.y = 250; + event.button.button = SDL_BUTTON_LEFT; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, MouseMove) { + bool function_triggered = false; + EventHandler<MouseMoveEvent> on_mouse_move = [&](const MouseMoveEvent & e) { + function_triggered = true; + EXPECT_EQ(e.mouse_pos.x, 0); + EXPECT_EQ(e.mouse_pos.y, 0); + EXPECT_EQ(e.mouse_delta.x, 10); + EXPECT_EQ(e.mouse_delta.y, 10); + return false; + }; + event_manager.subscribe<MouseMoveEvent>(on_mouse_move); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_MOUSEMOTION; + event.motion.x = 250; + event.motion.y = 250; + event.motion.xrel = 10; + event.motion.yrel = 10; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, KeyDown) { + bool function_triggered = false; + + // Define event handler for KeyPressEvent + EventHandler<KeyPressEvent> on_key_press = [&](const KeyPressEvent & event) { + function_triggered = true; + EXPECT_EQ(event.key, Keycode::B); // Validate the key is 'B' + EXPECT_EQ(event.repeat, true); // Validate repeat flag + return false; + }; + + event_manager.subscribe<KeyPressEvent>(on_key_press); + + // Simulate SDL_KEYDOWN event + SDL_Event test_event; + SDL_zero(test_event); + test_event.type = SDL_KEYDOWN; // Key down event + test_event.key.keysym.scancode = SDL_SCANCODE_B; // Set scancode for 'B' + test_event.key.repeat = 1; // Set repeat flag + SDL_PushEvent(&test_event); + + input_system.fixed_update(); // Process the event + event_manager.dispatch_events(); // Dispatch events to handlers + + EXPECT_TRUE(function_triggered); // Check if the handler was triggered +} + +TEST_F(InputTest, KeyUp) { + bool function_triggered = false; + EventHandler<KeyReleaseEvent> on_key_release = [&](const KeyReleaseEvent & event) { + function_triggered = true; + EXPECT_EQ(event.key, Keycode::B); + return false; + }; + event_manager.subscribe<KeyReleaseEvent>(on_key_release); + + SDL_Event event; + SDL_zero(event); + event.type = SDL_KEYUP; + event.key.keysym.scancode = SDL_SCANCODE_B; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(function_triggered); +} + +TEST_F(InputTest, MouseClick) { + bool on_click_triggered = false; + EventHandler<MouseClickEvent> on_mouse_click = [&](const MouseClickEvent & event) { + on_click_triggered = true; + EXPECT_EQ(event.button, MouseButton::LEFT_MOUSE); + EXPECT_EQ(event.mouse_pos.x, 0); + EXPECT_EQ(event.mouse_pos.y, 0); + return false; + }; + event_manager.subscribe<MouseClickEvent>(on_mouse_click); + + this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(on_click_triggered); +} + +TEST_F(InputTest, testButtonClick) { + GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + bool button_clicked = false; + event_manager.subscribe<ButtonPressEvent>([&](const ButtonPressEvent & event) { + button_clicked = true; + EXPECT_EQ(event.metadata.game_object_id, button_obj.id); + return false; + }); + auto & button = button_obj.add_component<Button>(vec2{100, 100}, vec2{0, 0}); + + bool hover = false; + button.active = true; + this->simulate_mouse_click(999, 999, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_FALSE(button_clicked); + + this->simulate_mouse_click(250, 250, SDL_BUTTON_LEFT); + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(button_clicked); +} + +TEST_F(InputTest, testButtonHover) { + GameObject button_obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + bool button_hover = false; + event_manager.subscribe<ButtonEnterEvent>([&](const ButtonEnterEvent & event) { + button_hover = true; + EXPECT_EQ(event.metadata.game_object_id, button_obj.id); + return false; + }); + event_manager.subscribe<ButtonExitEvent>([&](const ButtonExitEvent & event) { + button_hover = false; + EXPECT_EQ(event.metadata.game_object_id, button_obj.id); + return false; + }); + auto & button = button_obj.add_component<Button>(vec2{100, 100}, vec2{0, 0}); + button.active = true; + // Mouse on button + SDL_Event hover_event; + SDL_zero(hover_event); + hover_event.type = SDL_MOUSEMOTION; + hover_event.motion.x = 250; + hover_event.motion.y = 250; + hover_event.motion.xrel = 10; + hover_event.motion.yrel = 10; + SDL_PushEvent(&hover_event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_TRUE(button.hover); + EXPECT_TRUE(button_hover); + // Mouse not on button + SDL_Event event; + SDL_zero(event); + event.type = SDL_MOUSEMOTION; + event.motion.x = 500; + event.motion.y = 500; + event.motion.xrel = 10; + event.motion.yrel = 10; + SDL_PushEvent(&event); + + input_system.fixed_update(); + event_manager.dispatch_events(); + EXPECT_FALSE(button.hover); + EXPECT_FALSE(button_hover); +} diff --git a/src/test/LoopManagerTest.cpp b/src/test/LoopManagerTest.cpp new file mode 100644 index 0000000..f6653fa --- /dev/null +++ b/src/test/LoopManagerTest.cpp @@ -0,0 +1,78 @@ +#include <chrono> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <thread> +#define private public +#define protected public +#include <crepe/api/Engine.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/LoopTimerManager.h> +using namespace std::chrono; +using namespace crepe; + +class DISABLED_LoopManagerTest : public ::testing::Test { +protected: + class TestGameLoop : public crepe::Engine { + public: + MOCK_METHOD(void, fixed_update, (), (override)); + MOCK_METHOD(void, frame_update, (), (override)); + }; + + TestGameLoop test_loop; + void SetUp() override {} +}; + +TEST_F(DISABLED_LoopManagerTest, FixedUpdate) { + // Arrange + test_loop.loop_timer.set_target_framerate(60); + + // Set expectations for the mock calls + EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65)); + EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52)); + + // Start the loop in a separate thread + std::thread loop_thread([&]() { test_loop.start(); }); + + // Let the loop run for exactly 1 second + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Stop the game loop + test_loop.game_running = false; + // Wait for the loop thread to finish + loop_thread.join(); + + // Test finished +} + +TEST_F(DISABLED_LoopManagerTest, ScaledFixedUpdate) { + // Arrange + test_loop.loop_timer.set_target_framerate(60); + + // Set expectations for the mock calls + EXPECT_CALL(test_loop, frame_update).Times(::testing::Between(55, 65)); + EXPECT_CALL(test_loop, fixed_update).Times(::testing::Between(48, 52)); + + // Start the loop in a separate thread + std::thread loop_thread([&]() { test_loop.start(); }); + + // Let the loop run for exactly 1 second + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Stop the game loop + test_loop.game_running = false; + // Wait for the loop thread to finish + loop_thread.join(); + + // Test finished +} + +TEST_F(DISABLED_LoopManagerTest, ShutDown) { + // Arrange + test_loop.loop_timer.set_target_framerate(60); + // Start the loop in a separate thread + std::thread loop_thread([&]() { test_loop.start(); }); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + test_loop.event_manager.trigger_event<ShutDownEvent>(ShutDownEvent{}); + // Wait for the loop thread to finish + loop_thread.join(); +} diff --git a/src/test/LoopTimerTest.cpp b/src/test/LoopTimerTest.cpp new file mode 100644 index 0000000..7bd6305 --- /dev/null +++ b/src/test/LoopTimerTest.cpp @@ -0,0 +1,97 @@ +#include <chrono> +#include <gtest/gtest.h> +#include <thread> + +#define private public +#define protected public + +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/Mediator.h> + +using namespace std::chrono; +using namespace crepe; + +class LoopTimerTest : public ::testing::Test { +protected: + Mediator mediator; + LoopTimerManager loop_timer{mediator}; + + void SetUp() override { loop_timer.start(); } +}; + +TEST_F(LoopTimerTest, EnforcesTargetFrameRate) { + // Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms) + loop_timer.set_target_framerate(60); + + auto start_time = steady_clock::now(); + loop_timer.enforce_frame_rate(); + + auto elapsed_time = steady_clock::now() - start_time; + auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count(); + + // For 60 FPS, the target frame time is around 16.67ms + ASSERT_NEAR(elapsed_ms, 16.7, 5); +} + +TEST_F(LoopTimerTest, SetTargetFps) { + // Set the target FPS to 120 + loop_timer.set_target_framerate(120); + + // Calculate the expected frame time (~8.33ms per frame) + duration_t expected_frame_time = std::chrono::duration<float>(1.0 / 120.0); + + ASSERT_NEAR(loop_timer.frame_target_time.count(), expected_frame_time.count(), 0.001); +} + +TEST_F(LoopTimerTest, DeltaTimeCalculation) { + // Set the target FPS to 60 (16.67 ms per frame) + loop_timer.set_target_framerate(60); + + auto start_time = steady_clock::now(); + loop_timer.update(); + auto end_time = steady_clock::now(); + + // Check the delta time + duration_t delta_time = loop_timer.get_delta_time(); + + auto elapsed_time = duration_cast<seconds>(end_time - start_time).count(); + + // Assert that delta_time is close to the elapsed time + ASSERT_NEAR(delta_time.count(), elapsed_time, 1); +} + +TEST_F(LoopTimerTest, DISABLED_getCurrentTime) { + // Set the target FPS to 60 (16.67 ms per frame) + loop_timer.set_target_framerate(60); + + auto start_time = steady_clock::now(); + + // Sleep + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + loop_timer.update(); + + auto end_time = steady_clock::now(); + + // Get the elapsed time in seconds as a double + auto elapsed_time + = std::chrono::duration_cast<elapsed_time_t>(end_time - start_time).count(); + + ASSERT_NEAR(loop_timer.get_elapsed_time().count(), elapsed_time, 5); +} +TEST_F(LoopTimerTest, getFPS) { + // Set the target FPS to 60 (which gives a target time per frame of ~16.67 ms) + loop_timer.set_target_framerate(60); + + auto start_time = steady_clock::now(); + loop_timer.enforce_frame_rate(); + + auto elapsed_time = steady_clock::now() - start_time; + loop_timer.update(); + unsigned int fps = loop_timer.get_fps(); + auto elapsed_ms = duration_cast<milliseconds>(elapsed_time).count(); + + // For 60 FPS, the target frame time is around 16.67ms + ASSERT_NEAR(elapsed_ms, 16.7, 1); + ASSERT_NEAR(fps, 60, 2); +} diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp new file mode 100644 index 0000000..83f7b23 --- /dev/null +++ b/src/test/OptionalRefTest.cpp @@ -0,0 +1,42 @@ +#include <gtest/gtest.h> + +#include <crepe/util/OptionalRef.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +TEST(OptionalRefTest, Normal) { + string value = "foo"; + OptionalRef<string> ref = value; + + EXPECT_TRUE(ref); + ASSERT_NO_THROW({ + string & value_ref = ref; + EXPECT_EQ(value_ref, value); + }); + + ref.clear(); + EXPECT_FALSE(ref); + ASSERT_THROW({ string & value_ref = ref; }, runtime_error); +} + +TEST(OptionalRefTest, Empty) { + string value = "foo"; + OptionalRef<string> ref; + + EXPECT_FALSE(ref); + ASSERT_THROW({ string & value_ref = ref; }, runtime_error); +} + +TEST(OptionalRefTest, Chain) { + string value = "foo"; + OptionalRef<string> ref1 = value; + OptionalRef<string> ref2 = ref1; + + EXPECT_TRUE(ref2); + string & value_ref = ref2; + EXPECT_EQ(value_ref, value); + value_ref = "bar"; + EXPECT_EQ(value_ref, value); +} diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp new file mode 100644 index 0000000..7a731a3 --- /dev/null +++ b/src/test/ParticleTest.cpp @@ -0,0 +1,212 @@ +#include "api/Asset.h" +#include <crepe/api/Config.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Transform.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/LoopTimerManager.h> +#include <gtest/gtest.h> +#include <math.h> +#define protected public +#define private public +#include <crepe/Particle.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/system/ParticleSystem.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class ParticlesTest : public ::testing::Test { + Mediator m; + +public: + ComponentManager component_manager{m}; + ParticleSystem particle_system{m}; + LoopTimerManager loop_timer{m}; + + void SetUp() override { + ComponentManager & mgr = this->component_manager; + std::vector<std::reference_wrapper<Transform>> transforms + = mgr.get_components_by_id<Transform>(0); + if (transforms.empty()) { + GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 0); + + Color color(0, 0, 0, 0); + auto s1 = Asset("asset/texture/img.png"); + Sprite & test_sprite = game_object.add_component<Sprite>( + s1, Sprite::Data{ + .color = color, + .flip = Sprite::FlipSettings{true, true}, + .size = {10, 10}, + }); + + game_object.add_component<ParticleEmitter>(test_sprite, + ParticleEmitter::Data{ + .offset = {0, 0}, + .max_particles = 100, + .emission_rate = 0, + .min_speed = 0, + .max_speed = 0, + .min_angle = 0, + .max_angle = 0, + .begin_lifespan = 0, + .end_lifespan = 0, + .force_over_time = vec2{0, 0}, + .boundary{ + .width = 0, + .height = 0, + .offset = vec2{0, 0}, + .reset_on_exit = false, + }, + }); + } + transforms = mgr.get_components_by_id<Transform>(0); + Transform & transform = transforms.front().get(); + transform.position.x = 0.0; + transform.position.y = 0.0; + transform.rotation = 0.0; + std::vector<std::reference_wrapper<ParticleEmitter>> rigidbodies + = mgr.get_components_by_id<ParticleEmitter>(0); + ParticleEmitter & emitter = rigidbodies.front().get(); + emitter.data.offset = {0, 0}; + emitter.data.emission_rate = 0; + emitter.data.min_speed = 0; + emitter.data.max_speed = 0; + emitter.data.min_angle = 0; + emitter.data.max_angle = 0; + emitter.data.begin_lifespan = 0; + emitter.data.end_lifespan = 0; + emitter.data.force_over_time = vec2{0, 0}; + emitter.data.boundary = {0, 0, vec2{0, 0}, false}; + for (auto & particle : emitter.particles) { + particle.active = false; + } + } +}; + +TEST_F(ParticlesTest, spawnParticle) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = this->component_manager; + ParticleEmitter & emitter = mgr.get_components_by_id<ParticleEmitter>(0).front().get(); + emitter.data.end_lifespan = 5; + emitter.data.boundary.height = 100; + emitter.data.boundary.width = 100; + emitter.data.max_speed = 0.1; + emitter.data.max_angle = 0.1; + emitter.data.max_speed = 10; + emitter.data.max_angle = 10; + particle_system.fixed_update(); + //check if nothing happend + EXPECT_EQ(emitter.particles[0].active, false); + emitter.data.emission_rate = 50; + //check particle spawnes + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[0].active, true); + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[1].active, true); + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[2].active, true); + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[3].active, true); + + for (auto & particle : emitter.particles) { + // Check velocity range + EXPECT_GE(particle.velocity.x, emitter.data.min_speed); + // Speed should be greater than or equal to min_speed + EXPECT_LE(particle.velocity.x, emitter.data.max_speed); + // Speed should be less than or equal to max_speed + EXPECT_GE(particle.velocity.y, emitter.data.min_speed); + // Speed should be greater than or equal to min_speed + EXPECT_LE(particle.velocity.y, emitter.data.max_speed); + // Speed should be less than or equal to max_speed + + // Check angle range + EXPECT_GE(particle.angle, emitter.data.min_angle); + // Angle should be greater than or equal to min_angle + EXPECT_LE(particle.angle, emitter.data.max_angle); + // Angle should be less than or equal to max_angle + } +} + +TEST_F(ParticlesTest, moveParticleHorizontal) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = this->component_manager; + ParticleEmitter & emitter = mgr.get_components_by_id<ParticleEmitter>(0).front().get(); + emitter.data.end_lifespan = 100; + emitter.data.boundary.height = 100; + emitter.data.boundary.width = 100; + emitter.data.min_speed = 50; + emitter.data.max_speed = 50; + emitter.data.max_angle = 0; + emitter.data.emission_rate = 50; + for (int a = 1; a < emitter.data.boundary.width / 2; a++) { + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[0].position.x, a); + } +} + +TEST_F(ParticlesTest, moveParticleVertical) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = this->component_manager; + ParticleEmitter & emitter = mgr.get_components_by_id<ParticleEmitter>(0).front().get(); + emitter.data.end_lifespan = 100; + emitter.data.boundary.height = 100; + emitter.data.boundary.width = 100; + emitter.data.min_speed = 50; + emitter.data.max_speed = 50; + emitter.data.min_angle = 90; + emitter.data.max_angle = 90; + emitter.data.emission_rate = 50; + for (int a = 1; a < emitter.data.boundary.width / 2; a++) { + particle_system.fixed_update(); + EXPECT_EQ(emitter.particles[0].position.y, a); + } +} + +TEST_F(ParticlesTest, boundaryParticleReset) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = this->component_manager; + ParticleEmitter & emitter = mgr.get_components_by_id<ParticleEmitter>(0).front().get(); + emitter.data.end_lifespan = 100; + emitter.data.boundary.height = 10; + emitter.data.boundary.width = 10; + emitter.data.boundary.reset_on_exit = true; + emitter.data.min_speed = 1; + emitter.data.max_speed = 1; + emitter.data.min_angle = 90; + emitter.data.max_angle = 90; + emitter.data.emission_rate = 1; + for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { + particle_system.fixed_update(); + } + EXPECT_EQ(emitter.particles[0].active, false); +} + +TEST_F(ParticlesTest, boundaryParticleStop) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = this->component_manager; + ParticleEmitter & emitter = mgr.get_components_by_id<ParticleEmitter>(0).front().get(); + emitter.data.end_lifespan = 100; + emitter.data.boundary.height = 10; + emitter.data.boundary.width = 10; + emitter.data.boundary.reset_on_exit = false; + emitter.data.min_speed = 1; + emitter.data.max_speed = 1; + emitter.data.min_angle = 90; + emitter.data.max_angle = 90; + emitter.data.emission_rate = 1; + for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { + particle_system.fixed_update(); + } + const double TOLERANCE = 0.01; + EXPECT_NEAR(emitter.particles[0].velocity.x, 0, TOLERANCE); + EXPECT_NEAR(emitter.particles[0].velocity.y, 0, TOLERANCE); + if (emitter.particles[0].velocity.x != 0) + EXPECT_NEAR(std::abs(emitter.particles[0].position.x), + emitter.data.boundary.height / 2, TOLERANCE); + if (emitter.particles[0].velocity.y != 0) + EXPECT_NEAR(std::abs(emitter.particles[0].position.y), emitter.data.boundary.width / 2, + TOLERANCE); +} diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp new file mode 100644 index 0000000..79ed0b8 --- /dev/null +++ b/src/test/PhysicsTest.cpp @@ -0,0 +1,132 @@ +#include <crepe/api/Config.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Transform.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/Mediator.h> +#include <crepe/system/PhysicsSystem.h> +#include <gtest/gtest.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class PhysicsTest : public ::testing::Test { + Mediator m; + +public: + ComponentManager component_manager{m}; + PhysicsSystem system{m}; + LoopTimerManager loop_timer{m}; + + void SetUp() override { + ComponentManager & mgr = this->component_manager; + vector<reference_wrapper<Transform>> transforms + = mgr.get_components_by_id<Transform>(0); + if (transforms.empty()) { + auto entity = mgr.new_object("", "", vec2{0, 0}, 0, 0); + entity.add_component<Rigidbody>(Rigidbody::Data{ + .mass = 1, + .gravity_scale = 1, + .body_type = Rigidbody::BodyType::DYNAMIC, + .max_linear_velocity = 10, + .max_angular_velocity = 10, + .constraints = {0, 0}, + }); + } + transforms = mgr.get_components_by_id<Transform>(0); + Transform & transform = transforms.front().get(); + transform.position.x = 0.0; + transform.position.y = 0.0; + transform.rotation = 0.0; + vector<reference_wrapper<Rigidbody>> rigidbodies + = mgr.get_components_by_id<Rigidbody>(0); + Rigidbody & rigidbody = rigidbodies.front().get(); + rigidbody.data.angular_velocity = 0; + rigidbody.data.linear_velocity.x = 0; + rigidbody.data.linear_velocity.y = 0; + } +}; + +TEST_F(PhysicsTest, gravity) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = this->component_manager; + vector<reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0); + const Transform & transform = transforms.front().get(); + ASSERT_FALSE(transforms.empty()); + EXPECT_EQ(transform.position.y, 0); + + system.fixed_update(); + EXPECT_NEAR(transform.position.y, 0.0004, 0.0001); + + system.fixed_update(); + EXPECT_NEAR(transform.position.y, 0.002, 0.001); +} + +TEST_F(PhysicsTest, max_velocity) { + ComponentManager & mgr = this->component_manager; + vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0); + Rigidbody & rigidbody = rigidbodies.front().get(); + rigidbody.data.gravity_scale = 0; + ASSERT_FALSE(rigidbodies.empty()); + EXPECT_EQ(rigidbody.data.linear_velocity.y, 0); + + rigidbody.add_force_linear({100, 100}); + rigidbody.add_force_angular(100); + system.fixed_update(); + EXPECT_NEAR(rigidbody.data.linear_velocity.y, 7.07, 0.01); + EXPECT_NEAR(rigidbody.data.linear_velocity.x, 7.07, 0.01); + EXPECT_EQ(rigidbody.data.angular_velocity, 10); + + rigidbody.add_force_linear({-100, -100}); + rigidbody.add_force_angular(-100); + system.fixed_update(); + EXPECT_NEAR(rigidbody.data.linear_velocity.y, -7.07, 0.01); + EXPECT_NEAR(rigidbody.data.linear_velocity.x, -7.07, 0.01); + EXPECT_EQ(rigidbody.data.angular_velocity, -10); +} + +TEST_F(PhysicsTest, movement) { + ComponentManager & mgr = this->component_manager; + vector<reference_wrapper<Rigidbody>> rigidbodies = mgr.get_components_by_id<Rigidbody>(0); + Rigidbody & rigidbody = rigidbodies.front().get(); + rigidbody.data.gravity_scale = 0; + vector<reference_wrapper<Transform>> transforms = mgr.get_components_by_id<Transform>(0); + const Transform & transform = transforms.front().get(); + ASSERT_FALSE(rigidbodies.empty()); + ASSERT_FALSE(transforms.empty()); + + rigidbody.add_force_linear({1, 1}); + rigidbody.add_force_angular(1); + system.fixed_update(); + EXPECT_NEAR(transform.position.x, 0.02, 0.001); + EXPECT_NEAR(transform.position.y, 0.02, 0.001); + EXPECT_NEAR(transform.rotation, 0.02, 0.001); + + rigidbody.data.constraints = {1, 1, 1}; + EXPECT_NEAR(transform.position.x, 0.02, 0.001); + EXPECT_NEAR(transform.position.y, 0.02, 0.001); + EXPECT_NEAR(transform.rotation, 0.02, 0.001); + rigidbody.data.constraints = {0, 0, 0}; + rigidbody.data.linear_velocity_coefficient.x = 0.5; + rigidbody.data.linear_velocity_coefficient.y = 0.5; + rigidbody.data.angular_velocity_coefficient = 0.5; + system.fixed_update(); + EXPECT_NEAR(rigidbody.data.linear_velocity.x, 0.98, 0.01); + EXPECT_NEAR(rigidbody.data.linear_velocity.y, 0.98, 0.01); + EXPECT_NEAR(rigidbody.data.angular_velocity, 0.98, 0.01); + + rigidbody.data.constraints = {1, 1, 0}; + rigidbody.data.angular_velocity_coefficient = 0; + rigidbody.data.max_angular_velocity = 1000; + rigidbody.data.angular_velocity = 360; + system.fixed_update(); + EXPECT_NEAR(transform.rotation, 7.24, 0.01); + + rigidbody.data.angular_velocity = -360; + system.fixed_update(); + EXPECT_NEAR(transform.rotation, 0.04, 0.001); + system.fixed_update(); + EXPECT_NEAR(transform.rotation, 352.84, 0.01); +} diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp new file mode 100644 index 0000000..f988700 --- /dev/null +++ b/src/test/Profiling.cpp @@ -0,0 +1,251 @@ +#include <chrono> +#include <cmath> +#include <crepe/api/Asset.h> +#include <crepe/manager/Mediator.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/system/ParticleSystem.h> +#include <crepe/system/PhysicsSystem.h> +#include <crepe/system/RenderSystem.h> +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Transform.h> +#include <crepe/facade/SDLContext.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/system/CollisionSystem.h> +#include <crepe/system/ScriptSystem.h> +#include <crepe/types.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class TestScript : public Script { + bool oncollision(const CollisionEvent & test) { + Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); + return true; + } + void init() { + subscribe<CollisionEvent>( + [this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); + } + void fixed_update() { + // Retrieve component from the same GameObject this script is on + } +}; + +class DISABLED_ProfilingTest : public Test { +public: + // Config for test + // Minimum amount to let test pass + const int min_gameobject_count = 100; + // Maximum amount to stop test + const int max_gameobject_count = 150; + // Amount of times a test runs to calculate average + const int average = 5; + // Maximum duration to stop test + const std::chrono::microseconds duration = 16000us; + + Mediator m; + SDLContext sdl_context{m}; + ResourceManager resman{m}; + ComponentManager mgr{m}; + // Add system used for profling tests + CollisionSystem collision_sys{m}; + PhysicsSystem physics_sys{m}; + ParticleSystem particle_sys{m}; + RenderSystem render_sys{m}; + ScriptSystem script_sys{m}; + + // Test data + std::map<std::string, std::chrono::microseconds> timings; + int game_object_count = 0; + std::chrono::microseconds total_time = 0us; + + void SetUp() override { + + GameObject do_not_use = mgr.new_object("DO_NOT_USE", "", {0, 0}); + do_not_use.add_component<Camera>(ivec2{1080, 720}, vec2{2000, 2000}, + Camera::Data{ + .bg_color = Color::WHITE, + .zoom = 1.0f, + }); + // initialize systems here: + //calls init + script_sys.fixed_update(); + //creates window + render_sys.frame_update(); + } + + // Helper function to time an update call and store its duration + template <typename Func> + std::chrono::microseconds time_function(const std::string & name, Func && func) { + auto start = std::chrono::steady_clock::now(); + func(); + auto end = std::chrono::steady_clock::now(); + std::chrono::microseconds duration + = std::chrono::duration_cast<std::chrono::microseconds>(end - start); + timings[name] += duration; + return duration; + } + + // Run and profile all systems, return the total time in milliseconds + std::chrono::microseconds run_all_systems() { + std::chrono::microseconds total_microseconds = 0us; + total_microseconds + += time_function("PhysicsSystem", [&]() { physics_sys.fixed_update(); }); + total_microseconds + += time_function("CollisionSystem", [&]() { collision_sys.fixed_update(); }); + total_microseconds + += time_function("ParticleSystem", [&]() { particle_sys.fixed_update(); }); + total_microseconds + += time_function("RenderSystem", [&]() { render_sys.frame_update(); }); + return total_microseconds; + } + + // Print timings of all functions + void log_timings() const { + std::string result = "\nFunction timings:\n"; + + for (const auto & [name, duration] : timings) { + result += name + " took " + std::to_string(duration.count() / 1000.0 / average) + + " ms (" + std::to_string(duration.count() / average) + " µs).\n"; + } + + result += "Total time: " + std::to_string(this->total_time.count() / 1000.0 / average) + + " ms (" + std::to_string(this->total_time.count() / average) + " µs)\n"; + + result += "Amount of gameobjects: " + std::to_string(game_object_count) + "\n"; + + GTEST_LOG_(INFO) << result; + } + + void clear_timings() { + for (auto & [key, value] : timings) { + value = std::chrono::microseconds(0); + } + } +}; + +TEST_F(DISABLED_ProfilingTest, Profiling_1) { + while (this->total_time / this->average < this->duration) { + + { + //define gameobject used for testing + GameObject gameobject = mgr.new_object("gameobject", "", {0, 0}); + } + + this->game_object_count++; + + this->total_time = 0us; + clear_timings(); + + for (int amount = 0; amount < this->average; amount++) { + this->total_time += run_all_systems(); + } + + if (this->game_object_count >= this->max_gameobject_count) break; + } + log_timings(); + EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} + +TEST_F(DISABLED_ProfilingTest, Profiling_2) { + while (this->total_time / this->average < this->duration) { + + { + //define gameobject used for testing + GameObject gameobject = mgr.new_object( + "gameobject", "", {static_cast<float>(game_object_count * 2), 0}); + gameobject.add_component<Rigidbody>(Rigidbody::Data{ + .gravity_scale = 0.0, + .body_type = Rigidbody::BodyType::STATIC, + }); + gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1}); + + gameobject.add_component<BehaviorScript>().set_script<TestScript>(); + Sprite & test_sprite = gameobject.add_component<Sprite>( + Asset{"asset/texture/square.png"}, + Sprite::Data{ + .color = {0, 0, 0, 0}, + .flip = {.flip_x = false, .flip_y = false}, + .sorting_in_layer = 1, + .order_in_layer = 1, + .size = {.y = 500}, + }); + } + + this->game_object_count++; + + this->total_time = 0us; + clear_timings(); + for (int amount = 0; amount < this->average; amount++) { + this->total_time += run_all_systems(); + } + + if (this->game_object_count >= this->max_gameobject_count) break; + } + log_timings(); + EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} + +TEST_F(DISABLED_ProfilingTest, Profiling_3) { + while (this->total_time / this->average < this->duration) { + + { + //define gameobject used for testing + GameObject gameobject = mgr.new_object( + "gameobject", "", {static_cast<float>(game_object_count * 2), 0}); + gameobject.add_component<Rigidbody>(Rigidbody::Data{ + .gravity_scale = 0, + .body_type = Rigidbody::BodyType::STATIC, + }); + gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1}); + gameobject.add_component<BehaviorScript>().set_script<TestScript>(); + Sprite & test_sprite = gameobject.add_component<Sprite>( + Asset{"asset/texture/square.png"}, + Sprite::Data{ + .color = {0, 0, 0, 0}, + .flip = {.flip_x = false, .flip_y = false}, + .sorting_in_layer = 1, + .order_in_layer = 1, + .size = {.y = 500}, + }); + auto & test = gameobject.add_component<ParticleEmitter>( + test_sprite, ParticleEmitter::Data{ + .max_particles = 10, + .emission_rate = 100, + .end_lifespan = 100000, + .boundary{ + .width = 1000, + .height = 1000, + .offset = vec2{0, 0}, + .reset_on_exit = false, + }, + + }); + } + render_sys.frame_update(); + this->game_object_count++; + + this->total_time = 0us; + clear_timings(); + for (int amount = 0; amount < this->average; amount++) { + this->total_time += run_all_systems(); + } + + if (this->game_object_count >= this->max_gameobject_count) break; + } + log_timings(); + EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp new file mode 100644 index 0000000..689a6d4 --- /dev/null +++ b/src/test/RenderSystemTest.cpp @@ -0,0 +1,186 @@ +#include "api/Asset.h" +#include "facade/SDLContext.h" +#include "manager/ResourceManager.h" +#include "types.h" +#include <functional> +#include <gtest/gtest.h> +#include <memory> +#include <vector> + +#define private public +#define protected public + +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Sprite.h> +#include <crepe/manager/ComponentManager.h> + +#include <crepe/system/RenderSystem.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class RenderSystemTest : public Test { + Mediator m; + +public: + ComponentManager mgr{m}; + SDLContext ctx{m}; + ResourceManager resource_manager{m}; + RenderSystem sys{m}; + GameObject entity1 = this->mgr.new_object("name"); + GameObject entity2 = this->mgr.new_object("name"); + GameObject entity3 = this->mgr.new_object("name"); + GameObject entity4 = this->mgr.new_object("name"); + + void SetUp() override { + auto s1 = Asset("asset/texture/img.png"); + auto s2 = Asset("asset/texture/img.png"); + auto s3 = Asset("asset/texture/img.png"); + auto s4 = Asset("asset/texture/img.png"); + auto & sprite1 + = entity1.add_component<Sprite>(s1, Sprite::Data{ + .color = Color(0, 0, 0, 0), + .flip = Sprite::FlipSettings{false, false}, + .sorting_in_layer = 5, + .order_in_layer = 5, + .size = {10, 10}, + }); + + EXPECT_EQ(sprite1.data.order_in_layer, 5); + EXPECT_EQ(sprite1.data.sorting_in_layer, 5); + auto & sprite2 + = entity2.add_component<Sprite>(s2, Sprite::Data{ + .color = Color(0, 0, 0, 0), + .flip = Sprite::FlipSettings{false, false}, + .sorting_in_layer = 2, + .order_in_layer = 1, + }); + EXPECT_EQ(sprite2.data.sorting_in_layer, 2); + EXPECT_EQ(sprite2.data.order_in_layer, 1); + + auto & sprite3 + = entity3.add_component<Sprite>(s3, Sprite::Data{ + .color = Color(0, 0, 0, 0), + .flip = Sprite::FlipSettings{false, false}, + .sorting_in_layer = 1, + .order_in_layer = 2, + }); + EXPECT_EQ(sprite3.data.sorting_in_layer, 1); + EXPECT_EQ(sprite3.data.order_in_layer, 2); + + auto & sprite4 + = entity4.add_component<Sprite>(s4, Sprite::Data{ + .color = Color(0, 0, 0, 0), + .flip = Sprite::FlipSettings{false, false}, + .sorting_in_layer = 1, + .order_in_layer = 1, + }); + EXPECT_EQ(sprite4.data.sorting_in_layer, 1); + EXPECT_EQ(sprite4.data.order_in_layer, 1); + } +}; + +TEST_F(RenderSystemTest, NoCamera) { + // No camera + EXPECT_ANY_THROW({ this->sys.frame_update(); }); +} + +TEST_F(RenderSystemTest, make_sprites) {} + +TEST_F(RenderSystemTest, sorting_sprites) { + vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); + ASSERT_EQ(sprites.size(), 4); + + vector<reference_wrapper<Sprite>> sorted_sprites = this->sys.sort(sprites); + ASSERT_EQ(sorted_sprites.size(), 4); + + // Expected order after sorting: + // 1. sorting_in_layer: 1, order_in_layer: 1 (entity4) + // 2. sorting_in_layer: 1, order_in_layer: 2 (entity3) + // 3. sorting_in_layer: 2, order_in_layer: 1 (entity2) + // 4. sorting_in_layer: 5, order_in_layer: 5 (entity1) + + EXPECT_EQ(sorted_sprites[0].get().data.sorting_in_layer, 1); + EXPECT_EQ(sorted_sprites[0].get().data.order_in_layer, 1); + + EXPECT_EQ(sorted_sprites[1].get().data.sorting_in_layer, 1); + EXPECT_EQ(sorted_sprites[1].get().data.order_in_layer, 2); + + EXPECT_EQ(sorted_sprites[2].get().data.sorting_in_layer, 2); + EXPECT_EQ(sorted_sprites[2].get().data.order_in_layer, 1); + + EXPECT_EQ(sorted_sprites[3].get().data.sorting_in_layer, 5); + EXPECT_EQ(sorted_sprites[3].get().data.order_in_layer, 5); + + for (size_t i = 1; i < sorted_sprites.size(); ++i) { + const Sprite & prev = sorted_sprites[i - 1].get(); + const Sprite & curr = sorted_sprites[i].get(); + + if (prev.data.sorting_in_layer == curr.data.sorting_in_layer) { + EXPECT_LE(prev.data.order_in_layer, curr.data.order_in_layer); + } else { + EXPECT_LE(prev.data.sorting_in_layer, curr.data.sorting_in_layer); + } + } +} + +TEST_F(RenderSystemTest, Update) { + entity1.add_component<Camera>(ivec2{100, 100}, vec2{100, 100}, + Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); + { + vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); + ASSERT_EQ(sprites.size(), 4); + + EXPECT_EQ(sprites[0].get().game_object_id, 0); + EXPECT_EQ(sprites[1].get().game_object_id, 1); + EXPECT_EQ(sprites[2].get().game_object_id, 2); + EXPECT_EQ(sprites[3].get().game_object_id, 3); + } + this->sys.frame_update(); + { + vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); + ASSERT_EQ(sprites.size(), 4); + + EXPECT_EQ(sprites[0].get().game_object_id, 0); + EXPECT_EQ(sprites[1].get().game_object_id, 1); + EXPECT_EQ(sprites[2].get().game_object_id, 2); + EXPECT_EQ(sprites[3].get().game_object_id, 3); + } +} + +TEST_F(RenderSystemTest, Camera) { + { + auto cameras = this->mgr.get_components_by_type<Camera>(); + EXPECT_NE(cameras.size(), 1); + } + { + entity1.add_component<Camera>(ivec2{100, 100}, vec2{100, 100}, + Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); + + auto cameras = this->mgr.get_components_by_type<Camera>(); + EXPECT_EQ(cameras.size(), 1); + } + + //TODO improve with newer version +} +TEST_F(RenderSystemTest, Color) { + entity1.add_component<Camera>(ivec2{100, 100}, vec2{100, 100}, + Camera::Data{.bg_color = Color::WHITE, .zoom = 1.0f}); + + auto & sprite = this->mgr.get_components_by_id<Sprite>(entity1.id).front().get(); + //ASSERT_NE(sprite.texture.texture.get(), nullptr); + + sprite.data.color = Color::GREEN; + EXPECT_EQ(sprite.data.color.r, Color::GREEN.r); + EXPECT_EQ(sprite.data.color.g, Color::GREEN.g); + EXPECT_EQ(sprite.data.color.b, Color::GREEN.b); + EXPECT_EQ(sprite.data.color.a, Color::GREEN.a); + this->sys.frame_update(); + EXPECT_EQ(sprite.data.color.r, Color::GREEN.r); + EXPECT_EQ(sprite.data.color.g, Color::GREEN.g); + EXPECT_EQ(sprite.data.color.b, Color::GREEN.b); + EXPECT_EQ(sprite.data.color.a, Color::GREEN.a); +} diff --git a/src/test/ReplayManagerTest.cpp b/src/test/ReplayManagerTest.cpp new file mode 100644 index 0000000..5ee4b40 --- /dev/null +++ b/src/test/ReplayManagerTest.cpp @@ -0,0 +1,38 @@ +#include <gtest/gtest.h> + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Script.h> +#include <crepe/manager/ReplayManager.h> +#include <crepe/system/ReplaySystem.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ReplayManagerTest : public Test { + Mediator mediator; + +public: + ComponentManager component_manager{mediator}; + ReplayManager replay_manager{mediator}; + ReplaySystem replay_system{mediator}; + + GameObject entity = component_manager.new_object("foo"); + Transform & entity_transform + = component_manager.get_components_by_id<Transform>(entity.id).back(); + Metadata & entity_metadata + = component_manager.get_components_by_id<Metadata>(entity.id).back(); +}; + +TEST_F(ReplayManagerTest, Default) { + // replay_manager.record_start(); + + // replay_system.fixed_update(); + // entity_transform.position += {1, 1}; + // replay_system.fixed_update(); + // entity_transform.position += {1, 1}; + // replay_system.fixed_update(); + + // recording_t recording = replay_manager.record_end(); +} diff --git a/src/test/ResourceManagerTest.cpp b/src/test/ResourceManagerTest.cpp new file mode 100644 index 0000000..965eeab --- /dev/null +++ b/src/test/ResourceManagerTest.cpp @@ -0,0 +1,84 @@ +#include "manager/Mediator.h" +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/api/GameObject.h> +#include <crepe/manager/ResourceManager.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ResourceManagerTest : public Test { + Mediator mediator; + +public: + ResourceManager resource_manager{mediator}; + + class Unrelated : public Resource { + using Resource::Resource; + }; + + Asset asset_a{"asset/texture/img.png"}; + Asset asset_b{"asset/texture/ERROR.png"}; + + class TestResource : public Resource { + public: + static unsigned instances; + + public: + const unsigned instance; + TestResource(const Asset & src, Mediator & mediator) + : Resource(src, mediator), + instance(this->instances++) {} + ~TestResource() { this->instances--; } + bool operator==(const TestResource & other) const { + return this->instance == other.instance; + } + }; + +private: + void SetUp() override { TestResource::instances = 0; } +}; +unsigned ResourceManagerTest::TestResource::instances = 0; + +TEST_F(ResourceManagerTest, Default) { + TestResource & res_1 = resource_manager.get<TestResource>(asset_a); + TestResource & res_2 = resource_manager.get<TestResource>(asset_a); + TestResource & res_3 = resource_manager.get<TestResource>(asset_b); + TestResource & res_4 = resource_manager.get<TestResource>(asset_b); + + ASSERT_EQ(res_1, res_2); + ASSERT_EQ(res_3, res_4); + EXPECT_NE(res_1, res_3); + + EXPECT_EQ(TestResource::instances, 2); + + resource_manager.clear(); +} + +TEST_F(ResourceManagerTest, Persistent) { + resource_manager.set_persistent(asset_a, true); + EXPECT_EQ(TestResource::instances, 0); + + resource_manager.get<TestResource>(asset_a); + resource_manager.get<TestResource>(asset_a); + resource_manager.get<TestResource>(asset_b); + resource_manager.get<TestResource>(asset_b); + EXPECT_EQ(TestResource::instances, 2); + + resource_manager.clear(); + EXPECT_EQ(TestResource::instances, 1); + + resource_manager.clear_all(); + EXPECT_EQ(TestResource::instances, 0); +} + +TEST_F(ResourceManagerTest, UnmatchedType) { + EXPECT_NO_THROW({ resource_manager.get<TestResource>(asset_a); }); + + EXPECT_THROW({ resource_manager.get<Unrelated>(asset_a); }, runtime_error); +} diff --git a/src/test/SaveManagerTest.cpp b/src/test/SaveManagerTest.cpp new file mode 100644 index 0000000..7609e69 --- /dev/null +++ b/src/test/SaveManagerTest.cpp @@ -0,0 +1,51 @@ +#include <gtest/gtest.h> + +#include <crepe/ValueBroker.h> +#include <crepe/facade/DB.h> +#include <crepe/manager/SaveManager.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class SaveManagerTest : public Test { + Mediator m; + class TestSaveManager : public SaveManager { + using SaveManager::SaveManager; + + // in-memory database for testing + DB db{}; + virtual DB & get_db() override { return this->db; } + }; + +public: + TestSaveManager mgr{m}; +}; + +TEST_F(SaveManagerTest, ReadWrite) { + ASSERT_FALSE(mgr.has("foo")); + mgr.set<string>("foo", "bar"); + ASSERT_TRUE(mgr.has("foo")); + + string value = mgr.get<string>("foo"); + EXPECT_EQ(value, "bar"); +} + +TEST_F(SaveManagerTest, DefaultValue) { + ValueBroker value = mgr.get<int>("foo", 3); + + ASSERT_EQ(value.get(), 3); + value.set(5); + EXPECT_EQ(value.get(), 5); +} + +TEST_F(SaveManagerTest, MultipleKeys) { + ValueBroker foo = mgr.get<int>("foo", 1); + ValueBroker bar = mgr.get<int>("bar", 2); + + EXPECT_EQ(foo.get(), 1); + EXPECT_EQ(bar.get(), 2); + + EXPECT_EQ(mgr.get<int>("foo"), 1); + EXPECT_EQ(mgr.get<int>("bar"), 2); +} diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp new file mode 100644 index 0000000..480e07a --- /dev/null +++ b/src/test/SceneManagerTest.cpp @@ -0,0 +1,160 @@ +#include <gtest/gtest.h> + +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/SceneManager.h> +#include <crepe/types.h> + +using namespace std; +using namespace crepe; + +class ConcreteScene1 : public Scene { +public: + void load_scene() { + GameObject object1 = new_object("scene_1", "tag_scene_1", vec2{0, 0}, 0, 1); + GameObject object2 = new_object("scene_1", "tag_scene_1", vec2{1, 0}, 0, 1); + GameObject object3 = new_object("scene_1", "tag_scene_1", vec2{2, 0}, 0, 1); + } + + string get_name() const { return "scene1"; } +}; + +class ConcreteScene2 : public Scene { +public: + void load_scene() { + GameObject object1 = new_object("scene_2", "tag_scene_2", vec2{0, 0}, 0, 1); + GameObject object2 = new_object("scene_2", "tag_scene_2", vec2{0, 1}, 0, 1); + GameObject object3 = new_object("scene_2", "tag_scene_2", vec2{0, 2}, 0, 1); + GameObject object4 = new_object("scene_2", "tag_scene_2", vec2{0, 3}, 0, 1); + } + + string get_name() const { return "scene2"; } +}; + +class ConcreteScene3 : public Scene { +public: + ConcreteScene3(const string & name) : name(name) {} + + void load_scene() { + GameObject object1 = new_object("scene_3", "tag_scene_3", vec2{0, 0}, 0, 1); + } + + string get_name() const { return name; } + +private: + const string name; +}; + +class SceneManagerTest : public ::testing::Test { + Mediator m; + +public: + ComponentManager component_mgr{m}; + SceneManager scene_mgr{m}; +}; + +TEST_F(SceneManagerTest, loadScene) { + scene_mgr.add_scene<ConcreteScene1>(); + scene_mgr.add_scene<ConcreteScene2>(); + + scene_mgr.load_next_scene(); + + vector<reference_wrapper<Metadata>> metadata + = component_mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform + = component_mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 3); + EXPECT_EQ(transform.size(), 3); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "scene_1"); + EXPECT_EQ(metadata[0].get().tag, "tag_scene_1"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); + + EXPECT_EQ(metadata[1].get().game_object_id, 1); + EXPECT_EQ(metadata[1].get().name, "scene_1"); + EXPECT_EQ(metadata[1].get().tag, "tag_scene_1"); + EXPECT_EQ(metadata[1].get().parent, -1); + EXPECT_EQ(metadata[1].get().children.size(), 0); + EXPECT_EQ(transform[1].get().position.x, 1); + EXPECT_EQ(transform[1].get().position.y, 0); + + EXPECT_EQ(metadata[2].get().game_object_id, 2); + EXPECT_EQ(metadata[2].get().name, "scene_1"); + EXPECT_EQ(metadata[2].get().tag, "tag_scene_1"); + EXPECT_EQ(metadata[2].get().parent, -1); + EXPECT_EQ(metadata[2].get().children.size(), 0); + EXPECT_EQ(transform[2].get().position.x, 2); + EXPECT_EQ(transform[2].get().position.y, 0); + + scene_mgr.set_next_scene("scene2"); + scene_mgr.load_next_scene(); + + metadata = component_mgr.get_components_by_type<Metadata>(); + transform = component_mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 4); + EXPECT_EQ(transform.size(), 4); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "scene_2"); + EXPECT_EQ(metadata[0].get().tag, "tag_scene_2"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); + + EXPECT_EQ(metadata[1].get().game_object_id, 1); + EXPECT_EQ(metadata[1].get().name, "scene_2"); + EXPECT_EQ(metadata[1].get().tag, "tag_scene_2"); + EXPECT_EQ(metadata[1].get().parent, -1); + EXPECT_EQ(metadata[1].get().children.size(), 0); + EXPECT_EQ(transform[1].get().position.x, 0); + EXPECT_EQ(transform[1].get().position.y, 1); + + EXPECT_EQ(metadata[2].get().game_object_id, 2); + EXPECT_EQ(metadata[2].get().name, "scene_2"); + EXPECT_EQ(metadata[2].get().tag, "tag_scene_2"); + EXPECT_EQ(metadata[2].get().parent, -1); + EXPECT_EQ(metadata[2].get().children.size(), 0); + EXPECT_EQ(transform[2].get().position.x, 0); + EXPECT_EQ(transform[2].get().position.y, 2); + + EXPECT_EQ(metadata[3].get().game_object_id, 3); + EXPECT_EQ(metadata[3].get().name, "scene_2"); + EXPECT_EQ(metadata[3].get().tag, "tag_scene_2"); + EXPECT_EQ(metadata[3].get().parent, -1); + EXPECT_EQ(metadata[3].get().children.size(), 0); + EXPECT_EQ(transform[3].get().position.x, 0); + EXPECT_EQ(transform[3].get().position.y, 3); +} + +TEST_F(SceneManagerTest, perfectForwarding) { + scene_mgr.add_scene<ConcreteScene3>("scene3"); + + scene_mgr.load_next_scene(); + + vector<reference_wrapper<Metadata>> metadata + = component_mgr.get_components_by_type<Metadata>(); + vector<reference_wrapper<Transform>> transform + = component_mgr.get_components_by_type<Transform>(); + + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(transform.size(), 1); + + EXPECT_EQ(metadata[0].get().game_object_id, 0); + EXPECT_EQ(metadata[0].get().name, "scene_3"); + EXPECT_EQ(metadata[0].get().tag, "tag_scene_3"); + EXPECT_EQ(metadata[0].get().parent, -1); + EXPECT_EQ(metadata[0].get().children.size(), 0); + EXPECT_EQ(transform[0].get().position.x, 0); + EXPECT_EQ(transform[0].get().position.y, 0); +} diff --git a/src/test/ScriptECSTest.cpp b/src/test/ScriptECSTest.cpp new file mode 100644 index 0000000..1ec33ba --- /dev/null +++ b/src/test/ScriptECSTest.cpp @@ -0,0 +1,41 @@ +#include <gtest/gtest.h> + +#define protected public + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Script.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/system/ScriptSystem.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptECSTest : public ScriptTest { +public: + class TestComponent : public Component { + using Component::Component; + }; +}; + +TEST_F(ScriptECSTest, GetOwnComponent) { + MyScript & script = this->script; + Metadata & metadata = script.get_component<Metadata>(); + + EXPECT_EQ(metadata.name, OBJ_NAME); +} + +TEST_F(ScriptECSTest, GetOwnComponents) { + const unsigned COUNT = 4; + + for (unsigned i = 0; i < COUNT; i++) entity.add_component<TestComponent>(); + + MyScript & script = this->script; + RefVector<TestComponent> components = script.get_components<TestComponent>(); + + EXPECT_EQ(components.size(), COUNT); +} diff --git a/src/test/ScriptEventTest.cpp b/src/test/ScriptEventTest.cpp new file mode 100644 index 0000000..479e3f5 --- /dev/null +++ b/src/test/ScriptEventTest.cpp @@ -0,0 +1,50 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Script.h> +#include <crepe/api/Vector2.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/system/ScriptSystem.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptEventTest : public ScriptTest { +public: + EventManager & event_manager = mediator.event_manager; + + class MyEvent : public Event {}; +}; + +TEST_F(ScriptEventTest, Default) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + EventManager & evmgr = this->event_manager; + + unsigned event_count = 0; + script.subscribe<MyEvent>([&](const MyEvent &) { + event_count++; + return true; + }); + + system.fixed_update(); + behaviorscript.active = false; + EXPECT_EQ(0, event_count); + + evmgr.trigger_event<MyEvent>(); + EXPECT_EQ(0, event_count); + + behaviorscript.active = true; + evmgr.trigger_event<MyEvent>(); + EXPECT_EQ(1, event_count); +} diff --git a/src/test/ScriptSaveManagerTest.cpp b/src/test/ScriptSaveManagerTest.cpp new file mode 100644 index 0000000..64403c4 --- /dev/null +++ b/src/test/ScriptSaveManagerTest.cpp @@ -0,0 +1,35 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include <crepe/facade/DB.h> +#include <crepe/manager/SaveManager.h> + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptSaveManagerTest : public ScriptTest { +public: + class TestSaveManager : public SaveManager { + using SaveManager::SaveManager; + + // in-memory database for testing + DB db{}; + virtual DB & get_db() override { return this->db; } + }; + + TestSaveManager save_mgr{mediator}; +}; + +TEST_F(ScriptSaveManagerTest, GetSaveManager) { + MyScript & script = this->script; + + SaveManager & mgr = script.get_save_manager(); + + EXPECT_EQ(&mgr, &save_mgr); +} diff --git a/src/test/ScriptSceneTest.cpp b/src/test/ScriptSceneTest.cpp new file mode 100644 index 0000000..2568049 --- /dev/null +++ b/src/test/ScriptSceneTest.cpp @@ -0,0 +1,30 @@ +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include "ScriptTest.h" +#include <crepe/manager/SceneManager.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ScriptSceneTest : public ScriptTest { +public: + SceneManager scene_manager{mediator}; + + class MyScene : public Scene {}; +}; + +TEST_F(ScriptSceneTest, Default) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + + const char * non_default_value = "foo"; + ASSERT_NE(non_default_value, scene_manager.next_scene); + + script.set_next_scene(non_default_value); + EXPECT_EQ(non_default_value, scene_manager.next_scene); +} diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp new file mode 100644 index 0000000..40aa25c --- /dev/null +++ b/src/test/ScriptTest.cpp @@ -0,0 +1,95 @@ +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +// stupid hack to allow access to private/protected members under test +#define private public +#define protected public + +#include "ScriptTest.h" + +using namespace std; +using namespace crepe; +using namespace testing; + +void ScriptTest::SetUp() { + auto & mgr = this->component_manager; + BehaviorScript & component = entity.add_component<BehaviorScript>(); + + this->behaviorscript = component; + ASSERT_TRUE(this->behaviorscript); + EXPECT_EQ(component.script.get(), nullptr); + component.set_script<NiceMock<MyScript>>(); + ASSERT_NE(component.script.get(), nullptr); + + this->script = *(MyScript *) component.script.get(); + ASSERT_TRUE(this->script); +} + +TEST_F(ScriptTest, Default) { + MyScript & script = this->script; + EXPECT_CALL(script, init()).Times(0); + EXPECT_CALL(script, fixed_update(_)).Times(0); + EXPECT_CALL(script, frame_update(_)).Times(0); +} + +TEST_F(ScriptTest, UpdateOnce) { + MyScript & script = this->script; + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(1); + EXPECT_CALL(script, fixed_update(_)).Times(1); + system.fixed_update(); + } + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(0); + EXPECT_CALL(script, fixed_update(_)).Times(1); + system.fixed_update(); + } + + { + InSequence seq; + + EXPECT_CALL(script, frame_update(_)).Times(1); + system.frame_update(); + } +} + +TEST_F(ScriptTest, UpdateInactive) { + BehaviorScript & behaviorscript = this->behaviorscript; + MyScript & script = this->script; + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(0); + EXPECT_CALL(script, fixed_update(_)).Times(0); + behaviorscript.active = false; + system.fixed_update(); + } + + { + InSequence seq; + + EXPECT_CALL(script, init()).Times(1); + EXPECT_CALL(script, fixed_update(_)).Times(1); + behaviorscript.active = true; + system.fixed_update(); + } +} + +TEST_F(ScriptTest, SaveManager) { + MyScript & script = this->script; + + EXPECT_EQ(&script.get_save_manager(), &this->save_manager); +} + +TEST_F(ScriptTest, LoopTimerManager) { + MyScript & script = this->script; + + EXPECT_EQ(&script.get_loop_timer(), &this->loop_timer); +} diff --git a/src/test/ScriptTest.h b/src/test/ScriptTest.h new file mode 100644 index 0000000..8637df0 --- /dev/null +++ b/src/test/ScriptTest.h @@ -0,0 +1,40 @@ +#pragma once + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Script.h> +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/EventManager.h> +#include <crepe/manager/LoopTimerManager.h> +#include <crepe/manager/SaveManager.h> +#include <crepe/system/ScriptSystem.h> + +class ScriptTest : public testing::Test { +protected: + crepe::Mediator mediator; + static constexpr const char * OBJ_NAME = "foo"; + +public: + crepe::ComponentManager component_manager{mediator}; + crepe::ScriptSystem system{mediator}; + crepe::EventManager event_mgr{mediator}; + crepe::LoopTimerManager loop_timer{mediator}; + crepe::SaveManager save_manager{mediator}; + crepe::GameObject entity = component_manager.new_object(OBJ_NAME); + + class MyScript : public crepe::Script { + // NOTE: explicitly stating `public:` is not required on actual scripts + + public: + MOCK_METHOD(void, init, (), (override)); + MOCK_METHOD(void, fixed_update, (crepe::duration_t), (override)); + MOCK_METHOD(void, frame_update, (crepe::duration_t), (override)); + }; + + crepe::OptionalRef<crepe::BehaviorScript> behaviorscript; + crepe::OptionalRef<MyScript> script; + + virtual void SetUp(); +}; diff --git a/src/test/ValueBrokerTest.cpp b/src/test/ValueBrokerTest.cpp new file mode 100644 index 0000000..e6bb058 --- /dev/null +++ b/src/test/ValueBrokerTest.cpp @@ -0,0 +1,63 @@ +#include <gtest/gtest.h> + +#include <crepe/ValueBroker.h> +#include <crepe/util/Proxy.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ValueBrokerTest : public Test { +public: + int read_count = 0; + int write_count = 0; + int value = 0; + + ValueBroker<int> broker{ + [this](const int & target) -> void { + this->write_count++; + this->value = target; + }, + [this]() -> const int & { + this->read_count++; + return this->value; + }, + }; + Proxy<int> proxy{broker}; + + void SetUp() override { + ASSERT_EQ(read_count, 0); + ASSERT_EQ(write_count, 0); + } +}; + +TEST_F(ValueBrokerTest, BrokerWrite) { + broker.set(0); + EXPECT_EQ(read_count, 0); + EXPECT_EQ(write_count, 1); +} + +TEST_F(ValueBrokerTest, BrokerRead) { + broker.get(); + EXPECT_EQ(read_count, 1); + EXPECT_EQ(write_count, 0); +} + +TEST_F(ValueBrokerTest, ProxyWrite) { + proxy = 0; + EXPECT_EQ(read_count, 0); + EXPECT_EQ(write_count, 1); +} + +void dummy(int) {} +TEST_F(ValueBrokerTest, ProxyRead) { + dummy(proxy); + EXPECT_EQ(read_count, 1); + EXPECT_EQ(write_count, 0); +} + +TEST_F(ValueBrokerTest, ProxyReadWrite) { + proxy = proxy; + ASSERT_EQ(read_count, 1); + ASSERT_EQ(write_count, 1); +} diff --git a/src/test/Vector2Test.cpp b/src/test/Vector2Test.cpp new file mode 100644 index 0000000..1e21af9 --- /dev/null +++ b/src/test/Vector2Test.cpp @@ -0,0 +1,532 @@ +#include <gtest/gtest.h> + +#include <crepe/api/Vector2.h> + +using namespace crepe; + +class Vector2Test : public ::testing::Test { +public: + Vector2<int> int_vec1; + Vector2<int> int_vec2; + Vector2<double> double_vec1; + Vector2<double> double_vec2; + Vector2<long> long_vec1; + Vector2<long> long_vec2; + Vector2<float> float_vec1; + Vector2<float> float_vec2; + + void SetUp() override { + int_vec1 = {1, 2}; + int_vec2 = {3, 4}; + double_vec1 = {1.0, 2.0}; + double_vec2 = {3.0, 4.0}; + long_vec1 = {1, 2}; + long_vec2 = {3, 4}; + float_vec1 = {1.0f, 2.0f}; + float_vec2 = {3.0f, 4.0f}; + } +}; + +TEST_F(Vector2Test, Subtract) { + Vector2<int> result = int_vec1 - int_vec2; + EXPECT_EQ(result.x, -2); + EXPECT_EQ(result.y, -2); + + Vector2<double> result2 = double_vec1 - double_vec2; + EXPECT_FLOAT_EQ(result2.x, -2.0); + EXPECT_FLOAT_EQ(result2.y, -2.0); + + Vector2<long> result3 = long_vec1 - long_vec2; + EXPECT_EQ(result3.x, -2); + EXPECT_EQ(result3.y, -2); + + Vector2<float> result4 = float_vec1 - float_vec2; + EXPECT_FLOAT_EQ(result4.x, -2.0f); + EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, SubtractScalar) { + Vector2<int> result = int_vec1 - 1; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1 - 1.0; + EXPECT_FLOAT_EQ(result2.x, 0.0); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1 - 1; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1 - 1.0f; + EXPECT_FLOAT_EQ(result4.x, 0.0f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, Add) { + Vector2<int> result = int_vec1 + int_vec2; + EXPECT_EQ(result.x, 4); + EXPECT_EQ(result.y, 6); + + Vector2<double> result2 = double_vec1 + double_vec2; + EXPECT_FLOAT_EQ(result2.x, 4.0); + EXPECT_FLOAT_EQ(result2.y, 6.0); + + Vector2<long> result3 = long_vec1 + long_vec2; + EXPECT_EQ(result3.x, 4); + EXPECT_EQ(result3.y, 6); + + Vector2<float> result4 = float_vec1 + float_vec2; + EXPECT_FLOAT_EQ(result4.x, 4.0f); + EXPECT_FLOAT_EQ(result4.y, 6.0f); +} + +TEST_F(Vector2Test, AddScalar) { + Vector2<int> result = int_vec1 + 1; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 3); + + Vector2<double> result2 = double_vec1 + 1.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 3.0); + + Vector2<long> result3 = long_vec1 + 1; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 3); + + Vector2<float> result4 = float_vec1 + 1.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 3.0f); +} + +TEST_F(Vector2Test, Multiply) { + Vector2<int> result = int_vec1 * int_vec2; + EXPECT_EQ(result.x, 3); + EXPECT_EQ(result.y, 8); + + Vector2<double> result2 = double_vec1 * double_vec2; + EXPECT_FLOAT_EQ(result2.x, 3.0); + EXPECT_FLOAT_EQ(result2.y, 8.0); + + Vector2<long> result3 = long_vec1 * long_vec2; + EXPECT_EQ(result3.x, 3); + EXPECT_EQ(result3.y, 8); + + Vector2<float> result4 = float_vec1 * float_vec2; + EXPECT_FLOAT_EQ(result4.x, 3.0f); + EXPECT_FLOAT_EQ(result4.y, 8.0f); +} + +TEST_F(Vector2Test, MultiplyScalar) { + Vector2<int> result = int_vec1 * 2; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 4); + + Vector2<double> result2 = double_vec1 * 2.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 4.0); + + Vector2<long> result3 = long_vec1 * 2; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 4); + + Vector2<float> result4 = float_vec1 * 2.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 4.0f); +} + +TEST_F(Vector2Test, Divide) { + Vector2<int> result = int_vec1 / int_vec2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 0); + + Vector2<double> result2 = double_vec1 / double_vec2; + EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331); + EXPECT_FLOAT_EQ(result2.y, 0.5); + + Vector2<long> result3 = long_vec1 / long_vec2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 0); + + Vector2<float> result4 = float_vec1 / float_vec2; + EXPECT_FLOAT_EQ(result4.x, 0.333333343f); + EXPECT_FLOAT_EQ(result4.y, 0.5f); +} + +TEST_F(Vector2Test, DivideScalar) { + Vector2<int> result = int_vec1 / 2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1 / 2.0; + EXPECT_FLOAT_EQ(result2.x, 0.5); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1 / 2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1 / 2.0f; + EXPECT_FLOAT_EQ(result4.x, 0.5f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, AddChain) { + Vector2<int> result = int_vec1; + result += int_vec2; + EXPECT_EQ(result.x, 4); + EXPECT_EQ(result.y, 6); + + Vector2<double> result2 = double_vec1; + result2 += double_vec2; + EXPECT_FLOAT_EQ(result2.x, 4.0); + EXPECT_FLOAT_EQ(result2.y, 6.0); + + Vector2<long> result3 = long_vec1; + result3 += long_vec2; + EXPECT_EQ(result3.x, 4); + EXPECT_EQ(result3.y, 6); + + Vector2<float> result4 = float_vec1; + result4 += float_vec2; + EXPECT_FLOAT_EQ(result4.x, 4.0f); + EXPECT_FLOAT_EQ(result4.y, 6.0f); +} + +TEST_F(Vector2Test, AddScalarChain) { + Vector2<int> result = int_vec1; + result += 1; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 3); + + Vector2<double> result2 = double_vec1; + result2 += 1.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 3.0); + + Vector2<long> result3 = long_vec1; + result3 += 1; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 3); + + Vector2<float> result4 = float_vec1; + result4 += 1.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 3.0f); +} + +TEST_F(Vector2Test, SubtractChain) { + Vector2<int> result = int_vec1; + result -= int_vec2; + EXPECT_EQ(result.x, -2); + EXPECT_EQ(result.y, -2); + + Vector2<double> result2 = double_vec1; + result2 -= double_vec2; + EXPECT_FLOAT_EQ(result2.x, -2.0); + EXPECT_FLOAT_EQ(result2.y, -2.0); + + Vector2<long> result3 = long_vec1; + result3 -= long_vec2; + EXPECT_EQ(result3.x, -2); + EXPECT_EQ(result3.y, -2); + + Vector2<float> result4 = float_vec1; + result4 -= float_vec2; + EXPECT_FLOAT_EQ(result4.x, -2.0f); + EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, SubtractScalarChain) { + Vector2<int> result = int_vec1; + result -= 1; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1; + result2 -= 1.0; + EXPECT_FLOAT_EQ(result2.x, 0.0); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1; + result3 -= 1; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1; + result4 -= 1.0f; + EXPECT_FLOAT_EQ(result4.x, 0.0f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, MultiplyChain) { + Vector2<int> result = int_vec1; + result *= int_vec2; + EXPECT_EQ(result.x, 3); + EXPECT_EQ(result.y, 8); + + Vector2<double> result2 = double_vec1; + result2 *= double_vec2; + EXPECT_FLOAT_EQ(result2.x, 3.0); + EXPECT_FLOAT_EQ(result2.y, 8.0); + + Vector2<long> result3 = long_vec1; + result3 *= long_vec2; + EXPECT_EQ(result3.x, 3); + EXPECT_EQ(result3.y, 8); + + Vector2<float> result4 = float_vec1; + result4 *= float_vec2; + EXPECT_FLOAT_EQ(result4.x, 3.0f); + EXPECT_FLOAT_EQ(result4.y, 8.0f); +} + +TEST_F(Vector2Test, MultiplyScalarChain) { + Vector2<int> result = int_vec1; + result *= 2; + EXPECT_EQ(result.x, 2); + EXPECT_EQ(result.y, 4); + + Vector2<double> result2 = double_vec1; + result2 *= 2.0; + EXPECT_FLOAT_EQ(result2.x, 2.0); + EXPECT_FLOAT_EQ(result2.y, 4.0); + + Vector2<long> result3 = long_vec1; + result3 *= 2; + EXPECT_EQ(result3.x, 2); + EXPECT_EQ(result3.y, 4); + + Vector2<float> result4 = float_vec1; + result4 *= 2.0f; + EXPECT_FLOAT_EQ(result4.x, 2.0f); + EXPECT_FLOAT_EQ(result4.y, 4.0f); +} + +TEST_F(Vector2Test, DivideChain) { + Vector2<int> result = int_vec1; + result /= int_vec2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 0); + + Vector2<double> result2 = double_vec1; + result2 /= double_vec2; + EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331); + EXPECT_FLOAT_EQ(result2.y, 0.5); + + Vector2<long> result3 = long_vec1; + result3 /= long_vec2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 0); + + Vector2<float> result4 = float_vec1; + result4 /= float_vec2; + EXPECT_FLOAT_EQ(result4.x, 0.333333343f); + EXPECT_FLOAT_EQ(result4.y, 0.5f); +} + +TEST_F(Vector2Test, DivideScalarChain) { + Vector2<int> result = int_vec1; + result /= 2; + EXPECT_EQ(result.x, 0); + EXPECT_EQ(result.y, 1); + + Vector2<double> result2 = double_vec1; + result2 /= 2.0; + EXPECT_FLOAT_EQ(result2.x, 0.5); + EXPECT_FLOAT_EQ(result2.y, 1.0); + + Vector2<long> result3 = long_vec1; + result3 /= 2; + EXPECT_EQ(result3.x, 0); + EXPECT_EQ(result3.y, 1); + + Vector2<float> result4 = float_vec1; + result4 /= 2.0f; + EXPECT_FLOAT_EQ(result4.x, 0.5f); + EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, Negatation) { + Vector2<int> result = -int_vec1; + EXPECT_EQ(result.x, -1); + EXPECT_EQ(result.y, -2); + + Vector2<double> result2 = -double_vec1; + EXPECT_FLOAT_EQ(result2.x, -1.0); + EXPECT_FLOAT_EQ(result2.y, -2.0); + + Vector2<long> result3 = -long_vec1; + EXPECT_EQ(result3.x, -1); + EXPECT_EQ(result3.y, -2); + + Vector2<float> result4 = -float_vec1; + EXPECT_FLOAT_EQ(result4.x, -1.0f); + EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, Equals) { + EXPECT_TRUE(int_vec1 == int_vec1); + EXPECT_FALSE(int_vec1 == int_vec2); + EXPECT_TRUE(double_vec1 == double_vec1); + EXPECT_FALSE(double_vec1 == double_vec2); + EXPECT_TRUE(long_vec1 == long_vec1); + EXPECT_FALSE(long_vec1 == long_vec2); +} + +TEST_F(Vector2Test, NotEquals) { + EXPECT_FALSE(int_vec1 != int_vec1); + EXPECT_TRUE(int_vec1 != int_vec2); + EXPECT_FALSE(double_vec1 != double_vec1); + EXPECT_TRUE(double_vec1 != double_vec2); + EXPECT_FALSE(long_vec1 != long_vec1); + EXPECT_TRUE(long_vec1 != long_vec2); +} + +TEST_F(Vector2Test, Truncate) { + Vector2<int> vec = {3, 4}; + vec.truncate(3); + EXPECT_EQ(vec.x, 0); + EXPECT_EQ(vec.y, 0); + + Vector2<double> vec2 = {3.0, 4.0}; + vec2.truncate(3.0); + EXPECT_FLOAT_EQ(vec2.x, 1.8); + EXPECT_FLOAT_EQ(vec2.y, 2.4); + + Vector2<long> vec3 = {3, 4}; + vec3.truncate(3); + EXPECT_EQ(vec3.x, 0); + EXPECT_EQ(vec3.y, 0); + + Vector2<float> vec4 = {3.0f, 4.0f}; + vec4.truncate(3.0f); + EXPECT_FLOAT_EQ(vec4.x, 1.8f); + EXPECT_FLOAT_EQ(vec4.y, 2.4f); +} + +TEST_F(Vector2Test, Normalize) { + Vector2<int> vec = {3, 4}; + vec.normalize(); + EXPECT_EQ(vec.x, 0); + EXPECT_EQ(vec.y, 0); + + Vector2<double> vec2 = {3.0, 4.0}; + vec2.normalize(); + EXPECT_FLOAT_EQ(vec2.x, 0.6); + EXPECT_FLOAT_EQ(vec2.y, 0.8); + + Vector2<long> vec3 = {3, 4}; + vec3.normalize(); + EXPECT_EQ(vec3.x, 0); + EXPECT_EQ(vec3.y, 0); + + Vector2<float> vec4 = {3.0f, 4.0f}; + vec4.normalize(); + EXPECT_FLOAT_EQ(vec4.x, 0.6f); + EXPECT_FLOAT_EQ(vec4.y, 0.8f); +} + +TEST_F(Vector2Test, Length) { + Vector2<int> vec = {3, 4}; + EXPECT_EQ(vec.length(), 5); + + Vector2<double> vec2 = {3.0, 4.0}; + EXPECT_FLOAT_EQ(vec2.length(), 5.0); + + Vector2<long> vec3 = {3, 4}; + EXPECT_EQ(vec3.length(), 5); + + Vector2<float> vec4 = {3.0f, 4.0f}; + EXPECT_FLOAT_EQ(vec4.length(), 5.0f); +} + +TEST_F(Vector2Test, LengthSquared) { + Vector2<int> vec = {3, 4}; + EXPECT_EQ(vec.length_squared(), 25); + + Vector2<double> vec2 = {3.0, 4.0}; + EXPECT_FLOAT_EQ(vec2.length_squared(), 25.0); + + Vector2<long> vec3 = {3, 4}; + EXPECT_EQ(vec3.length_squared(), 25); + + Vector2<float> vec4 = {3.0f, 4.0f}; + EXPECT_FLOAT_EQ(vec4.length_squared(), 25.0f); +} + +TEST_F(Vector2Test, Dot) { + Vector2<int> vec1 = {3, 4}; + Vector2<int> vec2 = {5, 6}; + EXPECT_EQ(vec1.dot(vec2), 39); + + Vector2<double> vec3 = {3.0, 4.0}; + Vector2<double> vec4 = {5.0, 6.0}; + EXPECT_FLOAT_EQ(vec3.dot(vec4), 39.0); + + Vector2<long> vec5 = {3, 4}; + Vector2<long> vec6 = {5, 6}; + EXPECT_EQ(vec5.dot(vec6), 39); + + Vector2<float> vec7 = {3.0f, 4.0f}; + Vector2<float> vec8 = {5.0f, 6.0f}; + EXPECT_FLOAT_EQ(vec7.dot(vec8), 39.0f); +} + +TEST_F(Vector2Test, Distance) { + Vector2<int> vec1 = {1, 1}; + Vector2<int> vec2 = {4, 5}; + EXPECT_EQ(vec1.distance(vec2), 5); + + Vector2<double> vec3 = {1.0, 1.0}; + Vector2<double> vec4 = {4.0, 5.0}; + EXPECT_FLOAT_EQ(vec3.distance(vec4), 5.0); + + Vector2<long> vec5 = {1, 1}; + Vector2<long> vec6 = {4, 5}; + EXPECT_EQ(vec5.distance(vec6), 5); + + Vector2<float> vec7 = {1.0f, 1.0f}; + Vector2<float> vec8 = {4.0f, 5.0f}; + EXPECT_FLOAT_EQ(vec7.distance(vec8), 5.0f); +} + +TEST_F(Vector2Test, DistanceSquared) { + Vector2<int> vec1 = {3, 4}; + Vector2<int> vec2 = {5, 6}; + EXPECT_EQ(vec1.distance_squared(vec2), 8); + + Vector2<double> vec3 = {3.0, 4.0}; + Vector2<double> vec4 = {5.0, 6.0}; + EXPECT_FLOAT_EQ(vec3.distance_squared(vec4), 8.0); + + Vector2<long> vec5 = {3, 4}; + Vector2<long> vec6 = {5, 6}; + EXPECT_EQ(vec5.distance_squared(vec6), 8); + + Vector2<float> vec7 = {3.0f, 4.0f}; + Vector2<float> vec8 = {5.0f, 6.0f}; + EXPECT_FLOAT_EQ(vec7.distance_squared(vec8), 8.0f); +} + +TEST_F(Vector2Test, Perpendicular) { + Vector2<int> vec = {3, 4}; + Vector2<int> result = vec.perpendicular(); + EXPECT_EQ(result.x, -4); + EXPECT_EQ(result.y, 3); + + Vector2<double> vec2 = {3.0, 4.0}; + Vector2<double> result2 = vec2.perpendicular(); + EXPECT_FLOAT_EQ(result2.x, -4.0); + EXPECT_FLOAT_EQ(result2.y, 3.0); + + Vector2<long> vec3 = {3, 4}; + Vector2<long> result3 = vec3.perpendicular(); + EXPECT_EQ(result3.x, -4); + EXPECT_EQ(result3.y, 3); + + Vector2<float> vec4 = {3.0f, 4.0f}; + Vector2<float> result4 = vec4.perpendicular(); + EXPECT_FLOAT_EQ(result4.x, -4.0f); + EXPECT_FLOAT_EQ(result4.y, 3.0f); +} diff --git a/src/test/audio.cpp b/src/test/audio.cpp deleted file mode 100644 index d6ff689..0000000 --- a/src/test/audio.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include <gtest/gtest.h> - -using namespace std; -using namespace std::chrono_literals; - -// using namespace crepe; - -// TODO: mock internal audio class - -TEST(audio, play) { ASSERT_TRUE(true); } diff --git a/src/test/dummy.cpp b/src/test/dummy.cpp deleted file mode 100644 index a00a9c6..0000000 --- a/src/test/dummy.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include <gtest/gtest.h> - -TEST(dummy, foo) { ASSERT_TRUE(1); } diff --git a/src/test/main.cpp b/src/test/main.cpp new file mode 100644 index 0000000..0e1bc75 --- /dev/null +++ b/src/test/main.cpp @@ -0,0 +1,29 @@ +#include <gtest/gtest.h> + +#include <crepe/api/Config.h> + +using namespace crepe; +using namespace testing; + +class GlobalConfigReset : public EmptyTestEventListener { +public: + Config & cfg = Config::get_instance(); + + // This function is called before each test + void OnTestStart(const TestInfo &) override { + cfg = { + .log = { + .level = Log::Level::WARNING, + }, + }; + } +}; + +int main(int argc, char ** argv) { + InitGoogleTest(&argc, argv); + + UnitTest & ut = *UnitTest::GetInstance(); + ut.listeners().Append(new GlobalConfigReset); + + return RUN_ALL_TESTS(); +} |