diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory.cpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory.cpp index f9dcc4c83a..38131eedd0 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory.cpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory.cpp @@ -33,9 +33,6 @@ namespace score::mw::lifecycle::internal::saf::factory { -using RecoveryClient = score::mw::lifecycle::IRecoveryClient; -using IdentifierHash = score::mw::lifecycle::IdentifierHash; - AliveWorkerFactory::AliveWorkerFactory() : IAliveWorkerFactory() { } @@ -73,7 +70,7 @@ bool EmplaceAndAttach( bool AliveWorkerFactory::createObservableEvent( std::vector& events, const IdentifierHash component_id, - ifexm::ObservableEventReader& event_reader_) + ifexm::IObservableEventReader& event_reader_) { try { diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory.hpp index 2ec2c1172c..f7d36817f7 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory.hpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory.hpp @@ -50,7 +50,7 @@ class AliveWorkerFactory : public IAliveWorkerFactory bool createObservableEvent( std::vector& events, const IdentifierHash component_id, - ifexm::ObservableEventReader& event_reader_) override; + ifexm::IObservableEventReader& event_reader_) override; /// Refer to the description of the base class (IAliveWorkerFactory) bool createAliveIfIpc( diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory_UT.cpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory_UT.cpp new file mode 100644 index 0000000000..088a2cb019 --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/AliveWorkerFactory_UT.cpp @@ -0,0 +1,257 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include +#include + +#include "score/mw/launch_manager/alive_monitor/details/factory/AliveWorkerFactory.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifexm/MockObservableEventReader.hpp" +#include "score/mw/launch_manager/recovery_client/mock_recovery_client.h" + +using namespace testing; + +namespace score::mw::lifecycle::internal::saf +{ + +namespace ifappl +{ +std::unique_ptr g_mock_checkpoint_ipc_server; +std::unique_ptr g_mock_monitor_if_daemon; +} // namespace ifappl + +namespace supervision +{ +std::unique_ptr g_mock_alive_supervision; +} + +namespace factory +{ + +struct FactoryException : public std::exception +{ + explicit FactoryException(std::string_view reason) : reason_(reason) {}; + + std::string_view reason_; + + const char* what() const noexcept override + { + return reason_.data(); + } +}; + +class AliveWorkerFactoryTest : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + + config_.reporting_cycle_ms = 100; + + ipc_ = std::make_unique(); + interface_ = std::make_unique(*ipc_, "/dev/null"); + ifappl::g_mock_checkpoint_ipc_server = std::make_unique(); + ifappl::g_mock_monitor_if_daemon = std::make_unique(); + supervision::g_mock_alive_supervision = std::make_unique(); + + EXPECT_CALL(*ifappl::g_mock_checkpoint_ipc_server, getPath) + .Times(AnyNumber()) + .WillRepeatedly(Return("/dev/null")); + + EXPECT_CALL(*supervision::g_mock_alive_supervision, getIdentifier) + .Times(AnyNumber()) + .WillRepeatedly(Return(component_id_)); + } + + void TearDown() override + { + ifappl::g_mock_checkpoint_ipc_server.reset(); // GMock destructor verifies and clears expectations + ifappl::g_mock_monitor_if_daemon.reset(); + supervision::g_mock_alive_supervision.reset(); + } + + AliveWorkerFactory factory{}; + + const IdentifierHash component_id_{"Component"}; + std::unique_ptr ipc_; + std::unique_ptr interface_; + ifexm::ObservableEvent event_{component_id_}; + std::shared_ptr mock_recovery_client_ = std::make_shared(); + ComponentAliveSupervision config_{}; +}; + +TEST_F(AliveWorkerFactoryTest, createObservableEventFails) +{ + RecordProperty( + "Description", + "Verify that exceptions thrown while creating an observable event are caught and a failure is returned"); + + ifexm::MockObservableEventReader reader{}; + std::vector out{}; + bool create_result = true; + + EXPECT_CALL(reader, registerObservableEvent).WillOnce(Throw(FactoryException{"Exception during registration"})); + + EXPECT_NO_THROW(create_result = factory.createObservableEvent(out, component_id_, reader)); + EXPECT_FALSE(create_result); +} + +TEST_F(AliveWorkerFactoryTest, createObservableEventSucceeds) +{ + RecordProperty( + "Description", + "Verify that createObservableEvent creates and attaches an observable event with the correct information"); + + ifexm::MockObservableEventReader reader{}; + std::vector out{}; + bool create_result = false; + PolymorphicMatcher event_matcher = + Field(&ifexm::ObservableEvent::event, Field(&SupervisionEvent::id, Eq(component_id_))); + + EXPECT_CALL(reader, registerObservableEvent(event_matcher, component_id_)).WillOnce(Return(true)); + + create_result = factory.createObservableEvent(out, component_id_, reader); + + EXPECT_TRUE(create_result); + EXPECT_THAT(out.back(), event_matcher); +} + +TEST_F(AliveWorkerFactoryTest, createAliveIfIpcThrows) +{ + RecordProperty( + "Description", + "Verify that exceptions thrown while creating an ipc server are caught and a failure is returned"); + + std::vector out{}; + bool create_result = true; + + EXPECT_CALL(*ifappl::g_mock_checkpoint_ipc_server, init).WillOnce(Throw(FactoryException{"IPC init error"})); + + EXPECT_NO_THROW(create_result = factory.createAliveIfIpc(out, component_id_, 0)); + EXPECT_FALSE(create_result); +} + +TEST_F(AliveWorkerFactoryTest, createAliveIfIpcInitFails) +{ + RecordProperty("Description", "Verify that a failure to initialise an ipc server fails the method"); + + std::vector out{}; + bool create_result = true; + + EXPECT_CALL(*ifappl::g_mock_checkpoint_ipc_server, init) + .WillOnce(Return(ifappl::CheckpointIpcServer::EIpcInitResult::kFailed)); + + EXPECT_NO_THROW(create_result = factory.createAliveIfIpc(out, component_id_, 0)); + EXPECT_FALSE(create_result); +} + +TEST_F(AliveWorkerFactoryTest, createAliveIfIpcSetAccessFails) +{ + RecordProperty("Description", "Verify that a failure to set access rights on an ipc server fails the method"); + + std::vector out{}; + bool create_result = true; + + EXPECT_CALL(*ifappl::g_mock_checkpoint_ipc_server, init) + .WillOnce(Return(ifappl::CheckpointIpcServer::EIpcInitResult::kOk)); + EXPECT_CALL(*ifappl::g_mock_checkpoint_ipc_server, setAccessRights).WillOnce(Return(false)); + + EXPECT_NO_THROW(create_result = factory.createAliveIfIpc(out, component_id_, 0)); + EXPECT_FALSE(create_result); +} + +TEST_F(AliveWorkerFactoryTest, createAliveIfIpcSucceeds) +{ + RecordProperty("Description", "Verify that createAliveIfIpc creates an ipc server with the correct information"); + + std::vector out{}; + bool create_result = true; + const uid_t uid = 151; + + EXPECT_CALL(*ifappl::g_mock_checkpoint_ipc_server, init) + .WillOnce(Return(ifappl::CheckpointIpcServer::EIpcInitResult::kOk)); + EXPECT_CALL(*ifappl::g_mock_checkpoint_ipc_server, setAccessRights(uid)).WillOnce(Return(true)); + + create_result = factory.createAliveIfIpc(out, component_id_, uid); + EXPECT_TRUE(create_result); + EXPECT_EQ(out.size(), 1); +} + +TEST_F(AliveWorkerFactoryTest, createAliveIfFails) +{ + RecordProperty( + "Description", + "Verify that exceptions thrown while creating an alive interface are caught and a failure is returned"); + + std::vector out{}; + bool create_result = true; + + EXPECT_CALL(*ifappl::g_mock_monitor_if_daemon, ctor) + .WillOnce(Throw(FactoryException{"Exception during construction"})); + EXPECT_CALL(*ifappl::g_mock_checkpoint_ipc_server, getPath).WillOnce(Return("/dev/null")); + + EXPECT_NO_THROW(create_result = factory.createAliveIf(out, *ipc_, event_)); + EXPECT_FALSE(create_result); +} + +TEST_F(AliveWorkerFactoryTest, createAliveIfSucceeds) +{ + RecordProperty("Description", "Verify that createAliveIf creates an alive interface with the correct information"); + + std::vector out{}; + bool create_result = true; + + EXPECT_CALL(*ifappl::g_mock_monitor_if_daemon, ctor); + EXPECT_CALL(*ifappl::g_mock_checkpoint_ipc_server, getPath).WillOnce(Return("/dev/null")); + + create_result = factory.createAliveIf(out, *ipc_, event_); + EXPECT_TRUE(create_result); + EXPECT_EQ(out.size(), 1); +} + +TEST_F(AliveWorkerFactoryTest, createAliveSupervisionFails) +{ + RecordProperty( + "Description", + "Verify that exceptions thrown while creating an alive supervision are caught and a failure is returned"); + + std::vector out{}; + bool create_result = true; + EXPECT_CALL(*supervision::g_mock_alive_supervision, ctor) + .WillOnce(Throw(FactoryException{"Exception during construction"})); + + EXPECT_NO_THROW( + create_result = + factory.createAliveSupervision(out, *interface_, event_, mock_recovery_client_, component_id_, config_)); + EXPECT_FALSE(create_result); +} + +TEST_F(AliveWorkerFactoryTest, createAliveSupervisionSucceeds) +{ + RecordProperty( + "Description", "Verify that createAliveSupervision creates an alive supervision with the correct information"); + + std::vector out{}; + bool create_result = true; + EXPECT_CALL(*supervision::g_mock_alive_supervision, ctor(component_id_, _, _, _, _)); + + create_result = + factory.createAliveSupervision(out, *interface_, event_, mock_recovery_client_, component_id_, config_); + EXPECT_TRUE(create_result); + EXPECT_EQ(out.size(), 1); +} + +} // namespace factory + +} // namespace score::mw::lifecycle::internal::saf diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/BUILD b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/BUILD index 39b7bdc336..64e569f812 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/BUILD +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/BUILD @@ -11,6 +11,7 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* load("@rules_cc//cc:defs.bzl", "cc_library") +load("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") cc_library( name = "i_alive_worker_factory", @@ -20,6 +21,9 @@ cc_library( visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], deps = [ "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl:data_structures", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl:monitor_if_daemon", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:i_observable_event_reader", + "//score/launch_manager/src/daemon/src/alive_monitor/details/supervision:alive", ], ) @@ -33,7 +37,6 @@ cc_library( deps = [ ":i_alive_worker_factory", "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl:checkpoint", - "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl:monitor_if_daemon", "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:observable_event", "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:observable_event_reader", "//score/launch_manager/src/daemon/src/alive_monitor/details/supervision:alive", @@ -50,3 +53,55 @@ cc_library( "@platforms//os:linux": ["//externals/acl"], }), ) + +cc_library( + name = "mockable_i_alive_worker_factory", + testonly = True, + hdrs = ["IAliveWorkerFactory.hpp"], + include_prefix = "score/mw/launch_manager/alive_monitor/details/factory", + strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/factory", + visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], + deps = [ + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks:mock_data_structures", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks:mock_monitor_if_daemon", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:i_observable_event_reader", + "//score/launch_manager/src/daemon/src/alive_monitor/details/supervision/injection_mocks:mock_alive", + ], +) + +cc_library( + name = "mockable_alive_worker_factory", + testonly = True, + srcs = ["AliveWorkerFactory.cpp"], + hdrs = ["AliveWorkerFactory.hpp"], + include_prefix = "score/mw/launch_manager/alive_monitor/details/factory", + strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/factory", + visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], + deps = [ + ":mockable_i_alive_worker_factory", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl:checkpoint", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:observable_event", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:observable_event_reader", + "//score/launch_manager/src/daemon/src/alive_monitor/details/timers:time_conversion", + "//score/launch_manager/src/daemon/src/alive_monitor/details/timers:timers_os_clock", + "//score/launch_manager/src/daemon/src/common:alive_interface_path", + "//score/launch_manager/src/daemon/src/common:constants", + "//score/launch_manager/src/daemon/src/common:identifier_hash", + "//score/launch_manager/src/daemon/src/common:log", + "//score/launch_manager/src/daemon/src/configuration:config", + "@score_baselibs//score/language/futurecpp", + ], +) + +lm_cc_test( + name = "alive_worker_factory_UT", + srcs = [ + "AliveWorkerFactory_UT.cpp", + ], + deps = [ + ":mockable_alive_worker_factory", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:mock_observable_event_reader", + "//score/launch_manager/src/daemon/src/recovery_client:mock_recovery_client", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/IAliveWorkerFactory.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/IAliveWorkerFactory.hpp index 32ea7c5527..17ec9a3f63 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/factory/IAliveWorkerFactory.hpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/factory/IAliveWorkerFactory.hpp @@ -15,8 +15,12 @@ #define IALIVEWORKERFACTORY_HPP_INCLUDED #include "score/mw/launch_manager/alive_monitor/details/ifappl/DataStructures.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifappl/MonitorIfDaemon.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifexm/IObservableEventReader.hpp" +#include "score/mw/launch_manager/alive_monitor/details/supervision/Alive.hpp" #include "score/mw/launch_manager/common/identifier_hash.hpp" #include "score/mw/launch_manager/configuration/config.hpp" + #include namespace score::mw::lifecycle @@ -31,20 +35,8 @@ namespace score::mw::lifecycle::internal::saf namespace ifexm { class ObservableEvent; -class ObservableEventReader; } // namespace ifexm -namespace ifappl -{ -class MonitorIfDaemon; -class Checkpoint; -} // namespace ifappl - -namespace supervision -{ -class Alive; -} // namespace supervision - namespace factory { @@ -78,7 +70,7 @@ class IAliveWorkerFactory virtual bool createObservableEvent( std::vector& events, const IdentifierHash component_id, - ifexm::ObservableEventReader& event_reader_) = 0; + ifexm::IObservableEventReader& event_reader_) = 0; /// @brief Create IPC for Alive Interface /// @param [out] servers Container to emplace the new server into diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/BUILD b/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/BUILD index fce53cace0..cb0f7f14fd 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/BUILD +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/BUILD @@ -53,6 +53,24 @@ cc_library( ], ) +cc_library( + name = "mockable_monitor_if_daemon", + testonly = True, + srcs = ["MonitorIfDaemon.cpp"], + hdrs = ["MonitorIfDaemon.hpp"], + include_prefix = "score/mw/launch_manager/alive_monitor/details/ifappl", + strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl", + visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], + deps = [ + ":checkpoint", + "//score/launch_manager/src/daemon/src/alive_monitor/details/common:observer", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks:mock_data_structures", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:observable_event", + "//score/launch_manager/src/daemon/src/alive_monitor/details/timers:timers_os_clock", + "//score/launch_manager/src/daemon/src/common:log", + ], +) + lm_cc_test( name = "MonitorIfDaemon_UT", srcs = [ diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks/BUILD b/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks/BUILD new file mode 100644 index 0000000000..3aa892c258 --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks/BUILD @@ -0,0 +1,44 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "mock_data_structures", + testonly = True, + hdrs = ["DataStructures.hpp"], + include_prefix = "score/mw/launch_manager/alive_monitor/details/ifappl/", + strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks", + visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], + deps = [ + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "mock_monitor_if_daemon", + testonly = True, + hdrs = ["MonitorIfDaemon.hpp"], + include_prefix = "score/mw/launch_manager/alive_monitor/details/ifappl/", + strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks", + visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], + deps = [ + ":mock_data_structures", + "//score/launch_manager/src/daemon/src/alive_monitor/details/common:observer", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl:checkpoint", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:observable_event", + "//score/launch_manager/src/daemon/src/alive_monitor/details/timers:timers_os_clock", + "//score/launch_manager/src/daemon/src/common:log", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks/DataStructures.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks/DataStructures.hpp new file mode 100644 index 0000000000..93dc9be468 --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks/DataStructures.hpp @@ -0,0 +1,111 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef MOCK_CHECKPOINT_IPC_SERVER_HPP +#define MOCK_CHECKPOINT_IPC_SERVER_HPP + +#include +#include + +#include + +namespace score::mw::lifecycle::internal::saf::ifappl +{ + +enum class EMockIpcInitResult : std::uint8_t +{ + kOk = 0U, + kFailed = 1U, + kPermissionDenied = 2U +}; + +enum class EMockIpcPeekResult : std::uint8_t +{ + kOk = 0U, + kNoDataToRead = 1U, + kIpcError = 2U +}; + +struct CheckpointBufferElement final +{ + /// @brief Timestamp of the checkpoint + std::chrono::nanoseconds timestamp{0U}; + + /// @brief Default constructor needed for storage in vector + CheckpointBufferElement() = default; + + /// @brief Constructor for usage with emplace + /// @param [in] f_timestamp The checkpoint timestamp + CheckpointBufferElement(std::chrono::nanoseconds f_timestamp) noexcept(true) : timestamp(f_timestamp) + { + } +}; + +class MockCheckpointIpcServer +{ + public: + using Payload = CheckpointBufferElement; + using EIpcInitResult = EMockIpcInitResult; + using EIpcPeekResult = EMockIpcPeekResult; + MOCK_METHOD(std::string_view, getPath, (), (const, noexcept(true))); + MOCK_METHOD(bool, hasOverflow, (), (noexcept(true))); + MOCK_METHOD(EIpcPeekResult, peek, (Payload * &f_elem_p), (noexcept(true))); + MOCK_METHOD(EIpcInitResult, init, (const std::string& f_ipcName_r, mode_t f_mode), (noexcept(false))); + MOCK_METHOD(bool, setAccessRights, (uid_t f_uid), (noexcept(false))); + MOCK_METHOD(bool, pop, (), (noexcept(true))); +}; + +/// @brief @see tests/utils/mocks/README.md#Header%20replacement for explanation and usage +extern std::unique_ptr g_mock_checkpoint_ipc_server; + +class CheckpointIpcServer +{ + public: + using Payload = CheckpointBufferElement; + using EIpcInitResult = EMockIpcInitResult; + using EIpcPeekResult = EMockIpcPeekResult; + + std::string_view getPath() const noexcept(true) + { + return g_mock_checkpoint_ipc_server->getPath(); + } + + bool hasOverflow() noexcept(true) + { + return g_mock_checkpoint_ipc_server->hasOverflow(); + } + + EIpcPeekResult peek(Payload*& f_elem_p) noexcept(true) + { + return g_mock_checkpoint_ipc_server->peek(f_elem_p); + } + + EIpcInitResult init(const std::string& f_ipcName_r, mode_t f_mode) noexcept(false) + { + return g_mock_checkpoint_ipc_server->init(f_ipcName_r, f_mode); + } + + bool setAccessRights(uid_t f_uid) noexcept(false) + { + return g_mock_checkpoint_ipc_server->setAccessRights(f_uid); + } + + bool pop() noexcept(true) + { + return g_mock_checkpoint_ipc_server->pop(); + } +}; + +} // namespace score::mw::lifecycle::internal::saf::ifappl + +#endif diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks/MonitorIfDaemon.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks/MonitorIfDaemon.hpp new file mode 100644 index 0000000000..5bcf1e7fc4 --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/ifappl/injection_mocks/MonitorIfDaemon.hpp @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef MOCK_MONITORIFDAEMON_HPP_INCLUDED +#define MOCK_MONITORIFDAEMON_HPP_INCLUDED + +#include "score/mw/launch_manager/alive_monitor/details/ifappl/Checkpoint.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifappl/DataStructures.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifexm/ObservableEvent.hpp" +#include "score/mw/launch_manager/common/identifier_hash.hpp" + +#include + +namespace score::mw::lifecycle::internal::saf::ifappl +{ + +struct MockMonitorIfDaemon +{ + MOCK_METHOD(IdentifierHash, getIdentifier, (), (const noexcept(true))); + MOCK_METHOD(void, updateData, (const ifexm::ObservableEvent& f_observable_r), (noexcept(true))); + MOCK_METHOD(void, checkForNewData, (const std::chrono::nanoseconds f_syncTimestamp), (noexcept(true))); + // Not a real method, but we will call this from the constructor to inject exceptions + MOCK_METHOD(void, ctor, (CheckpointIpcServer & f_ipcServer_r, const char* f_interfaceName_p), (noexcept(false))); +}; + +/// @brief @see tests/utils/mocks/README.md#Header%20replacement for explanation and usage +extern std::unique_ptr g_mock_monitor_if_daemon; + +class MonitorIfDaemon : public common::Observer, public common::Observable +{ + public: + explicit MonitorIfDaemon(CheckpointIpcServer& f_ipcServer_r, const char* f_interfaceName_p) noexcept(false) + { + if (g_mock_monitor_if_daemon) + { + g_mock_monitor_if_daemon->ctor(f_ipcServer_r, f_interfaceName_p); + } + } + + IdentifierHash getIdentifier() const noexcept(true) override + { + return g_mock_monitor_if_daemon->getIdentifier(); + } + + void updateData(const ifexm::ObservableEvent& f_observable_r) noexcept(true) override + { + g_mock_monitor_if_daemon->updateData(f_observable_r); + } + + void checkForNewData(const std::chrono::nanoseconds f_syncTimestamp) noexcept(true) + { + g_mock_monitor_if_daemon->checkForNewData(f_syncTimestamp); + } +}; + +} // namespace score::mw::lifecycle::internal::saf::ifappl + +#endif diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/BUILD b/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/BUILD index fc12c2c92a..7bd8d4ac97 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/BUILD +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/BUILD @@ -27,6 +27,30 @@ cc_library( ], ) +cc_library( + name = "i_observable_event_reader", + hdrs = ["IObservableEventReader.hpp"], + include_prefix = "score/mw/launch_manager/alive_monitor/details/ifexm", + strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm", + visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], + deps = [ + ":observable_event", + "//score/launch_manager/src/daemon/src/common:identifier_hash", + ], +) + +cc_library( + name = "mock_observable_event_reader", + testonly = True, + hdrs = ["MockObservableEventReader.hpp"], + include_prefix = "score/mw/launch_manager/alive_monitor/details/ifexm", + strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm", + visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], + deps = [ + ":i_observable_event_reader", + ], +) + cc_library( name = "observable_event_reader", srcs = ["ObservableEventReader.cpp"], @@ -35,6 +59,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm", visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], deps = [ + ":i_observable_event_reader", ":observable_event", ":supervision_event", "//score/launch_manager:error_event", diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/IObservableEventReader.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/IObservableEventReader.hpp new file mode 100644 index 0000000000..63d85023c7 --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/IObservableEventReader.hpp @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef I_OBSERVABLEEVENTREADER_HPP_INCLUDED +#define I_OBSERVABLEEVENTREADER_HPP_INCLUDED + +#include "score/mw/launch_manager/alive_monitor/details/ifexm/ObservableEvent.hpp" +#include "score/mw/launch_manager/common/identifier_hash.hpp" + +namespace score::mw::lifecycle::internal::saf::ifexm +{ + +/// @brief Observable Event reader interface +/// @details The Observable Event reader fetches supervision events via the lcm library and distributes +/// the information to the Observable Event classes. +class IObservableEventReader +{ + public: + virtual ~IObservableEventReader() = default; + + /// @brief Register observable events for reader + /// @param [in] f_processState_r Process state to be registered + /// @param [in] f_processId Process ID + /// @return true (registered), false (not registered) + virtual bool registerObservableEvent(ObservableEvent& f_processState_r, const IdentifierHash f_processId) noexcept( + false) = 0; + + /// @brief Deregister observable events from reader + /// @param [in] f_processId Process ID to deregister the particular process + virtual void deregisterObservableEvent(const IdentifierHash f_processId) noexcept = 0; + + /// @brief Distribute changes + /// @details Distribute supervision events to the registered Observable Event classes + /// @param [in] f_syncTimestamp Timestamp for cyclic synchronization + /// @return true (successful distribution), false (failed distribution) + virtual bool distributeChanges(const std::chrono::nanoseconds f_syncTimestamp) noexcept = 0; +}; + +} // namespace score::mw::lifecycle::internal::saf::ifexm + +#endif diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/MockObservableEventReader.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/MockObservableEventReader.hpp new file mode 100644 index 0000000000..f394c52537 --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/MockObservableEventReader.hpp @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef MOCK_OBSERVABLEEVENTREADER_HPP_INCLUDED +#define MOCK_OBSERVABLEEVENTREADER_HPP_INCLUDED + +#include "score/mw/launch_manager/alive_monitor/details/ifexm/IObservableEventReader.hpp" +#include + +namespace score::mw::lifecycle::internal::saf::ifexm +{ + +class MockObservableEventReader : public IObservableEventReader +{ + public: + MOCK_METHOD( + bool, + registerObservableEvent, + (ObservableEvent & f_processState_r, const IdentifierHash f_processId), + (noexcept(false), override)); + MOCK_METHOD(void, deregisterObservableEvent, (const IdentifierHash f_processId), (noexcept, override)); + MOCK_METHOD(bool, distributeChanges, (const std::chrono::nanoseconds f_syncTimestamp), (noexcept, override)); +}; + +} // namespace score::mw::lifecycle::internal::saf::ifexm + +#endif diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/ObservableEventReader.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/ObservableEventReader.hpp index 8b2ce348b6..90975aa28c 100644 --- a/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/ObservableEventReader.hpp +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/ifexm/ObservableEventReader.hpp @@ -16,6 +16,7 @@ #include +#include "score/mw/launch_manager/alive_monitor/details/ifexm/IObservableEventReader.hpp" #include "score/mw/launch_manager/alive_monitor/details/ifexm/ObservableEvent.hpp" #include "score/mw/launch_manager/alive_monitor/details/ifexm/supervision_event.hpp" #include "score/mw/launch_manager/alive_monitor/details/timers/Timers_OsClock.hpp" @@ -28,7 +29,7 @@ namespace score::mw::lifecycle::internal::saf::ifexm /// @brief Observable Event reader /// @details The Observable Event reader fetches supervision events via the lcm library and distributes /// the information to the Observable Event classes. -class ObservableEventReader +class ObservableEventReader final : public IObservableEventReader { public: /// @brief Constructor @@ -51,17 +52,18 @@ class ObservableEventReader /// @param [in] f_processState_r Process state to be registered /// @param [in] f_processId Process ID /// @return true (registered), false (not registered) - bool registerObservableEvent(ObservableEvent& f_processState_r, const IdentifierHash f_processId) noexcept(false); + bool registerObservableEvent(ObservableEvent& f_processState_r, const IdentifierHash f_processId) noexcept( + false) override; /// @brief Deregister observable events from reader /// @param [in] f_processId Process ID to deregister the particular process - void deregisterObservableEvent(const IdentifierHash f_processId) noexcept; + void deregisterObservableEvent(const IdentifierHash f_processId) noexcept override; /// @brief Distribute changes /// @details Distribute supervision events to the registered Observable Event classes /// @param [in] f_syncTimestamp Timestamp for cyclic synchronization /// @return true (successful distribution), false (failed distribution) - bool distributeChanges(const std::chrono::nanoseconds f_syncTimestamp) noexcept; + bool distributeChanges(const std::chrono::nanoseconds f_syncTimestamp) noexcept override; private: /// @brief Push update for changed registered process diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/supervision/injection_mocks/Alive.hpp b/score/launch_manager/src/daemon/src/alive_monitor/details/supervision/injection_mocks/Alive.hpp new file mode 100644 index 0000000000..c00da7e1d3 --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/supervision/injection_mocks/Alive.hpp @@ -0,0 +1,133 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef ALIVE_HPP_INCLUDED +#define ALIVE_HPP_INCLUDED + +#include +#include + +#include "score/mw/launch_manager/alive_monitor/details/common/Observer.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifappl/Checkpoint.hpp" +#include "score/mw/launch_manager/alive_monitor/details/ifexm/ObservableEvent.hpp" +#include "score/mw/launch_manager/alive_monitor/details/supervision/ISupervision.hpp" +#include "score/mw/launch_manager/configuration/config.hpp" +#include "score/mw/launch_manager/recovery_client/irecovery_client.h" + +namespace score::mw::lifecycle::internal::saf::supervision +{ + +using configuration::ComponentAliveSupervision; + +enum class EMockStatus : uint32_t +{ + kOk = 0U, + kFailed = 1U, + kExpired = 2U, + kDeactivated = 4U +}; + +class MockAlive +{ + public: + using EStatus = EMockStatus; + + MOCK_METHOD( + void, + ctor, + (const IdentifierHash id, + const ComponentAliveSupervision& f_aliveCfg_r, + const std::shared_ptr recovery_client, + common::Observable& interface, + const uint16_t bufferSize), + (noexcept(false))); + MOCK_METHOD(void, updateData, (const ifappl::Checkpoint& f_observable_r), (noexcept(true))); + MOCK_METHOD(void, updateData, (const ifexm::ObservableEvent& f_observable_r), (noexcept(true))); + MOCK_METHOD(void, evaluate, (const std::chrono::nanoseconds f_syncTimestamp), ()); + MOCK_METHOD(EStatus, getStatus, (), (const, noexcept(true))); + MOCK_METHOD(std::chrono::nanoseconds, getTimestamp, (), (const, noexcept(true))); + MOCK_METHOD(bool, hasRecoveryEnqueueFailed, (), (const, noexcept)); + MOCK_METHOD(IdentifierHash, getIdentifier, (), (const, noexcept)); +}; + +/// @brief @see tests/utils/mocks/README.md#Header%20replacement for explanation and usage +extern std::unique_ptr g_mock_alive_supervision; + +class Alive : public ISupervision, + public saf::common::Observable, + public saf::common::Observer, + public saf::common::Observer +{ + public: + using EStatus = EMockStatus; + + Alive() = delete; + Alive(Alive&&) = default; + Alive& operator=(Alive&&) = delete; + Alive(const Alive&) = delete; + Alive& operator=(const Alive&) = delete; + ~Alive() override = default; + + explicit Alive( + const IdentifierHash id, + const ComponentAliveSupervision& f_aliveCfg_r, + const std::shared_ptr recovery_client, + common::Observable& interface, + const uint16_t bufferSize) noexcept(false) + : ISupervision(id) + { + if (g_mock_alive_supervision) + { + g_mock_alive_supervision->ctor(id, f_aliveCfg_r, recovery_client, interface, bufferSize); + } + } + + void updateData(const ifappl::Checkpoint& f_observable_r) noexcept(true) override + { + g_mock_alive_supervision->updateData(f_observable_r); + } + + void updateData(const ifexm::ObservableEvent& f_observable_r) noexcept(true) override + { + g_mock_alive_supervision->updateData(f_observable_r); + } + + void evaluate(const std::chrono::nanoseconds f_syncTimestamp) override + { + g_mock_alive_supervision->evaluate(f_syncTimestamp); + } + + EStatus getStatus(void) const noexcept(true) + { + return g_mock_alive_supervision->getStatus(); + } + + std::chrono::nanoseconds getTimestamp(void) const noexcept(true) + { + return g_mock_alive_supervision->getTimestamp(); + } + + bool hasRecoveryEnqueueFailed(void) const noexcept + { + return g_mock_alive_supervision->hasRecoveryEnqueueFailed(); + } + + IdentifierHash getIdentifier() const noexcept override + { + return g_mock_alive_supervision->getIdentifier(); + } +}; + +} // namespace score::mw::lifecycle::internal::saf::supervision + +#endif diff --git a/score/launch_manager/src/daemon/src/alive_monitor/details/supervision/injection_mocks/BUILD b/score/launch_manager/src/daemon/src/alive_monitor/details/supervision/injection_mocks/BUILD new file mode 100644 index 0000000000..1a3789157f --- /dev/null +++ b/score/launch_manager/src/daemon/src/alive_monitor/details/supervision/injection_mocks/BUILD @@ -0,0 +1,32 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("@rules_cc//cc:defs.bzl", "cc_library") +load("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") + +cc_library( + name = "mock_alive", + testonly = True, + hdrs = ["Alive.hpp"], + include_prefix = "score/mw/launch_manager/alive_monitor/details/supervision", + strip_include_prefix = "/score/launch_manager/src/daemon/src/alive_monitor/details/supervision/injection_mocks", + visibility = ["//score/launch_manager/src/daemon/src/alive_monitor:__subpackages__"], + deps = [ + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifappl:checkpoint", + "//score/launch_manager/src/daemon/src/alive_monitor/details/ifexm:observable_event", + "//score/launch_manager/src/daemon/src/alive_monitor/details/supervision:i_supervision", + "//score/launch_manager/src/daemon/src/configuration:config", + "//score/launch_manager/src/daemon/src/recovery_client:irecovery_client", + "@googletest//:gtest_main", + "@score_baselibs//score/language/futurecpp", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp index bbe85e1827..cf14ffd993 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp @@ -16,7 +16,7 @@ #include "score/mw/launch_manager/alive_monitor/mock_alive_monitor.hpp" #include "score/mw/launch_manager/alive_monitor/mock_alive_supervision_handle.hpp" #include "score/mw/launch_manager/alive_monitor/mock_supervision_factory.hpp" -#include "score/mw/launch_manager/recovery_client/mock_irecovery_client.h" +#include "score/mw/launch_manager/recovery_client/mock_recovery_client.h" #include "score/mw/launch_manager/watchdog/mock_IWatchdogIf.hpp" #include diff --git a/score/launch_manager/src/daemon/src/recovery_client/BUILD b/score/launch_manager/src/daemon/src/recovery_client/BUILD index 6ed52cc50a..c7f2147169 100644 --- a/score/launch_manager/src/daemon/src/recovery_client/BUILD +++ b/score/launch_manager/src/daemon/src/recovery_client/BUILD @@ -49,7 +49,7 @@ cc_library( cc_library( name = "mock_recovery_client", testonly = True, - hdrs = ["mock_irecovery_client.h"], + hdrs = ["mock_recovery_client.h"], include_prefix = "score/mw/launch_manager/recovery_client", strip_include_prefix = "/score/launch_manager/src/daemon/src/recovery_client", visibility = ["//score:__subpackages__"], diff --git a/score/launch_manager/src/daemon/src/recovery_client/mock_irecovery_client.h b/score/launch_manager/src/daemon/src/recovery_client/mock_recovery_client.h similarity index 100% rename from score/launch_manager/src/daemon/src/recovery_client/mock_irecovery_client.h rename to score/launch_manager/src/daemon/src/recovery_client/mock_recovery_client.h diff --git a/tests/utils/mocks/README.md b/tests/utils/mocks/README.md new file mode 100644 index 0000000000..69e09cd309 --- /dev/null +++ b/tests/utils/mocks/README.md @@ -0,0 +1,40 @@ + + +# Unit test mocks + +Mocks in Lifecycle use GMock injected in a few different ways. + +## Dependency injection + +This is the preferred way to mock functionality for unit tests and can be used in any cases where a method or class uses an existing object. + +To mock a class `Foo` used in a method `Bar(Foo& param)` + +1. Define an interface `IFoo` declaring `Foo`'s public methods +2. Make `Foo` implement `IFoo` +3. Change signatures accepting `Foo` to accept `IFoo` +4. Create a gmock `MockFoo` that also implements `IFoo` +5. Pass an instance of `MockFoo` to the methods under test + +## Linker overrides + +For syscalls or `extern "C"` free functions, linker overrides are used. The Bazel target for the test must use `linkstatic = True` and a `linkopts` argument, `"-Wl,--wrap=func_to_wrap"`, for each wrapped function. + +See [[tests/utils/mocks/mock_syscalls.hpp]] for examples of how to write gmocks for these functions. + +## Header replacement + +These mocks are needed when construction, destruction, or static methods need to be mocked. They declare a mock header to replace one that source files link against and are usually found in an `injection_mocks` subdirectory. The file name is identical and Bazel's `strip_include_prefix` is used to ensure that mock header paths match the real headers. An additional Bazel target is used that includes the mock targets instead of the real dependencies. + +Each header file has an `extern std::unique_ptr` that must be defined and managed within test files to allow use of the mock. \ No newline at end of file