diff --git a/.clang-tidy b/.clang-tidy index d63a95901..442969ca3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,2 +1,5 @@ Checks: ' -*, bugprone-*, cert-*, clang-analyzer-core.*, clang-analyzer-cplusplus.*, clang-analyzer-security.*, cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-cstyle-cast, hicpp-* ' WarningsAsErrors: 'bugprone-*,cert-*,clang-analyzer-*,hicpp-*' +CheckOptions: + - key: 'hicpp-special-member-functions.AllowSoleDefaultDtor' + - value: 1 diff --git a/.github/workflows/code_coverage.yml b/.github/workflows/code_coverage.yml index 8d75225e5..ff76864e5 100644 --- a/.github/workflows/code_coverage.yml +++ b/.github/workflows/code_coverage.yml @@ -28,7 +28,7 @@ jobs: with: bazel-target: "//score/..." bazel-config: "x86_64-linux" - extra-bazel-flags: "--test_output=errors --nocache_test_results --lockfile_mode=error --test_lang_filters=-rust,-miri" + extra-bazel-flags: "--test_output=errors --nocache_test_results --lockfile_mode=error --test_lang_filters=-rust,-miri --instrumentation_filter=-mock_*" artifact-name-suffix: "_cpp" retention-days: 10 min-coverage: 76 diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD index 2bc35491f..6b0f00e10 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/BUILD @@ -10,7 +10,128 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +cc_library( + name = "component_task", + hdrs = ["component_task.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":icomponent", + ], +) + +cc_library( + name = "icomponent_controller", + hdrs = ["icomponent_controller.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":component_task", + ":icomponent", + ], +) + +cc_library( + name = "icomponent", + hdrs = ["icomponent.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + "@score_baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "mock_component", + testonly = True, + hdrs = ["mock_component.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":icomponent", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "mock_component_event_queue", + testonly = True, + hdrs = ["mock_component_event_queue.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":icomponent_event_publisher_consumer", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "mock_termination_callback", + testonly = True, + hdrs = ["mock_termination_callback.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score:__subpackages__"], + deps = [ + ":safe_process_map", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "component_event", + hdrs = ["component_event.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":icomponent", + "//score/launch_manager/src/daemon/src/common:identifier_hash", + ], +) + +cc_library( + name = "component_event_queue", + hdrs = ["component_event_queue.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":icomponent_event_publisher_consumer", + "//score/launch_manager/src/daemon/src/common:constants", + "//score/launch_manager/src/daemon/src/common:identifier_hash", + "//score/launch_manager/src/daemon/src/common/concurrency:mpsc_bounded_queue", + ], +) + +cc_library( + name = "icomponent_event_publisher_consumer", + hdrs = ["icomponent_event_publisher_consumer.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":component_event", + "//score/launch_manager/src/daemon/src/common:constants", + "//score/launch_manager/src/daemon/src/common:identifier_hash", + ], +) + +cc_test( + name = "component_event_queue_UT", + srcs = ["component_event_queue_UT.cpp"], + deps = [ + ":component_event_queue", + "@googletest//:gtest_main", + ], +) cc_library( name = "process_info_node", @@ -23,6 +144,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ + ":icomponent", ":safe_process_map", "//score/launch_manager/src/daemon/src/control:control_client_channel", "//score/launch_manager/src/daemon/src/osal:ipc_comms", @@ -39,6 +161,17 @@ cc_library( }), ) +cc_library( + name = "run_target", + hdrs = ["run_target.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":icomponent", + ], +) + cc_library( name = "graph", hdrs = ["graph.hpp"], @@ -46,7 +179,10 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ + ":component_event_queue", + ":component_task", ":process_info_node", + ":run_target", ":safe_process_map", "//score/launch_manager/src/daemon/src/common:identifier_hash", "//score/launch_manager/src/daemon/src/control:control_client_channel", @@ -63,6 +199,7 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ + ":icomponent_controller", "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", ], ) @@ -72,6 +209,7 @@ cc_test( timeout = "long", srcs = ["safeprocessmap_UT.cpp"], deps = [ + ":mock_termination_callback", ":safe_process_map", "@googletest//:gtest_main", ], @@ -92,10 +230,40 @@ cc_library( ], ) +cc_library( + name = "process_monitor", + srcs = ["process_monitor.cpp"], + hdrs = ["process_monitor.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + ":graph", + ":icomponent_controller", + ":icomponent_event_publisher_consumer", + ":os_handler", + ":process_info_node", + ":safe_process_map", + "//score/launch_manager/src/daemon/src/common:log", + ], +) + +cc_test( + name = "process_monitor_UT", + srcs = ["process_monitor_UT.cpp"], + deps = [ + ":mock_component", + ":mock_component_event_queue", + ":process_monitor", + "@googletest//:gtest_main", + ], +) + cc_test( name = "oshandler_UT", srcs = ["oshandler_UT.cpp"], deps = [ + ":mock_termination_callback", ":os_handler", ":safe_process_map", "@googletest//:gtest_main", @@ -135,10 +303,12 @@ cc_library( }), visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], deps = [ + ":component_event_queue", ":graph", ":os_handler", ":process_info_node", ":process_launcher", + ":process_monitor", ":safe_process_map", "//score/launch_manager/src/daemon/src/common:log", "//score/launch_manager/src/daemon/src/common/concurrency:mpmc_concurrent_queue", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp new file mode 100644 index 000000000..2636e76e1 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event.hpp @@ -0,0 +1,66 @@ +/******************************************************************************** + * 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 SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED +#define SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED + +#include +#include + +#include "score/mw/launch_manager/common/identifier_hash.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" + +namespace score::mw::lifecycle::internal +{ + +using namespace score::lcm; + +/// @brief A node finished activating successfully. +struct [[nodiscard]] ActivationSuccessful +{ + uint32_t node_index; +}; + +/// @brief A node failed to activate. +struct [[nodiscard]] ActivationFailed +{ + uint32_t node_index; + IComponent::ComponentError reason; +}; + +/// @brief A node finished deactivating. +struct [[nodiscard]] DeactivationComplete +{ + uint32_t node_index; +}; + +/// @brief A node terminated without having been requested to. +struct [[nodiscard]] UnexpectedTermination +{ + uint32_t node_index; +}; + +/// @brief Alive supervision has failed for the given process identifier. +struct [[nodiscard]] SupervisionFailure +{ + IdentifierHash process_identifier; +}; + +/// @brief A graph-relevant state change. There is only ever a single graph, so no process-group +/// identifier is needed to route most events. SupervisionFailure is routed by process identifier. +using ComponentEvent = std:: + variant; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_COMPONENT_EVENT_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp new file mode 100644 index 000000000..7af64d883 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue.hpp @@ -0,0 +1,105 @@ +/******************************************************************************** + * 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 SCORE_LCM_COMPONENT_EVENT_QUEUE_HPP_INCLUDED +#define SCORE_LCM_COMPONENT_EVENT_QUEUE_HPP_INCLUDED + +#include +#include +#include + +#include "score/mw/launch_manager/common/concurrency/concurrency_error_domain.hpp" +#include "score/mw/launch_manager/common/concurrency/mpsc_bounded_queue.hpp" +#include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_publisher_consumer.hpp" + +namespace score::mw::lifecycle::internal +{ + +/// @brief Queue of ComponentEvents produced by worker/OS-handler threads and consumed +/// exclusively by the main thread, backed by a fixed-capacity MpscBoundedQueue. +class ComponentEventQueue final : public IComponentEventPublisherConsumer +{ + public: + explicit ComponentEventQueue(std::size_t components) : queue_(components * 3U), capacity_(components * 3U) + { + } + + ~ComponentEventQueue() + { + stop(); + } + + ComponentEventQueue(const ComponentEventQueue&) = delete; + ComponentEventQueue& operator=(const ComponentEventQueue&) = delete; + ComponentEventQueue(ComponentEventQueue&&) = delete; + ComponentEventQueue& operator=(ComponentEventQueue&&) = delete; + + /// @brief Enqueues an event. If the queue is full, the event is dropped immediately and + /// getOverflow() will subsequently return true. + /// @returns False if the event is dropped. + [[nodiscard]] bool push(ComponentEvent&& event) override + { + auto result = queue_.push(std::move(event)); + if (!result.has_value() && result.error() == lcm::internal::ConcurrencyErrc::kOverflow) + { + overflow_.store(true, std::memory_order_release); + return false; + } + return true; + } + + /// @brief Waits up to timeout for at least one event to become available. + /// @param timeout Maximum time to wait. A `timeout` of zero means "check once, don't + /// block", NOT "wait forever" -- see MpscBoundedQueue::wait(). + /// @return true if an event is available (drain via getNextEvent()), false on timeout. + [[nodiscard]] bool waitForEvents(std::chrono::milliseconds timeout) override + { + return queue_.wait(timeout).has_value(); + } + + /// @brief Returns the next available event without blocking, or std::nullopt if none right + /// now. Call repeatedly until nullopt to drain everything currently queued. + [[nodiscard]] std::optional getNextEvent() override + { + return queue_.tryPop(); + } + + /// @return True if an event was ever dropped due to the queue being full. + [[nodiscard]] bool getOverflow() const override + { + return overflow_.load(std::memory_order_acquire); + } + + /// @brief Permanently marks the queue stopped and wakes any thread currently blocked in + /// waitForEvents(). + void stop() override + { + queue_.stop(); + } + + /// @brief Get the max number of items that can be stored in this queue + [[nodiscard]] std::size_t capacity() override + { + return capacity_; + } + + private: + lcm::internal::MpscBoundedQueue queue_; + std::size_t capacity_; + std::atomic overflow_{false}; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_COMPONENT_EVENT_QUEUE_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp new file mode 100644 index 000000000..d3e70d45b --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_event_queue_UT.cpp @@ -0,0 +1,136 @@ +/******************************************************************************** + * 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 "score/mw/launch_manager/process_group_manager/details/component_event_queue.hpp" + +#include +#include + +namespace score::mw::lifecycle::internal +{ + +using namespace score::lcm; + +class ComponentEventQueueTest : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + } + + ComponentEventQueue queue_{10}; +}; + +TEST_F(ComponentEventQueueTest, WaitForEventsReturnsFalseOnEmptyQueue) +{ + RecordProperty("Description", "Verify waitForEvents returns false promptly when no event has been pushed."); + EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{0})); +} + +TEST_F(ComponentEventQueueTest, WaitForEventsReturnsTrueAfterPush) +{ + RecordProperty("Description", "Verify waitForEvents returns true once an event has been pushed."); + EXPECT_TRUE(queue_.push(ActivationSuccessful{7U})); + EXPECT_TRUE(queue_.waitForEvents(std::chrono::milliseconds{0})); +} + +TEST_F(ComponentEventQueueTest, GetNextEventReturnsNulloptWhenEmpty) +{ + RecordProperty("Description", "Verify getNextEvent returns nullopt when nothing is queued."); + EXPECT_FALSE(queue_.getNextEvent().has_value()); +} + +TEST_F(ComponentEventQueueTest, GetNextEventReturnsPushedEventWithPayloadIntact) +{ + RecordProperty("Description", "Verify a pushed event is returned by getNextEvent with its payload preserved."); + EXPECT_TRUE(queue_.push(ActivationFailed{3U, IComponent::ComponentError::kErrorBeforeReady})); + + auto event = queue_.getNextEvent(); + ASSERT_TRUE(event.has_value()); + ASSERT_TRUE(std::holds_alternative(*event)); + const auto& failed = std::get(*event); + EXPECT_EQ(failed.node_index, 3U); + EXPECT_EQ(failed.reason, IComponent::ComponentError::kErrorBeforeReady); +} + +TEST_F(ComponentEventQueueTest, GetNextEventReturnsSupervisionFailureWithPayloadIntact) +{ + RecordProperty( + "Description", + "Verify a pushed SupervisionFailure event is returned by getNextEvent with process identifier " + "payload preserved."); + const IdentifierHash process_identifier{"proc_for_supervision_failure"}; + EXPECT_TRUE(queue_.push(SupervisionFailure{process_identifier})); + + auto event = queue_.getNextEvent(); + ASSERT_TRUE(event.has_value()); + ASSERT_TRUE(std::holds_alternative(*event)); + const auto& failure = std::get(*event); + EXPECT_EQ(failure.process_identifier, process_identifier); +} + +TEST_F(ComponentEventQueueTest, GetOverflowStaysFalseUnderNormalUsage) +{ + RecordProperty("Description", "Verify getOverflow() stays false when events are pushed and drained normally."); + EXPECT_TRUE(queue_.push(ActivationSuccessful{1U})); + static_cast(queue_.getNextEvent()); + EXPECT_FALSE(queue_.getOverflow()); +} + +TEST_F(ComponentEventQueueTest, GetOverflowBecomesTrueOnceQueueIsFull) +{ + RecordProperty( + "Description", + "Verify getOverflow() becomes true once a push is dropped because the queue is full, " + "mirroring how ProcessGroupManager::run() detects lost events."); + for (std::size_t i = 0U; i < queue_.capacity(); ++i) + { + EXPECT_TRUE(queue_.push(ActivationSuccessful{static_cast(i)})); + } + EXPECT_FALSE(queue_.getOverflow()); + + // One more push while the queue is already at capacity and nobody is draining it: this + // push is dropped immediately, flagging overflow. + EXPECT_FALSE(queue_.push(ActivationSuccessful{9999U})); + EXPECT_TRUE(queue_.getOverflow()); +} + +TEST_F(ComponentEventQueueTest, StopFailsWaitForEventsOnEmptyQueue) +{ + RecordProperty( + "Description", + "Verify stop() causes a subsequently-called waitForEvents() to return false, even if there's an event in the " + "queue"); + EXPECT_TRUE(queue_.push(ActivationSuccessful{1})); + queue_.stop(); + EXPECT_FALSE(queue_.waitForEvents(std::chrono::milliseconds{0})); +} + +TEST_F(ComponentEventQueueTest, GetNextEventStillDrainsQueuedEventsAfterStop) +{ + RecordProperty( + "Description", + "Verify that events pushed before stop() was called are not silently discarded -- " + "getNextEvent() must still be able to drain them during shutdown."); + EXPECT_TRUE(queue_.push(ActivationSuccessful{1U})); + queue_.stop(); + + auto event = queue_.getNextEvent(); + ASSERT_TRUE(event.has_value()); + EXPECT_TRUE(std::holds_alternative(*event)); + EXPECT_FALSE(queue_.getNextEvent().has_value()); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp new file mode 100644 index 000000000..a162f1885 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_task.hpp @@ -0,0 +1,48 @@ +/******************************************************************************** + * 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 SCORE_LCM_COMPONENT_TASK_HPP_INCLUDED +#define SCORE_LCM_COMPONENT_TASK_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" +#include +#include +#include + +namespace score::mw::lifecycle::internal +{ + +enum class ComponentTaskType : std::uint_least8_t +{ + /// @brief This task is to start activation of the component + kActivate = 0U, + /// @brief This task is to deactivate the component + kDeactivate = 1U, +}; + +/// @brief Work to perform on a component that can be sent to the job queue to be executed in parallel. +struct [[nodiscard]] ComponentTask +{ + /// @brief What kind of work to do + ComponentTaskType type; + + /// @brief The component to be acted upon + std::reference_wrapper component; + + /// @brief Token to exit and abandon the task early + score::cpp::stop_token stop_token; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_COMPONENT_TASK_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp new file mode 100644 index 000000000..aceda1399 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent.hpp @@ -0,0 +1,81 @@ +/******************************************************************************** + * 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 SCORE_LCM_ICOMPONENT_HPP_INCLUDED +#define SCORE_LCM_ICOMPONENT_HPP_INCLUDED + +#include +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief A class that implements IComponent is a node in the configuration that can be activated or deactivated. +/// +/// @details Each component type defines the implementation of how it shall reach the active and inactive states. +/// When a component becomes active, components that depend on it may be activated. When a component becomes +/// inactive, components it depends on may be deactivated. +/// For example, for a process implementation of IComponent, @c activate() could involve starting the process +/// and @c deactivate() could terminate the process. +class IComponent +{ + public: + enum class ComponentError : uint8_t + { + /// @brief An error occurred during startup, before the component was considered ready. + kErrorBeforeReady, + /// @brief An error occurred any time after the component was considered ready. + kErrorAfterReady, + /// @brief An error occurred during startup. Specifically, a timeout was reached. + kActivationTimedOut, + }; + + enum class RequestState : uint8_t + { + /// @brief Activation was successful and the component is now ready. + kSuccess, + /// @brief Activation is waiting on a notification from another thread. The component may not be ready. + kWaiting + }; + + using RequestResult = score::cpp::expected; + + /// @brief Begin activation of the component. + /// @p stop_token Token that can be stopped to exit the activation early. + /// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an + /// error. + [[nodiscard]] virtual RequestResult activate(score::cpp::stop_token stop_token) = 0; + + /// @brief Begin deactivation of the component. + /// @p stop_token Token that can be stopped to exit the deactivation early. + /// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an + /// error. + [[nodiscard]] virtual RequestResult deactivate(score::cpp::stop_token stop_token) = 0; + + /// @brief Notify the component that it has terminated with status @p status + /// @returns kSuccess if the component is now ready, kWaiting if the component is waiting for a notification, or an + /// error if the termination was not expected. + [[nodiscard]] virtual RequestResult tryHandleTermination(int32_t status) = 0; + + /// @returns the index of the component in the graph. + [[nodiscard]] virtual uint32_t getIndex() const = 0; + + /// @returns True if the component is active in the active run target. + [[nodiscard]] virtual bool active() const = 0; + + virtual ~IComponent() = default; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_ICOMPONENT_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp new file mode 100644 index 000000000..9c212d351 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_controller.hpp @@ -0,0 +1,40 @@ +/******************************************************************************** + * 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 SCORE_LCM_ICOMPONENT_CONTROLLER_HPP_INCLUDED +#define SCORE_LCM_ICOMPONENT_CONTROLLER_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/component_task.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" + +namespace score::mw::lifecycle::internal +{ + +/// @brief Interface to send requests & notifications about components. The class should handle these notifications by +/// informing the component, the graph, or event queue. +class IComponentController +{ + public: + /// @brief Start work on @p task and push the result to the event queue if the task completes + virtual void doWork(ComponentTask&& task) = 0; + + /// @brief Notify @p component that it has terminated with status @p status. Forward the appropriate event to the + /// event queue + virtual void terminated(IComponent& component, int32_t status) = 0; + + virtual ~IComponentController() = default; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_ICOMPONENT_CONTROLLER_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_publisher_consumer.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_publisher_consumer.hpp new file mode 100644 index 000000000..fddf7f33f --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/icomponent_event_publisher_consumer.hpp @@ -0,0 +1,53 @@ +/******************************************************************************** + * 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 SCORE_LCM_ICOMPONENT_EVENT_PUBLISHER_CONSUMER_HPP_INCLUDED +#define SCORE_LCM_ICOMPONENT_EVENT_PUBLISHER_CONSUMER_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/component_event.hpp" + +namespace score::mw::lifecycle::internal +{ + +/// @brief Interface an event producer can push component events to +class IComponentEventPublisher +{ + public: + [[nodiscard]] virtual bool push(ComponentEvent&& event) = 0; + [[nodiscard]] virtual bool getOverflow() const = 0; + [[nodiscard]] virtual std::size_t capacity() = 0; + + virtual ~IComponentEventPublisher() = default; +}; + +/// @brief Interface an event consumer can get component events from +class IComponentEventConsumer +{ + public: + [[nodiscard]] virtual bool waitForEvents(std::chrono::milliseconds timeout) = 0; + [[nodiscard]] virtual std::optional getNextEvent() = 0; + virtual void stop() = 0; + + virtual ~IComponentEventConsumer() = default; +}; + +/// @brief Interface including publisher and consumer methods +class IComponentEventPublisherConsumer : public IComponentEventPublisher, public IComponentEventConsumer +{ + public: + virtual ~IComponentEventPublisherConsumer() = default; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_ICOMPONENT_EVENT_PUBLISHER_CONSUMER_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp new file mode 100644 index 000000000..5fbe20165 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component.hpp @@ -0,0 +1,34 @@ +/******************************************************************************** + * 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_COMPONENT_HPP_INCLUDED +#define MOCK_COMPONENT_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockComponent : public IComponent +{ + public: + MOCK_METHOD(RequestResult, activate, (score::cpp::stop_token stop_token), (override)); + MOCK_METHOD(RequestResult, deactivate, (score::cpp::stop_token stop_token), (override)); + MOCK_METHOD(RequestResult, tryHandleTermination, (int32_t status), (override)); + MOCK_METHOD(uint32_t, getIndex, (), (override, const)); + MOCK_METHOD(bool, active, (), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_COMPONENT_HPP_INCLUDED \ No newline at end of file diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp new file mode 100644 index 000000000..d5f008146 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_component_event_queue.hpp @@ -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 + ********************************************************************************/ +#ifndef MOCK_COMPONENT_EVENT_QUEUE_HPP_INCLUDED +#define MOCK_COMPONENT_EVENT_QUEUE_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_publisher_consumer.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockComponentEventQueue : public IComponentEventPublisher +{ + public: + MOCK_METHOD(bool, push, (ComponentEvent && event), (override)); + MOCK_METHOD(bool, getOverflow, (), (override, const)); + MOCK_METHOD(std::size_t, capacity, (), ()); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_COMPONENT_EVENT_QUEUE_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_termination_callback.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_termination_callback.hpp new file mode 100644 index 000000000..904ffe040 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_termination_callback.hpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * 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_TERMINATION_CALLBACK_HPP_INCLUDED +#define MOCK_TERMINATION_CALLBACK_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/safe_process_map.hpp" +#include + +namespace score::lcm::internal +{ + +class MockTerminationCallback : public ITerminationCallback +{ + public: + MOCK_METHOD(void, terminated, (int32_t process_status), (override)); +}; + +} // namespace score::lcm::internal + +#endif // MOCK_TERMINATION_CALLBACK_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/oshandler_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/oshandler_UT.cpp index 997f6bcb0..c9c0735ca 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/oshandler_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/oshandler_UT.cpp @@ -22,6 +22,7 @@ #include "score/mw/launch_manager/common/constants.hpp" #include "score/mw/launch_manager/process_group_manager/details/safe_process_map.hpp" +#include "score/mw/launch_manager/process_group_manager/details/mock_termination_callback.hpp" #include "score/os/mocklib/sys_wait_mock.h" using namespace testing; @@ -30,12 +31,6 @@ using namespace score::lcm::internal; namespace { -class MockTerminationCallback : public ITerminationCallback -{ - public: - MOCK_METHOD(void, terminated, (int32_t process_status), (override)); -}; - class OsHandlerTest : public ::testing::Test { protected: diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp new file mode 100644 index 000000000..3c8c204df --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.cpp @@ -0,0 +1,128 @@ +/******************************************************************************** + * 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 "score/mw/launch_manager/process_group_manager/details/process_monitor.hpp" +#include "score/mw/launch_manager/common/log.hpp" + +namespace score::mw::lifecycle::internal +{ + +ProcessMonitor::ProcessMonitor(IComponentEventPublisher& event_queue) : event_queue_(event_queue) +{ +} + +ProcessMonitor::~ProcessMonitor() = default; + +void ProcessMonitor::doWork(ComponentTask&& task) +{ + auto run_task = [&]() { + auto& component = task.component.get(); + IComponent::RequestResult result; + + switch (task.type) + { + case ComponentTaskType::kActivate: + result = component.activate(task.stop_token); + break; + case ComponentTaskType::kDeactivate: + result = component.deactivate(task.stop_token); + break; + } + + return result; + }; + + auto handle_success = [&]() { + const uint32_t node_index = task.component.get().getIndex(); + bool push_res = true; + + switch (task.type) + { + case ComponentTaskType::kActivate: + push_res = event_queue_.push(ActivationSuccessful{node_index}); + break; + case ComponentTaskType::kDeactivate: + push_res = event_queue_.push(DeactivationComplete{node_index}); + break; + } + + if (!push_res) + { + LM_LOG_ERROR() << "Failed to send success to event queue!"; + } + }; + + auto handle_failure = [&](IComponent::ComponentError& error) { + const uint32_t node_index = task.component.get().getIndex(); + + switch (task.type) + { + case ComponentTaskType::kActivate: + if (!event_queue_.push(ActivationFailed{node_index, error})) + { + LM_LOG_ERROR() << "Failed to send activation failed event to event queue!"; + } + break; + case ComponentTaskType::kDeactivate: + break; + } + }; + + auto handle_state = [&](IComponent::RequestState& state) { + switch (state) + { + case IComponent::RequestState::kSuccess: + handle_success(); + break; + case IComponent::RequestState::kWaiting: + break; + } + }; + + if (task.stop_token.stop_requested()) + { + return; + } + + auto result = run_task(); + + if (result.has_value()) + { + handle_state(result.value()); + } + else + { + handle_failure(result.error()); + } +} + +void ProcessMonitor::terminated(IComponent& component, int32_t status) +{ + auto res = component.tryHandleTermination(status); + bool push_res = true; + if (!res.has_value()) + { + push_res = event_queue_.push(UnexpectedTermination{component.getIndex()}); + } + else if (res.value() != IComponent::RequestState::kWaiting) + { + push_res = event_queue_.push(ActivationSuccessful{component.getIndex()}); + } + + if (!push_res) + { + LM_LOG_ERROR() << "Failed to push terminated result to event queue!"; + } +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp new file mode 100644 index 000000000..d4c05fd9d --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor.hpp @@ -0,0 +1,49 @@ +/******************************************************************************** + * 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 PROCESS_MONITOR_HPP_INCLUDED +#define PROCESS_MONITOR_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent_controller.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent_event_publisher_consumer.hpp" + +namespace score::mw::lifecycle::internal +{ + +/// @brief Translates IComponentController callbacks (from worker threads and the OsHandler thread) +/// into ComponentEvents pushed onto the event queue for processing on the main thread. +class ProcessMonitor final : public IComponentController +{ + public: + explicit ProcessMonitor(IComponentEventPublisher& event_queue); + ~ProcessMonitor() override; + + ProcessMonitor(const ProcessMonitor&) = delete; + ProcessMonitor& operator=(const ProcessMonitor&) = delete; + ProcessMonitor(ProcessMonitor&&) = delete; + ProcessMonitor& operator=(ProcessMonitor&&) = delete; + + /// @brief Start work on @p task and push the result to the event queue if the task completes + void doWork(ComponentTask&& task) override; + + /// @brief Notify @p component that it has terminated with status @p status. If this is an error or finishes a + /// component activation, report to the event queue + void terminated(IComponent& component, int32_t status) override; + + private: + IComponentEventPublisher& event_queue_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // PROCESS_MONITOR_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp new file mode 100644 index 000000000..8bba2f979 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_monitor_UT.cpp @@ -0,0 +1,110 @@ +/******************************************************************************** + * 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 "score/mw/launch_manager/process_group_manager/details/mock_component.hpp" +#include "score/mw/launch_manager/process_group_manager/details/mock_component_event_queue.hpp" +#include "score/mw/launch_manager/process_group_manager/details/process_monitor.hpp" +#include +#include + +using namespace testing; +using namespace score::mw::lifecycle::internal; + +class ProcessMonitorTest : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing"); + + ON_CALL(mock_component, getIndex).WillByDefault(Return(1)); + } + + MockComponentEventQueue mock_queue{}; + ProcessMonitor process_monitor{mock_queue}; + MockComponent mock_component{}; + score::cpp::stop_source stop_source{}; +}; + +TEST_F(ProcessMonitorTest, DoWorkNormalActivation) +{ + RecordProperty("Description", "Verify that the process monitor activates components during activate tasks"); + // Given a valid activate task + // Then + EXPECT_CALL(mock_component, activate).WillOnce(Return(IComponent::RequestState::kSuccess)); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationSuccessful::node_index, 1)))) + .Times(1); + // When + process_monitor.doWork(ComponentTask{ComponentTaskType::kActivate, mock_component, stop_source.get_token()}); +} + +TEST_F(ProcessMonitorTest, DoWorkNormalDeactivation) +{ + RecordProperty("Description", "Verify that the process monitor deactivates components during deactivate tasks"); + // Given a valid activate task + // Then + EXPECT_CALL(mock_component, deactivate).WillOnce(Return(IComponent::RequestState::kSuccess)); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&DeactivationComplete::node_index, 1)))) + .Times(1); + // When + process_monitor.doWork(ComponentTask{ComponentTaskType::kDeactivate, mock_component, stop_source.get_token()}); +} + +TEST_F(ProcessMonitorTest, DoWorkOnTerminationDepProcess) +{ + RecordProperty( + "Description", "Verify that a process activated by its own termination recieves the correct instructions"); + // Given a task for starting a component that only reaches active when it terminates + // Then + // The component is neither complete nor failed + EXPECT_CALL(mock_component, activate).WillOnce(Return(IComponent::RequestState::kWaiting)); + EXPECT_CALL(mock_component, tryHandleTermination).WillOnce(Return(IComponent::RequestState::kSuccess)); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationSuccessful::node_index, 1)))) + .Times(1); + // When + process_monitor.doWork(ComponentTask{ComponentTaskType::kActivate, mock_component, stop_source.get_token()}); + // The OS thread detects the termination: + process_monitor.terminated(mock_component, 0); +} + +TEST_F(ProcessMonitorTest, TerminatedUnexpectedly) +{ + RecordProperty( + "Description", + "Verify that the process monitor sends the correct event when a termination is not handled by the component"); + + // Given a terminated signal on a component that is not expected to terminate: + // Then + EXPECT_CALL(mock_component, tryHandleTermination) + .WillOnce(Return(score::cpp::make_unexpected(IComponent::ComponentError::kErrorAfterReady))); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&UnexpectedTermination::node_index, 1)))) + .Times(1); + + process_monitor.terminated(mock_component, 0); +} + +TEST_F(ProcessMonitorTest, ActivationFailed) +{ + RecordProperty( + "Description", + "Verify that when a component activation fails, the correct error and data is pushed to the event queue"); + + // Given a task that will fail to activate + auto errc = IComponent::ComponentError::kActivationTimedOut; + // Then + EXPECT_CALL(mock_component, activate).WillOnce(Return(score::cpp::make_unexpected(errc))); + EXPECT_CALL(mock_queue, push(VariantWith(Field(&ActivationFailed::reason, errc)))).Times(1); + // When + process_monitor.doWork(ComponentTask{ComponentTaskType::kActivate, mock_component, stop_source.get_token()}); +} diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp new file mode 100644 index 000000000..71db95d9f --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/run_target.hpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * 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 SCORE_LCM_RUN_TARGET_HPP_INCLUDED +#define SCORE_LCM_RUN_TARGET_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" + +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief A virtual node in the dependency graph corresponding to a configured run target. +/// A RunTarget does not have any resources and is immediately active once requested, provided +/// all of its dependencies are active, and immediately inactive once deactivated. +class RunTarget final : public IComponent +{ + public: + explicit RunTarget(uint32_t index) : index_(index) + { + } + + RunTarget(RunTarget&& other) noexcept : index_(other.index_), active_(other.active_.load()) + { + } + RunTarget(const RunTarget&) = delete; + RunTarget& operator=(const RunTarget&) = delete; + RunTarget& operator=(RunTarget&&) = delete; + ~RunTarget() override = default; + + RequestResult activate(score::cpp::stop_token) override + { + active_ = true; + return RequestState::kSuccess; + } + + RequestResult deactivate(score::cpp::stop_token) override + { + active_ = false; + return RequestState::kSuccess; + } + + RequestResult tryHandleTermination(int32_t) override + { + return RequestState::kSuccess; + } + + uint32_t getIndex() const override + { + return index_; + } + + bool active() const override + { + return active_; + } + + private: + uint32_t index_; + std::atomic active_{false}; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_RUN_TARGET_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/safeprocessmap_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/safeprocessmap_UT.cpp index a5469425c..63d2bd6dd 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/safeprocessmap_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/safeprocessmap_UT.cpp @@ -21,6 +21,7 @@ #include "score/mw/launch_manager/common/constants.hpp" #include "score/mw/launch_manager/process_group_manager/details/safe_process_map.hpp" +#include "score/mw/launch_manager/process_group_manager/details/mock_termination_callback.hpp" using namespace testing; using namespace score::lcm::internal; @@ -41,12 +42,6 @@ constexpr int kPidsPerThread = 256; constexpr uint32_t kCapacity = static_cast(ProcessLimits::kMaxProcesses); -class MockTerminationCallback : public ITerminationCallback -{ - public: - MOCK_METHOD(void, terminated, (int32_t process_status), (override)); -}; - class SafeProcessMapTest : public ::testing::Test { protected: