diff --git a/include/interface/libs/BehaviorKit.h b/include/interface/libs/BehaviorKit.h new file mode 100644 index 0000000000..50755131b4 --- /dev/null +++ b/include/interface/libs/BehaviorKit.h @@ -0,0 +1,25 @@ +// Leka - LekaOS +// Copyright 2023 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "interface/Behavior.h" +#include "internal/BehaviorID.h" + +namespace leka::interface { + +class BehaviorKit +{ + public: + virtual ~BehaviorKit() = default; + + virtual void registerBehaviors(std::span behaviors) = 0; + virtual void start(interface::Behavior *behavior) = 0; + virtual void start(BehaviorID id) = 0; + virtual void stop() = 0; +}; + +} // namespace leka::interface diff --git a/libs/BehaviorKit/CMakeLists.txt b/libs/BehaviorKit/CMakeLists.txt index 73c3901a1b..95d922c947 100644 --- a/libs/BehaviorKit/CMakeLists.txt +++ b/libs/BehaviorKit/CMakeLists.txt @@ -15,10 +15,12 @@ target_sources(BehaviorKit ) target_link_libraries(BehaviorKit + EventLoopKit ) if(${CMAKE_PROJECT_NAME} STREQUAL "LekaOSUnitTests") leka_unit_tests_sources( + tests/BehaviorID_test.cpp tests/BehaviorKit_test.cpp ) endif() diff --git a/libs/BehaviorKit/include/BehaviorKit.h b/libs/BehaviorKit/include/BehaviorKit.h index 76f563e4f1..30be72fe96 100644 --- a/libs/BehaviorKit/include/BehaviorKit.h +++ b/libs/BehaviorKit/include/BehaviorKit.h @@ -4,14 +4,33 @@ #pragma once +#include + +#include "interface/Behavior.h" +#include "interface/libs/BehaviorKit.h" +#include "interface/libs/EventLoop.h" +#include "internal/BehaviorID.h" + namespace leka { -class BehaviorKit +class BehaviorKit : public interface::BehaviorKit { public: - explicit BehaviorKit() = default; + explicit BehaviorKit(interface::EventLoop &event_loop); + + void registerBehaviors(std::span behaviors) final; + + void start(interface::Behavior *behavior) final; + void start(BehaviorID id) final; + void stop() final; private: + void run(); + + interface::EventLoop &_event_loop; + + std::span _behaviors {}; + interface::Behavior *_behavior = nullptr; }; } // namespace leka diff --git a/libs/BehaviorKit/include/interface/Behavior.h b/libs/BehaviorKit/include/interface/Behavior.h new file mode 100644 index 0000000000..5d4e668a36 --- /dev/null +++ b/libs/BehaviorKit/include/interface/Behavior.h @@ -0,0 +1,23 @@ +// Leka - LekaOS +// Copyright 2023 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "../internal/BehaviorID.h" + +namespace leka::interface { + +struct Behavior { + virtual ~Behavior() = default; + + virtual auto id() -> BehaviorID = 0; + + virtual void run() = 0; + virtual void stop() = 0; +}; + +} // namespace leka::interface diff --git a/libs/BehaviorKit/include/internal/BehaviorID.h b/libs/BehaviorKit/include/internal/BehaviorID.h new file mode 100644 index 0000000000..b54b9a4d01 --- /dev/null +++ b/libs/BehaviorKit/include/internal/BehaviorID.h @@ -0,0 +1,15 @@ +// Leka - LekaOS +// Copyright 2023 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +using BehaviorID = uint16_t; + +namespace leka::behavior_id { + +inline constexpr BehaviorID none = 0x0000; + +} // namespace leka::behavior_id diff --git a/libs/BehaviorKit/source/BehaviorKit.cpp b/libs/BehaviorKit/source/BehaviorKit.cpp index 80ba6d3d1f..926159142f 100644 --- a/libs/BehaviorKit/source/BehaviorKit.cpp +++ b/libs/BehaviorKit/source/BehaviorKit.cpp @@ -3,5 +3,60 @@ // SPDX-License-Identifier: Apache-2.0 #include "BehaviorKit.h" +#include using namespace leka; + +BehaviorKit::BehaviorKit(interface::EventLoop &event_loop) : _event_loop(event_loop) +{ + _event_loop.registerCallback([this] { run(); }); +} + +void BehaviorKit::registerBehaviors(std::span behaviors) +{ + _behaviors = behaviors; +} + +void BehaviorKit::start(interface::Behavior *behavior) +{ + stop(); + + _behavior = nullptr; + for (auto *b: _behaviors) { + if (b == behavior) { + _behavior = b; + } + } + + if (_behavior == nullptr) { + return; + } + + _event_loop.start(); +} + +void BehaviorKit::start(BehaviorID id) +{ + interface::Behavior *found_behavior = nullptr; + + for (auto *behavior: _behaviors) { + if (id == behavior->id()) { + found_behavior = behavior; + break; + } + } + + start(found_behavior); +} + +void BehaviorKit::run() +{ + _behavior->run(); +} + +void BehaviorKit::stop() +{ + if (_behavior != nullptr) { + _behavior->stop(); + } +} diff --git a/libs/BehaviorKit/tests/BehaviorID_test.cpp b/libs/BehaviorKit/tests/BehaviorID_test.cpp new file mode 100644 index 0000000000..e3d8392bc5 --- /dev/null +++ b/libs/BehaviorKit/tests/BehaviorID_test.cpp @@ -0,0 +1,29 @@ +// Leka - LekaOS +// Copyright 2023 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "gtest/gtest.h" +#include "interface/Behavior.h" + +using namespace leka; + +TEST(BehaviorIDTest, behaviorsHaveUniqueID) +{ + // auto all_behaviors = std::to_array({}); + std::array all_behaviors = + {}; // TODO: to remove, only for empty list, use above instead + + auto comparator = [](interface::Behavior *a, interface::Behavior *b) { return a->id() < b->id(); }; + std::sort(all_behaviors.begin(), all_behaviors.end(), comparator); + + auto array_size = std::size(all_behaviors); + if (array_size <= 1) { + return; + } + + for (auto i = 1; i < array_size; i++) { + ASSERT_NE(all_behaviors.at(i - 1)->id(), all_behaviors.at(i)->id()); + } +} diff --git a/libs/BehaviorKit/tests/BehaviorKit_test.cpp b/libs/BehaviorKit/tests/BehaviorKit_test.cpp index 84e497d153..70b0d402be 100644 --- a/libs/BehaviorKit/tests/BehaviorKit_test.cpp +++ b/libs/BehaviorKit/tests/BehaviorKit_test.cpp @@ -6,21 +6,111 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "mocks/leka/Behavior.h" +#include "stubs/leka/EventLoopKit.h" using namespace leka; +using ::testing::Return; + class BehaviorKitTest : public ::testing::Test { protected: - BehaviorKitTest() : behaviorkit() {}; + BehaviorKitTest() = default; // void SetUp() override {} // void TearDown() override {} - BehaviorKit behaviorkit; + stub::EventLoopKit stub_event_loop {}; + BehaviorKit behaviorkit {stub_event_loop}; + + mock::Behavior mock_behavior_a {}; + mock::Behavior mock_behavior_b {}; }; TEST_F(BehaviorKitTest, initialization) { ASSERT_NE(&behaviorkit, nullptr); } + +TEST_F(BehaviorKitTest, startFirstBehavior) +{ + auto behaviors = std::to_array({&mock_behavior_a}); + behaviorkit.registerBehaviors(behaviors); + + EXPECT_CALL(mock_behavior_a, run); + + behaviorkit.start(&mock_behavior_a); +} + +TEST_F(BehaviorKitTest, startBehaviorNullPtr) +{ + // nothing expected + + behaviorkit.start(nullptr); +} + +TEST_F(BehaviorKitTest, startFirstBehaviorID) +{ + auto behaviors = std::to_array({ + &mock_behavior_a, + &mock_behavior_b, + }); + behaviorkit.registerBehaviors(behaviors); + + auto behavior_a_id = BehaviorID {1}; + auto behavior_b_id = BehaviorID {2}; + + EXPECT_CALL(mock_behavior_a, id).WillRepeatedly(Return(behavior_a_id)); + EXPECT_CALL(mock_behavior_b, id).WillRepeatedly(Return(behavior_b_id)); + EXPECT_CALL(mock_behavior_b, run); + + behaviorkit.start(behavior_b_id); +} + +TEST_F(BehaviorKitTest, startBehaviorIDNotRegistered) +{ + auto behaviors = std::to_array({ + &mock_behavior_a, + &mock_behavior_b, + }); + behaviorkit.registerBehaviors(behaviors); + + auto behavior_a_id = BehaviorID {1}; + auto behavior_b_id = BehaviorID {2}; + + EXPECT_CALL(mock_behavior_a, id).WillRepeatedly(Return(behavior_a_id)); + EXPECT_CALL(mock_behavior_b, id).WillRepeatedly(Return(behavior_b_id)); + + behaviorkit.start(BehaviorID {3}); +} + +TEST_F(BehaviorKitTest, startBehaviorNotRegistered) +{ + auto behaviors = std::to_array({ + &mock_behavior_a, + }); + behaviorkit.registerBehaviors(behaviors); + + EXPECT_CALL(mock_behavior_b, run).Times(0); + + behaviorkit.start(&mock_behavior_b); +} + +TEST_F(BehaviorKitTest, startAnyBehavior) +{ + auto behaviors = std::to_array({ + &mock_behavior_a, + &mock_behavior_b, + }); + behaviorkit.registerBehaviors(behaviors); + + EXPECT_CALL(mock_behavior_a, run); + + behaviorkit.start(&mock_behavior_a); + + EXPECT_CALL(mock_behavior_a, stop); + EXPECT_CALL(mock_behavior_b, run); + + behaviorkit.start(&mock_behavior_b); +} diff --git a/tests/unit/mocks/mocks/leka/Behavior.h b/tests/unit/mocks/mocks/leka/Behavior.h new file mode 100644 index 0000000000..377b57c7d7 --- /dev/null +++ b/tests/unit/mocks/mocks/leka/Behavior.h @@ -0,0 +1,21 @@ +// Leka - LekaOS +// Copyright 2023 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "gmock/gmock.h" +#include "interface/Behavior.h" + +namespace leka::mock { + +class Behavior : public interface::Behavior +{ + public: + MOCK_METHOD(BehaviorID, id, (), (override)); + + MOCK_METHOD(void, run, (), (override)); + MOCK_METHOD(void, stop, (), (override)); +}; + +} // namespace leka::mock