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 914c0c70e9..c65a68ed41 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 @@ -14,6 +14,37 @@ load("@rules_cc//cc:defs.bzl", "cc_library") load("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") +cc_library( + name = "component", + srcs = ["component.cpp"], + hdrs = ["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/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":icomponent", + "//score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action:iforce_stop_action", + "//score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition:iready_condition", + "//score/launch_manager/src/daemon/src/process_group_manager/details/start_action:istart_action", + "//score/launch_manager/src/daemon/src/process_group_manager/details/stop_action:istop_action", + "@score_baselibs//score/language/futurecpp", + ], +) + +lm_cc_test( + name = "component_UT", + srcs = ["component_UT.cpp"], + deps = [ + ":component", + ":mock_component", + "//score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action:mock_force_stop_action", + "//score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition:mock_ready_condition", + "//score/launch_manager/src/daemon/src/process_group_manager/details/start_action:mock_start_action", + "//score/launch_manager/src/daemon/src/process_group_manager/details/stop_action:mock_stop_action", + "@googletest//:gtest_main", + ], +) + cc_library( name = "component_task", hdrs = ["component_task.hpp"], @@ -49,6 +80,15 @@ cc_library( ], ) +cc_library( + name = "handle", + hdrs = ["handle.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:__subpackages__"], + deps = ["//score/launch_manager/src/daemon/src/osal:ipc_comms"], +) + cc_library( name = "mock_component", testonly = True, @@ -371,7 +411,7 @@ cc_library( hdrs = ["process_launcher.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__"], + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], deps = [ "//score/launch_manager/src/daemon/src/common:log", "//score/launch_manager/src/daemon/src/common:signal_safe_log", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp new file mode 100644 index 0000000000..89f4214847 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.cpp @@ -0,0 +1,171 @@ +/******************************************************************************** + * 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.hpp" + +namespace score::mw::lifecycle::internal +{ + +Component::Component( + const IStartAction& start_action, + const IStopAction& stop_action, + const IForceStopAction& force_stop_action, + const cpp::span> ready_conditions, + IdentifierHash identifier) + : start_action_(start_action), + stop_action_(stop_action), + force_stop_action_(force_stop_action), + ready_conditions_(ready_conditions), + identifier_(identifier), + state_(TerminatedState{}) +{ +} + +IComponent::RequestResult Component::activate(cpp::stop_token stop_token) +{ + return std::visit( + [&](auto state) { + return state.activate(*this, stop_token); + }, + state_); +} + +IComponent::RequestResult Component::TerminatedState::activate(Component& component, cpp::stop_token stop_token) +{ + const Result start_result = component.start_action_.start(stop_token); + if (!start_result.has_value()) + { + return cpp::make_unexpected(ComponentError::kErrorBeforeReady); + } + const Handle handle = start_result.value(); + + component.state_ = StartingState{handle}; + + for (const IReadyCondition& ready_condition : component.ready_conditions_) + { + if (!ready_condition.wait(stop_token, handle).has_value()) + { + return cpp::make_unexpected(ComponentError::kErrorBeforeReady); + } + } + + component.state_ = ReadyState{handle}; + + return RequestState::kSuccess; +} + +IComponent::RequestResult Component::StartingState::activate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in StartingState"); +} + +IComponent::RequestResult Component::ReadyState::activate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in ReadyState"); +} + +IComponent::RequestResult Component::TerminatingState::activate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in TerminatingState"); +} + +IComponent::RequestResult Component::FaultState::activate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot activate component in FaultState"); +} + +IComponent::RequestResult Component::deactivate(cpp::stop_token stop_token) +{ + return std::visit( + [&](auto state) { + return state.deactivate(*this, stop_token); + }, + state_); +} + +IComponent::RequestResult Component::TerminatedState::deactivate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot deactivate component in TerminatedState"); +} + +IComponent::RequestResult Component::StartingState::deactivate(Component& component, cpp::stop_token stop_token) +{ + if (!component.stop_action_.stop(stop_token, handle_).has_value()) + { + return cpp::make_unexpected(ComponentError::kErrorAfterReady); + } + + component.state_ = TerminatingState{handle_}; + return RequestState::kWaiting; +} + +IComponent::RequestResult Component::ReadyState::deactivate(Component& component, cpp::stop_token stop_token) +{ + if (!component.stop_action_.stop(stop_token, handle_).has_value()) + { + return cpp::make_unexpected(ComponentError::kErrorAfterReady); + } + + component.state_ = TerminatingState{handle_}; + return RequestState::kWaiting; +} + +IComponent::RequestResult Component::TerminatingState::deactivate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot deactivate component in TerminatingState"); +} + +IComponent::RequestResult Component::FaultState::deactivate( + [[maybe_unused]] Component& component, + [[maybe_unused]] cpp::stop_token stop_token) +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("Cannot deactivate component in FaultState"); +} + +IComponent::RequestResult Component::tryHandleTermination(int32_t status) +{ + if (status == 0) + { + state_ = TerminatedState{}; + } + else + { + state_ = FaultState{}; + } + + return RequestState::kSuccess; +} + +IdentifierHash Component::getIdentifier() const +{ + return identifier_; +} + +bool Component::active() const +{ + return std::holds_alternative(state_); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp new file mode 100644 index 0000000000..09816b853d --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component.hpp @@ -0,0 +1,134 @@ +/******************************************************************************** + * 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_HPP_INCLUDED +#define SCORE_LCM_COMPONENT_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/icomponent.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" +#include +#include +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief Manages and tracks the state of a resource. +class Component final : public IComponent +{ + public: + /// @brief Creates a new component from the given actions. + /// @param start_action How to start the resource. + /// @param stop_action How to gracefully stop the resource. + /// @param force_stop_action How to forcefully stop the resource. + /// @param ready_conditions How to decide when the resource has finished its startup. + /// @param identifier Name of the component. + Component( + const IStartAction& start_action, + const IStopAction& stop_action, + const IForceStopAction& force_stop_action, + cpp::span> ready_conditions = + cpp::span>{}, + IdentifierHash identifier = IdentifierHash{}); + + /// @brief Start the resource and set the component to active. + /// @param stop_token Token which can be used to interrupt the activation. + /// @return Whether the component activation was successful, is waiting for another + /// thread, or an error was encountered. + RequestResult activate(cpp::stop_token stop_token) override; + + /// @brief Stop the resource and set the component to inactive. + /// @param stop_token Token which can be used to interrupt the deactivation. + /// @return Whether the component deactivation was successful, is waiting for another + /// thread, or an error was encountered. + RequestResult deactivate(cpp::stop_token stop_token) override; + + /// @brief Notify the component that a POSIX process has terminated. + /// @param status Exit code of the process. + /// @return Whether the component activation was successful, is waiting for another + /// thread, or an error was encountered. + RequestResult tryHandleTermination(int32_t status) override; + + /// @brief Return the name of the component. + /// @return The name of the component. + [[nodiscard]] IdentifierHash getIdentifier() const override; + + /// @brief Return whether the component is active. + /// @return Whether the component is active. + [[nodiscard]] bool active() const override; + + private: + /// @brief How to start the resource. + const IStartAction& start_action_; + + /// @brief How to gracefully stop the resource. + const IStopAction& stop_action_; + + /// @brief How to forcefully stop the resource. + const IForceStopAction& force_stop_action_; + + /// @brief How to decide when the resource has finished its startup. + const cpp::span> ready_conditions_; + + /// @brief Name of the component. + const IdentifierHash identifier_; + + class TerminatedState final + { + public: + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + class StartingState final + { + public: + Handle handle_; + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + class ReadyState final + { + public: + Handle handle_; + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + class TerminatingState final + { + public: + Handle handle_; + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + class FaultState final + { + public: + Handle handle_; + RequestResult activate(Component& component, cpp::stop_token stop_token); + RequestResult deactivate(Component& component, cpp::stop_token stop_token); + }; + + /// @brief The current state of the component. + std::variant state_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_COMPONENT_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp new file mode 100644 index 0000000000..c1614715b1 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/component_UT.cpp @@ -0,0 +1,248 @@ +/******************************************************************************** + * 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.hpp" +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/mock_ready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/details/start_action/mock_start_action.hpp" +#include "score/mw/launch_manager/process_group_manager/details/stop_action/mock_stop_action.hpp" +#include +#include +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; + +const ProcessHandle mock_handle = ProcessHandle{42}; + +const cpp::stop_source mock_stop_source; +const cpp::stop_token mock_stop_token = mock_stop_source.get_token(); + +void expect_mock_handle(const Handle handle) +{ + EXPECT_EQ(std::get(handle).pid, mock_handle.pid); +} + +void expect_mock_stop_token(const cpp::stop_token token) +{ + EXPECT_EQ(token, mock_stop_token); +} + +TEST(ComponentTest, StartSucceeds) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + const auto result = component.activate(cpp::stop_token{}); + + EXPECT_TRUE(result.has_value()); +} + +TEST(ComponentTest, StartActionCalled) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + EXPECT_CALL(start_action, start(_)) + .WillOnce(DoAll(WithArg<0>(Invoke(expect_mock_stop_token)), Return(Result{mock_handle}))); + + static_cast(component.activate(mock_stop_token)); +} + +TEST(ComponentTest, StopActionCalled) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(stop_action, stop(_, _)) + .WillOnce(DoAll( + WithArg<0>(Invoke(expect_mock_stop_token)), + WithArg<1>(Invoke(expect_mock_handle)), + Return(Result{}))); + + static_cast(component.activate(cpp::stop_token{})); + component.deactivate(mock_stop_token); +} + +TEST(ComponentTest, ReadyConditionsCalled) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + MockReadyCondition ready_condition_1; + MockReadyCondition ready_condition_2; + std::array, 2> ready_conditions{ready_condition_1, ready_condition_2}; + Component component(start_action, stop_action, force_stop_action, ready_conditions); + + InSequence sequence; + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + EXPECT_CALL(ready_condition_1, wait(_, _)) + .WillOnce(DoAll( + WithArg<0>(Invoke(expect_mock_stop_token)), + WithArg<1>(Invoke(expect_mock_handle)), + Return(Result{}))); + EXPECT_CALL(ready_condition_2, wait(_, _)) + .WillOnce(DoAll( + WithArg<0>(Invoke(expect_mock_stop_token)), + WithArg<1>(Invoke(expect_mock_handle)), + Return(Result{}))); + + static_cast(component.activate(mock_stop_token)); +} + +TEST(ComponentTest, StartSetsActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + static_cast(component.activate(cpp::stop_token{})); + + EXPECT_TRUE(component.active()); +} + +TEST(ComponentTest, StopClearsActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + static_cast(component.activate(cpp::stop_token{})); + component.deactivate(cpp::stop_token{}); + + EXPECT_FALSE(component.active()); +} + +TEST(ComponentTest, ForceStopClearsActive) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + static_cast(component.activate(cpp::stop_token{})); + component.deactivate(cpp::stop_token{}); + + EXPECT_FALSE(component.active()); +} + +TEST(ComponentTest, CannotDeactivateInTerminatedState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + EXPECT_DEATH( + { static_cast(component.deactivate(cpp::stop_token{})); }, + "Cannot deactivate component in TerminatedState"); +} + +TEST(ComponentTest, CannotActivateInReadyState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + + EXPECT_DEATH( + { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in ReadyState"); +} + +TEST(ComponentTest, CannotActivateInTerminatingState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(Result{})); + + static_cast(component.activate(cpp::stop_token{})); + static_cast(component.deactivate(cpp::stop_token{})); + + EXPECT_DEATH( + { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in TerminatingState"); +} + +TEST(ComponentTest, CannotDeactivateInTerminatingState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + ON_CALL(stop_action, stop(_, _)).WillByDefault(Return(Result{})); + + static_cast(component.activate(cpp::stop_token{})); + static_cast(component.deactivate(cpp::stop_token{})); + + EXPECT_DEATH( + { static_cast(component.deactivate(cpp::stop_token{})); }, + "Cannot deactivate component in TerminatingState"); +} + +TEST(ComponentTest, CannotActivateInFaultState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + component.tryHandleTermination(1); + + EXPECT_DEATH( + { static_cast(component.activate(cpp::stop_token{})); }, "Cannot activate component in FaultState"); +} + +TEST(ComponentTest, CannotDeactivateInFaultState) +{ + MockStartAction start_action; + MockStopAction stop_action; + MockForceStopAction force_stop_action; + Component component(start_action, stop_action, force_stop_action); + + ON_CALL(start_action, start(_)).WillByDefault(Return(Result{mock_handle})); + + static_cast(component.activate(cpp::stop_token{})); + component.tryHandleTermination(1); + + EXPECT_DEATH( + { static_cast(component.deactivate(cpp::stop_token{})); }, "Cannot deactivate component in FaultState"); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD new file mode 100644 index 0000000000..d9a88d1541 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/BUILD @@ -0,0 +1,85 @@ +# ******************************************************************************* +# 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("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") + +cc_library( + name = "iforce_stop_action", + hdrs = ["iforce_stop_action.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:__subpackages__"], + deps = [ + "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "mock_force_stop_action", + testonly = True, + hdrs = ["mock_force_stop_action.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:__subpackages__"], + deps = [ + ":iforce_stop_action", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "empty_force_stop_action", + srcs = ["empty_force_stop_action.cpp"], + hdrs = ["empty_force_stop_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/force_stop_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [":iforce_stop_action"], +) + +lm_cc_test( + name = "empty_force_stop_action_UT", + srcs = ["empty_force_stop_action_UT.cpp"], + deps = [ + ":empty_force_stop_action", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "process_force_stop_action", + srcs = ["process_force_stop_action.cpp"], + hdrs = ["process_force_stop_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/force_stop_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":iforce_stop_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/osal:return_types", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + ], +) + +lm_cc_test( + name = "process_force_stop_action_UT", + srcs = ["process_force_stop_action_UT.cpp"], + deps = [ + ":process_force_stop_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/process_group_manager:mock_iprocess", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp new file mode 100644 index 0000000000..b997cbebbf --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.cpp @@ -0,0 +1,24 @@ +/******************************************************************************** + * 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/force_stop_action/empty_force_stop_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +Result EmptyForceStopAction::forceStop(cpp::stop_token stop_token, const Handle) const +{ + return {}; +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp new file mode 100644 index 0000000000..e62c414c4a --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp @@ -0,0 +1,35 @@ +/******************************************************************************** + * 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_EMPTY_FORCE_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_EMPTY_FORCE_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +/// @brief A force stop action which does nothing. +class EmptyForceStopAction final : public IForceStopAction +{ + public: + /// @brief Does nothing and returns success. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Always successful. + Result forceStop(cpp::stop_token stop_token, const Handle) const override; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_EMPTY_FORCE_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp new file mode 100644 index 0000000000..5e57524a6b --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/empty_force_stop_action_UT.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/empty_force_stop_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +TEST(EmptyForceStopActionTest, ForceStopSucceeds) +{ + EmptyForceStopAction empty_force_stop_action; + + const auto result = empty_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{}); + + EXPECT_TRUE(result.has_value()); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp new file mode 100644 index 0000000000..8e03af0fcc --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/iforce_stop_action.hpp @@ -0,0 +1,39 @@ +/******************************************************************************** + * 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_IFORCE_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_IFORCE_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/result/result.h" +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief An action which describes how to forcefully stop a resource. +class IForceStopAction +{ + public: + virtual ~IForceStopAction() = default; + + /// @brief Forcefully stop the resource represented by the given handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Whether the action was successful, or failed with an error. + virtual Result forceStop(cpp::stop_token stop_token, const Handle handle) const = 0; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_IFORCE_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp new file mode 100644 index 0000000000..dbe4890f8f --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/mock_force_stop_action.hpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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_FORCE_STOP_ACTION_HPP_INCLUDED +#define MOCK_FORCE_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockForceStopAction : public IForceStopAction +{ + public: + MOCK_METHOD(Result, forceStop, (cpp::stop_token, const Handle handle), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_FORCE_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp new file mode 100644 index 0000000000..3d751fc070 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.cpp @@ -0,0 +1,47 @@ +/******************************************************************************** + * 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/force_stop_action/process_force_stop_action.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +ProcessForceStopAction::ProcessForceStopAction(osal::IProcess& launcher) : launcher_(launcher) +{ +} + +Result ProcessForceStopAction::forceStop(cpp::stop_token stop_token, const Handle handle) const +{ + return std::visit(*this, handle); +} + +Result ProcessForceStopAction::operator()(const ProcessHandle process) const +{ + const osal::OsalReturnType result = launcher_.forceTermination(process.pid); + + if (result == osal::OsalReturnType::kSuccess) + { + return {}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +Result ProcessForceStopAction::operator()(const Handle handle) const +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessForceStopAction should only be called with a ProcessHandle"); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp new file mode 100644 index 0000000000..a9d1402a88 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action.hpp @@ -0,0 +1,57 @@ +/******************************************************************************** + * 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_PROCESS_FORCE_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_PROCESS_FORCE_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/force_stop_action/iforce_stop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/iprocess.hpp" +#include "score/result/result.h" + +namespace score::mw::lifecycle::internal +{ + +/// @brief A force stop action which forcefully stops a POSIX process. +/// @details The process is stopped by sending SIGKILL, which cannot be caught +/// by the process and results in immediate termination. +class ProcessForceStopAction final : public IForceStopAction +{ + public: + /// @brief Creates a new process force stop action. + /// @param launcher The process launcher used to stop the process. + explicit ProcessForceStopAction(osal::IProcess& launcher); + + /// @brief Forcefully stop the process represented by the given handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Whether the action was successful, or failed with an error. + Result forceStop(cpp::stop_token stop_token, const Handle handle) const override; + + /// @brief Forcefully stop the process represented by the given handle. + /// @param process The process to act upon. + /// @return Whether the action was successful, or failed with an error. + Result operator()(const ProcessHandle process) const; + + /// @brief Forcefully stop the resource represented by the given handle. + /// @param handle The resource to act upon. + /// @return Always returns a not-implemented error. + Result operator()(const Handle handle) const; + + private: + /// @brief The process launcher used to stop the process. + osal::IProcess& launcher_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_PROCESS_FORCE_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp new file mode 100644 index 0000000000..b11f3202bb --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/force_stop_action/process_force_stop_action_UT.cpp @@ -0,0 +1,74 @@ +/******************************************************************************** + * 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/force_stop_action/process_force_stop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/mock_iprocess.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; + +const ProcessHandle mock_handle = ProcessHandle{42}; + +TEST(ProcessForceStopActionTest, ForceStopCallsForceTerminationWithPid) +{ + StrictMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); + + EXPECT_CALL(launcher, forceTermination(mock_handle.pid)).WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle)); +} + +TEST(ProcessForceStopActionTest, ForceStopSucceedsReturnsSuccess) +{ + NiceMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); + + ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle); + + EXPECT_TRUE(result.has_value()); +} + +TEST(ProcessForceStopActionTest, ForceStopFailsReturnsError) +{ + NiceMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); + + ON_CALL(launcher, forceTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = process_force_stop_action.forceStop(cpp::stop_token{}, mock_handle); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(ProcessForceStopActionTest, ForceStopWithEmptyHandleAborts) +{ + NiceMock launcher; + const ProcessForceStopAction process_force_stop_action(launcher); + + EXPECT_DEATH( + static_cast(process_force_stop_action.forceStop(cpp::stop_token{}, EmptyHandle{})), "Unreachable_Code"); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp new file mode 100644 index 0000000000..b89f64ff0d --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/handle.hpp @@ -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 + ********************************************************************************/ + +#ifndef SCORE_LCM_HANDLE_HPP_INCLUDED +#define SCORE_LCM_HANDLE_HPP_INCLUDED + +#include +#include + +#include "score/mw/launch_manager/osal/ipc_comms.hpp" + +namespace score::mw::lifecycle::internal +{ + +/// @brief Signifies that we are not managing any resource. +/// @details This is useful for run targets and synchronisation points, +/// which exist only to depend on other components. +struct EmptyHandle +{ +}; + +/// @brief Represents a POSIX process that we are managing. +struct ProcessHandle +{ + pid_t pid; + osal::IpcCommsP sync; +}; + +/// @brief Represents some resource that we are managing. +using Handle = std::variant; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_HANDLE_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD new file mode 100644 index 0000000000..58398d843b --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/BUILD @@ -0,0 +1,94 @@ +# ******************************************************************************* +# 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("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") + +cc_library( + name = "iready_condition", + hdrs = ["iready_condition.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:__subpackages__"], + deps = [ + "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "mock_ready_condition", + testonly = True, + hdrs = ["mock_ready_condition.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:__subpackages__"], + deps = [ + ":iready_condition", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "file_ready_condition", + srcs = ["file_ready_condition.cpp"], + hdrs = ["file_ready_condition.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/ready_condition", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":iready_condition", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/configuration:component_config", + "//score/launch_manager/src/daemon/src/osal:ifile_waiter", + "//score/launch_manager/src/daemon/src/osal:return_types", + "@score_baselibs//score/language/safecpp/string_view:zstring_view", + ], +) + +lm_cc_test( + name = "file_ready_condition_UT", + srcs = ["file_ready_condition_UT.cpp"], + deps = [ + ":file_ready_condition", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/osal:mock_ifile_waiter", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "report_running_ready_condition", + srcs = ["report_running_ready_condition.cpp"], + hdrs = ["report_running_ready_condition.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/ready_condition", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":iready_condition", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/osal:return_types", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + ], +) + +lm_cc_test( + name = "report_running_ready_condition_UT", + srcs = ["report_running_ready_condition_UT.cpp"], + deps = [ + ":report_running_ready_condition", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/process_group_manager:mock_iprocess", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp new file mode 100644 index 0000000000..99c9944689 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.cpp @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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/ready_condition/file_ready_condition.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +FileReadyCondition::FileReadyCondition( + const osal::IFileWaiter& file_waiter, + score::safecpp::zstring_view file_path, + configuration::FileExistenceState state, + std::chrono::milliseconds timeout, + std::chrono::milliseconds interval) + : file_waiter_(file_waiter), file_path_(file_path), state_(state), timeout_(timeout), interval_(interval) +{ +} + +Result FileReadyCondition::wait(cpp::stop_token stop_token, [[maybe_unused]] const Handle) const +{ + const osal::OsalReturnType result = file_waiter_.waitForFile(file_path_, state_, timeout_, interval_, stop_token); + + if (result == osal::OsalReturnType::kSuccess) + { + return {}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp new file mode 100644 index 0000000000..c53abafbec --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition.hpp @@ -0,0 +1,71 @@ +/******************************************************************************** + * 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_FILE_READY_CONDITION_HPP_INCLUDED +#define SCORE_LCM_FILE_READY_CONDITION_HPP_INCLUDED + +#include "score/language/safecpp/string_view/zstring_view.h" +#include "score/mw/launch_manager/osal/ifile_waiter.hpp" +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" +#include "score/result/result.h" +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief A ready condition which waits for a file to exist/not exist. +/// @details This may be useful for components which mount filesystems, +/// or set up devices under `/dev`. +class FileReadyCondition final : public IReadyCondition +{ + public: + /// @brief Creates a new file ready condition. + /// @param file_waiter The waiter used to check the state of the file. + /// @param file_path The path to the file to wait for. + /// @param state The desired state of the file. + /// @param timeout The maximum duration to wait for the file condition. + /// @param interval How often to poll for the file. + explicit FileReadyCondition( + const osal::IFileWaiter& file_waiter, + score::safecpp::zstring_view file_path, + configuration::FileExistenceState state, + std::chrono::milliseconds timeout, + std::chrono::milliseconds interval); + + /// @brief Wait until the resource represented by the given handle is ready. + /// @param stop_token Token which can be used to interrupt the wait. + /// @param handle The resource to wait on. + /// @return Whether the wait was successful, or failed with an error. + Result wait(cpp::stop_token stop_token, const Handle handle) const override; + + private: + /// @brief The waiter used to check file state. + const osal::IFileWaiter& file_waiter_; + + /// @brief The path to the file to wait for. + const score::safecpp::zstring_view file_path_; + + /// @brief The desired state of the file. + const configuration::FileExistenceState state_; + + /// @brief The maximum duration to wait for the file to exist/not exist. + const std::chrono::milliseconds timeout_; + + /// @brief How often to poll for the file. + const std::chrono::milliseconds interval_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_FILE_READY_CONDITION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition_UT.cpp new file mode 100644 index 0000000000..a944efac58 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/file_ready_condition_UT.cpp @@ -0,0 +1,100 @@ +/******************************************************************************** + * 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/osal/mock_ifile_waiter.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/file_ready_condition.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; +using namespace std::chrono_literals; + +constexpr score::safecpp::zstring_view kFilePath = "/some/path"; + +TEST(FileReadyConditionTest, WaitCallsWaitForFile) +{ + StrictMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + EXPECT_CALL( + file_waiter, waitForFile(Eq(kFilePath), Eq(configuration::FileExistenceState::Exists), Eq(10ms), Eq(5ms), _)) + .WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{})); +} + +TEST(FileReadyConditionTest, WaitSucceedsReturnsSuccess) +{ + NiceMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + ON_CALL(file_waiter, waitForFile(_, _, _, _, _)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result = file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); + + EXPECT_TRUE(result.has_value()); +} + +TEST(FileReadyConditionTest, WaitFailsReturnsError) +{ + NiceMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + ON_CALL(file_waiter, waitForFile(_, _, _, _, _)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(FileReadyConditionTest, WaitTimesOutReturnsError) +{ + NiceMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + ON_CALL(file_waiter, waitForFile(_, _, _, _, _)).WillByDefault(Return(osal::OsalReturnType::kTimeout)); + + const Result result = file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(FileReadyConditionTest, WaitAcceptsVariousHandles) +{ + NiceMock file_waiter; + const FileReadyCondition file_ready_condition( + file_waiter, kFilePath, configuration::FileExistenceState::Exists, 10ms, 5ms); + + ON_CALL(file_waiter, waitForFile(_, _, _, _, _)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result_empty = file_ready_condition.wait(cpp::stop_token{}, EmptyHandle{}); + EXPECT_TRUE(result_empty.has_value()); + + const Result result_process = file_ready_condition.wait(cpp::stop_token{}, ProcessHandle{}); + EXPECT_TRUE(result_process.has_value()); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp new file mode 100644 index 0000000000..1f912b48a0 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/iready_condition.hpp @@ -0,0 +1,39 @@ +/******************************************************************************** + * 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_IREADY_CONDITION_HPP_INCLUDED +#define SCORE_LCM_IREADY_CONDITION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/result/result.h" +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief A condition which decides when a resource has finished its startup. +class IReadyCondition +{ + public: + virtual ~IReadyCondition() = default; + + /// @brief Wait until the resource represented by the given handle is ready. + /// @param stop_token Token which can be used to interrupt the wait. + /// @param handle The resource to wait on. + /// @return Whether the wait was successful, or failed with an error. + virtual Result wait(cpp::stop_token stop_token, const Handle handle) const = 0; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_IREADY_CONDITION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp new file mode 100644 index 0000000000..93003c7072 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/mock_ready_condition.hpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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_READY_CONDITION_HPP_INCLUDED +#define MOCK_READY_CONDITION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockReadyCondition : public IReadyCondition +{ + public: + MOCK_METHOD(Result, wait, (cpp::stop_token, const Handle handle), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_READY_CONDITION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp new file mode 100644 index 0000000000..eb394d4802 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/report_running_ready_condition.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +ReportRunningReadyCondition::ReportRunningReadyCondition( + osal::IProcess& launcher, + std::optional timeout) + : launcher_(launcher), timeout_(timeout) +{ +} + +Result ReportRunningReadyCondition::wait(cpp::stop_token stop_token, const Handle handle) const +{ + return std::visit(*this, handle); +} + +Result ReportRunningReadyCondition::operator()(const ProcessHandle process) const +{ + const osal::OsalReturnType result = launcher_.waitForkRunning(process.sync, timeout_); + + if (result == osal::OsalReturnType::kSuccess) + { + return {}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +Result ReportRunningReadyCondition::operator()(const Handle handle) const +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE( + "ReportRunningReadyCondition should only be called with a ProcessHandle"); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp new file mode 100644 index 0000000000..4c87857bb0 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition.hpp @@ -0,0 +1,65 @@ +/******************************************************************************** + * 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_REPORT_RUNNING_HPP_INCLUDED +#define SCORE_LCM_REPORT_RUNNING_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/iready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/iprocess.hpp" +#include "score/result/result.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief A ready condition which waits for a process to report running +/// through a shared memory channel. +class ReportRunningReadyCondition final : public IReadyCondition +{ + public: + /// @brief Creates a new report running ready condition. + /// @param launcher The process launcher used to wait for the process. + /// @param timeout The maximum duration to wait for the process to report running, + /// or no timeout if not specified. + explicit ReportRunningReadyCondition(osal::IProcess& launcher, std::optional timeout); + + /// @brief Wait until the resource represented by the given handle is ready. + /// @param stop_token Token which can be used to interrupt the wait. + /// @param handle The resource to wait on. + /// @return Whether the wait was successful, or failed with an error. + Result wait(cpp::stop_token stop_token, const Handle handle) const override; + + /// @brief Wait until the process represented by the given handle reports running. + /// @param process The process to wait on. + /// @return Whether the wait was successful, or failed with an error. + Result operator()(const ProcessHandle process) const; + + /// @brief Wait until the resource represented by the given handle is ready. + /// @param handle The resource to wait on. + /// @return Always returns a not-implemented error. + Result operator()(const Handle handle) const; + + private: + /// @brief The process launcher used to wait for the process. + osal::IProcess& launcher_; + + /// @brief The maximum duration to wait for the process to report running, + /// or no timeout if not specified. + const std::optional timeout_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_REPORT_RUNNING_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp new file mode 100644 index 0000000000..eb0516d844 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/ready_condition/report_running_ready_condition_UT.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/launch_manager/process_group_manager/details/ready_condition/report_running_ready_condition.hpp" +#include "score/mw/launch_manager/process_group_manager/mock_iprocess.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; +using namespace std::chrono_literals; + +const ProcessHandle mock_handle = ProcessHandle{42}; + +TEST(ReportRunningReadyConditionTest, WaitCallsWaitForkRunningWithSyncAndTimeout) +{ + StrictMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, 10ms); + + EXPECT_CALL(launcher, waitForkRunning(mock_handle.sync, std::optional(10ms))) + .WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(report_running_ready_condition.wait(cpp::stop_token{}, mock_handle)); +} + +TEST(ReportRunningReadyConditionTest, WaitSucceedsReturnsSuccess) +{ + NiceMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); + + ON_CALL(launcher, waitForkRunning(_, _)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result = report_running_ready_condition.wait(cpp::stop_token{}, mock_handle); + + EXPECT_TRUE(result.has_value()); +} + +TEST(ReportRunningReadyConditionTest, WaitFailsReturnsError) +{ + NiceMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); + + ON_CALL(launcher, waitForkRunning(_, _)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = report_running_ready_condition.wait(cpp::stop_token{}, mock_handle); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(ReportRunningReadyConditionTest, WaitWithEmptyHandleAborts) +{ + NiceMock launcher; + const ReportRunningReadyCondition report_running_ready_condition(launcher, std::nullopt); + + EXPECT_DEATH( + static_cast(report_running_ready_condition.wait(cpp::stop_token{}, EmptyHandle{})), "Unreachable_Code"); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD new file mode 100644 index 0000000000..b8cb1aad9e --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/BUILD @@ -0,0 +1,86 @@ +# ******************************************************************************* +# 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("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") + +cc_library( + name = "istart_action", + hdrs = ["istart_action.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:__subpackages__"], + deps = [ + "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "mock_start_action", + testonly = True, + hdrs = ["mock_start_action.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:__subpackages__"], + deps = [ + ":istart_action", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "empty_start_action", + srcs = ["empty_start_action.cpp"], + hdrs = ["empty_start_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/start_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/start_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [":istart_action"], +) + +lm_cc_test( + name = "empty_start_action_UT", + srcs = ["empty_start_action_UT.cpp"], + deps = [ + ":empty_start_action", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "process_start_action", + srcs = ["process_start_action.cpp"], + hdrs = ["process_start_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/start_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/start_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":istart_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/configuration:component_config", + "//score/launch_manager/src/daemon/src/osal:return_types", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + ], +) + +lm_cc_test( + name = "process_start_action_UT", + srcs = ["process_start_action_UT.cpp"], + deps = [ + ":process_start_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/process_group_manager:mock_iprocess", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp new file mode 100644 index 0000000000..89df46bc84 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.cpp @@ -0,0 +1,24 @@ +/******************************************************************************** + * 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/start_action/empty_start_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +Result EmptyStartAction::start(cpp::stop_token stop_token) const +{ + return EmptyHandle{}; +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.hpp new file mode 100644 index 0000000000..f9b9641d9d --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action.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 SCORE_LCM_EMPTY_START_ACTION_HPP_INCLUDED +#define SCORE_LCM_EMPTY_START_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +/// @brief A start action which does nothing. +class EmptyStartAction final : public IStartAction +{ + public: + /// @brief Does nothing and returns an empty handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @return An empty handle. + Result start(cpp::stop_token stop_token) const override; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_EMPTY_START_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp new file mode 100644 index 0000000000..321dd13bc6 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/empty_start_action_UT.cpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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/start_action/empty_start_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +TEST(EmptyStartActionTest, StartSucceeds) +{ + EmptyStartAction empty_start_action; + + const auto result = empty_start_action.start(cpp::stop_token{}); + + EXPECT_TRUE(result.has_value()); +} + +TEST(EmptyStartActionTest, StartReturnsEmptyHandle) +{ + EmptyStartAction empty_start_action; + + const auto result = empty_start_action.start(cpp::stop_token{}); + + EXPECT_TRUE(std::holds_alternative(result.value())); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp new file mode 100644 index 0000000000..3b50e65e8e --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/istart_action.hpp @@ -0,0 +1,38 @@ +/******************************************************************************** + * 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_ISTART_ACTION_HPP_INCLUDED +#define SCORE_LCM_ISTART_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/result/result.h" +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief An action which describes how to start a resource. +class IStartAction +{ + public: + virtual ~IStartAction() = default; + + /// @brief Start the resource. + /// @param stop_token Token which can be used to interrupt the action. + /// @return The handle of the started resource, or an error if the action failed. + virtual Result start(cpp::stop_token stop_token) const = 0; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_ISTART_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp new file mode 100644 index 0000000000..7f8bbb4d18 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/mock_start_action.hpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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_START_ACTION_HPP_INCLUDED +#define MOCK_START_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockStartAction : public IStartAction +{ + public: + MOCK_METHOD(Result, start, (cpp::stop_token), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_START_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp new file mode 100644 index 0000000000..d306aaa5d8 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.cpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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/start_action/process_start_action.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +ProcessStartAction::ProcessStartAction(osal::IProcess& launcher, const configuration::ComponentConfig& config) + : launcher_(launcher), config_(config) +{ +} + +Result ProcessStartAction::start(cpp::stop_token stop_token) const +{ + osal::ProcessID pid = 0; + osal::IpcCommsP sync = nullptr; + + const auto result = launcher_.startProcess(pid, sync, config_); + + if (result == osal::OsalReturnType::kSuccess) + { + return ProcessHandle{pid, sync}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.hpp new file mode 100644 index 0000000000..8adbdc27bc --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action.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 SCORE_LCM_PROCESS_START_ACTION_HPP_INCLUDED +#define SCORE_LCM_PROCESS_START_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/configuration/component_config.hpp" +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/mw/launch_manager/process_group_manager/details/start_action/istart_action.hpp" +#include "score/mw/launch_manager/process_group_manager/iprocess.hpp" +#include "score/result/result.h" + +namespace score::mw::lifecycle::internal +{ + +/// @brief A start action which starts a POSIX process. +class ProcessStartAction final : public IStartAction +{ + public: + /// @brief Creates a new process start action. + /// @param launcher The process launcher used to start the process. + /// @param config Configuration describing the executable to launch + /// and its environment. + ProcessStartAction(osal::IProcess& launcher, const configuration::ComponentConfig& config); + + /// @brief Start a process. + /// @param stop_token Token which can be used to interrupt the action. + /// @return The handle of the started process, or an error if the action failed. + Result start(cpp::stop_token stop_token) const override; + + private: + /// @brief The process launcher used to start the process. + osal::IProcess& launcher_; + + /// @brief Configuration describing the executable to launch and its environment. + const configuration::ComponentConfig& config_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_PROCESS_START_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp new file mode 100644 index 0000000000..1de9c8f3aa --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/start_action/process_start_action_UT.cpp @@ -0,0 +1,69 @@ +/******************************************************************************** + * 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/start_action/process_start_action.hpp" +#include "score/mw/launch_manager/process_group_manager/mock_iprocess.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; + +TEST(ProcessStartActionTest, StartCallsStartProcessWithConfig) +{ + StrictMock launcher; + const configuration::ComponentConfig config{}; + const ProcessStartAction process_start_action(launcher, config); + + EXPECT_CALL(launcher, startProcess(_, _, Ref(config))).WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(process_start_action.start(cpp::stop_token{})); +} + +TEST(ProcessStartActionTest, StartSucceedsReturnsProcessHandle) +{ + NiceMock launcher; + const configuration::ComponentConfig config{}; + const ProcessStartAction process_start_action(launcher, config); + const osal::ProcessID expected_pid = 42; + + ON_CALL(launcher, startProcess(_, _, _)) + .WillByDefault(DoAll(SetArgReferee<0>(expected_pid), Return(osal::OsalReturnType::kSuccess))); + + const Result result = process_start_action.start(cpp::stop_token{}); + + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(std::get(result.value()).pid, expected_pid); +} + +TEST(ProcessStartActionTest, StartFailsReturnsError) +{ + NiceMock launcher; + const configuration::ComponentConfig config{}; + const ProcessStartAction process_start_action(launcher, config); + + ON_CALL(launcher, startProcess(_, _, _)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = process_start_action.start(cpp::stop_token{}); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD new file mode 100644 index 0000000000..051454f096 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/BUILD @@ -0,0 +1,85 @@ +# ******************************************************************************* +# 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("//tests/utils/bazel:unit_test.bzl", "lm_cc_test") + +cc_library( + name = "istop_action", + hdrs = ["istop_action.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:__subpackages__"], + deps = [ + "//score/launch_manager/src/daemon/src/process_group_manager/details:handle", + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "mock_stop_action", + testonly = True, + hdrs = ["mock_stop_action.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:__subpackages__"], + deps = [ + ":istop_action", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "empty_stop_action", + srcs = ["empty_stop_action.cpp"], + hdrs = ["empty_stop_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/stop_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [":istop_action"], +) + +lm_cc_test( + name = "empty_stop_action_UT", + srcs = ["empty_stop_action_UT.cpp"], + deps = [ + ":empty_stop_action", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "process_stop_action", + srcs = ["process_stop_action.cpp"], + hdrs = ["process_stop_action.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details/stop_action", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__subpackages__"], + deps = [ + ":istop_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/osal:return_types", + "//score/launch_manager/src/daemon/src/process_group_manager:iprocess", + ], +) + +lm_cc_test( + name = "process_stop_action_UT", + srcs = ["process_stop_action_UT.cpp"], + deps = [ + ":process_stop_action", + "//score/launch_manager:error", + "//score/launch_manager/src/daemon/src/process_group_manager:mock_iprocess", + "@googletest//:gtest_main", + ], +) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp new file mode 100644 index 0000000000..76efcf8858 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.cpp @@ -0,0 +1,24 @@ +/******************************************************************************** + * 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/stop_action/empty_stop_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +Result EmptyStopAction::stop(cpp::stop_token stop_token, const Handle) const +{ + return {}; +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp new file mode 100644 index 0000000000..e957788b5a --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action.hpp @@ -0,0 +1,35 @@ +/******************************************************************************** + * 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_EMPTY_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_EMPTY_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" + +namespace score::mw::lifecycle::internal +{ + +/// @brief A stop action which does nothing. +class EmptyStopAction final : public IStopAction +{ + public: + /// @brief Does nothing and returns success. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Always successful. + Result stop(cpp::stop_token stop_token, const Handle) const override; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_EMPTY_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp new file mode 100644 index 0000000000..ceddb9b131 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/empty_stop_action_UT.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/launch_manager/process_group_manager/details/stop_action/empty_stop_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +TEST(EmptyStopActionTest, StopSucceeds) +{ + EmptyStopAction empty_stop_action; + + const auto result = empty_stop_action.stop(cpp::stop_token{}, EmptyHandle{}); + + EXPECT_TRUE(result.has_value()); +} + +} // namespace +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp new file mode 100644 index 0000000000..27426b3780 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/istop_action.hpp @@ -0,0 +1,39 @@ +/******************************************************************************** + * 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_ISTOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_ISTOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/handle.hpp" +#include "score/result/result.h" +#include + +namespace score::mw::lifecycle::internal +{ + +/// @brief An action which describes how to gracefully stop a resource. +class IStopAction +{ + public: + virtual ~IStopAction() = default; + + /// @brief Gracefully stop the resource represented by the given handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Whether the action was successful, or failed with an error. + virtual Result stop(cpp::stop_token stop_token, const Handle handle) const = 0; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_ISTOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp new file mode 100644 index 0000000000..ca4b3c2bd9 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/mock_stop_action.hpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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_STOP_ACTION_HPP_INCLUDED +#define MOCK_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" +#include + +namespace score::mw::lifecycle::internal +{ + +class MockStopAction : public IStopAction +{ + public: + MOCK_METHOD(Result, stop, (cpp::stop_token, const Handle handle), (override, const)); +}; + +} // namespace score::mw::lifecycle::internal + +#endif // MOCK_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp new file mode 100644 index 0000000000..74a18b55b0 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.cpp @@ -0,0 +1,47 @@ +/******************************************************************************** + * 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/stop_action/process_stop_action.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" +#include "score/mw/lifecycle/execution_error.h" + +namespace score::mw::lifecycle::internal +{ + +ProcessStopAction::ProcessStopAction(osal::IProcess& launcher) : launcher_(launcher) +{ +} + +Result ProcessStopAction::stop(cpp::stop_token stop_token, const Handle handle) const +{ + return std::visit(*this, handle); +} + +Result ProcessStopAction::operator()(const ProcessHandle process) const +{ + const osal::OsalReturnType result = launcher_.requestTermination(process.pid); + + if (result == osal::OsalReturnType::kSuccess) + { + return {}; + } + + return MakeUnexpected(ExecErrc::kGeneralError); +} + +Result ProcessStopAction::operator()(const Handle handle) const +{ + SCORE_LANGUAGE_FUTURECPP_UNREACHABLE_MESSAGE("ProcessStopAction should only be called with a ProcessHandle"); +} + +} // namespace score::mw::lifecycle::internal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp new file mode 100644 index 0000000000..03bb4526c2 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action.hpp @@ -0,0 +1,57 @@ +/******************************************************************************** + * 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_PROCESS_STOP_ACTION_HPP_INCLUDED +#define SCORE_LCM_PROCESS_STOP_ACTION_HPP_INCLUDED + +#include "score/mw/launch_manager/process_group_manager/details/stop_action/istop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/iprocess.hpp" +#include "score/result/result.h" + +namespace score::mw::lifecycle::internal +{ + +/// @brief A stop action which gracefully stops a POSIX process. +/// @details The process is stopped by sending SIGTERM, which the process may handle +/// to perform cleanup before exiting. +class ProcessStopAction final : public IStopAction +{ + public: + /// @brief Creates a new process stop action. + /// @param launcher The process launcher used to stop the process. + explicit ProcessStopAction(osal::IProcess& launcher); + + /// @brief Gracefully stop the process represented by the given handle. + /// @param stop_token Token which can be used to interrupt the action. + /// @param handle The resource to act upon. + /// @return Whether the action was successful, or failed with an error. + Result stop(cpp::stop_token stop_token, const Handle handle) const override; + + /// @brief Gracefully stop the process represented by the given handle. + /// @param process The process to act upon. + /// @return Whether the action was successful, or failed with an error. + Result operator()(const ProcessHandle process) const; + + /// @brief Gracefully stop the resource represented by the given handle. + /// @param handle The resource to act upon. + /// @return Always returns a not-implemented error. + Result operator()(const Handle handle) const; + + private: + /// @brief The process launcher used to stop the process. + osal::IProcess& launcher_; +}; + +} // namespace score::mw::lifecycle::internal + +#endif // SCORE_LCM_PROCESS_STOP_ACTION_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp new file mode 100644 index 0000000000..ce0a778adc --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/stop_action/process_stop_action_UT.cpp @@ -0,0 +1,73 @@ +/******************************************************************************** + * 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/stop_action/process_stop_action.hpp" +#include "score/mw/launch_manager/process_group_manager/mock_iprocess.hpp" +#include "score/mw/lifecycle/execution_error.h" +#include +#include + +namespace score::mw::lifecycle::internal +{ +namespace +{ + +using namespace ::testing; + +const ProcessHandle mock_handle = ProcessHandle{42}; + +TEST(ProcessStopActionTest, StopCallsRequestTerminationWithPid) +{ + StrictMock launcher; + const ProcessStopAction process_stop_action(launcher); + + EXPECT_CALL(launcher, requestTermination(mock_handle.pid)).WillOnce(Return(osal::OsalReturnType::kSuccess)); + + static_cast(process_stop_action.stop(cpp::stop_token{}, mock_handle)); +} + +TEST(ProcessStopActionTest, StopSucceedsReturnsSuccess) +{ + NiceMock launcher; + const ProcessStopAction process_stop_action(launcher); + + ON_CALL(launcher, requestTermination(_)).WillByDefault(Return(osal::OsalReturnType::kSuccess)); + + const Result result = process_stop_action.stop(cpp::stop_token{}, mock_handle); + + EXPECT_TRUE(result.has_value()); +} + +TEST(ProcessStopActionTest, StopFailsReturnsError) +{ + NiceMock launcher; + const ProcessStopAction process_stop_action(launcher); + + ON_CALL(launcher, requestTermination(_)).WillByDefault(Return(osal::OsalReturnType::kFail)); + + const Result result = process_stop_action.stop(cpp::stop_token{}, mock_handle); + + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ExecErrc::kGeneralError); +} + +TEST(ProcessStopActionTest, StopWithEmptyHandleAborts) +{ + NiceMock launcher; + const ProcessStopAction process_stop_action(launcher); + + EXPECT_DEATH(static_cast(process_stop_action.stop(cpp::stop_token{}, EmptyHandle{})), "Unreachable_Code"); +} + +} // namespace +} // namespace score::mw::lifecycle::internal