Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions score/launch_manager/src/daemon/src/process_group_manager/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ cc_library(
visibility = ["//score:__subpackages__"],
)

cc_library(
name = "ialive_monitor_thread_mock",
testonly = True,
hdrs = ["alive_monitor_thread_mock.hpp"],
include_prefix = "score/mw/launch_manager/process_group_manager",
strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager",
visibility = ["//score:__subpackages__"],
deps = [
":ialive_monitor_thread",
"@googletest//:gtest",
],
)

cc_library(
name = "alive_monitor_thread",
srcs = ["alive_monitor_thread.cpp"],
Expand Down
Original file line number Diff line number Diff line change
@@ -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
********************************************************************************/

#ifndef SCORE_LCM_IALIVE_MONITOR_THREAD_MOCK_HPP_INCLUDED
#define SCORE_LCM_IALIVE_MONITOR_THREAD_MOCK_HPP_INCLUDED

#include "score/mw/launch_manager/process_group_manager/ialive_monitor_thread.hpp"

#include <gmock/gmock.h>

namespace score
{
namespace lcm
{
namespace internal
{

/// @brief Reusable gmock mock for IAliveMonitorThread, for use by tests of components that own an alive monitor
/// thread.
class MockAliveMonitorThread : public IAliveMonitorThread
{
public:
MOCK_METHOD(bool, start, (), (override));
MOCK_METHOD(void, stop, (), (override));
};

} // namespace internal
} // namespace lcm
} // namespace score

#endif // SCORE_LCM_IALIVE_MONITOR_THREAD_MOCK_HPP_INCLUDED
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
load("@rules_cc//cc:defs.bzl", "cc_library")
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")

cc_library(
name = "process_info_node",
Expand Down Expand Up @@ -44,7 +44,10 @@ cc_library(
hdrs = ["graph.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:__pkg__",
"//score/launch_manager/src/daemon/src/process_group_manager/details:__pkg__",
],
deps = [
":process_info_node",
":safe_process_map",
Expand Down Expand Up @@ -103,6 +106,26 @@ cc_test(
],
)

cc_test(
name = "process_group_manager_UT",
timeout = "long",
srcs = ["process_group_manager_UT.cpp"],
defines = select({
"//config:lm_use_new_configuration": ["USE_NEW_CONFIGURATION"],
"//conditions:default": [],
}),
deps = [
":process_group_manager_impl",
"//score/launch_manager/src/daemon/src/configuration:config",
"//score/launch_manager/src/daemon/src/process_group_manager:ialive_monitor_thread_mock",
"//score/launch_manager/src/daemon/src/process_group_manager:process_group_manager_hdrs",
"//score/launch_manager/src/daemon/src/process_state_client:iprocess_state_notifier_mock",
"//score/launch_manager/src/daemon/src/recovery_client:recovery_client_mock",
"//score/launch_manager/src/daemon/src/watchdog:i_watchdog_if_mock",
"@googletest//:gtest_main",
],
)

cc_library(
name = "process_launcher",
srcs = ["process_launcher.cpp"],
Expand Down
Original file line number Diff line number Diff line change
@@ -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/process_group_manager.hpp"

#include "score/mw/launch_manager/process_group_manager/alive_monitor_thread_mock.hpp"
#include "score/mw/launch_manager/process_state_client/iprocess_state_notifier_mock.hpp"
#include "score/mw/launch_manager/recovery_client/irecovery_client_mock.h"
#include "score/mw/launch_manager/watchdog/IWatchdogIfMock.hpp"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <sched.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

#ifdef USE_NEW_CONFIGURATION
#include "score/mw/launch_manager/configuration/config.hpp"
#endif

using namespace testing;

namespace score::lcm::internal
{
namespace
{

using score::lcm::MockProcessStateNotifier;
using score::lcm::MockRecoveryClient;
using score::lcm::watchdog::MockWatchdogIf;

#ifdef USE_NEW_CONFIGURATION
using score::mw::launch_manager::configuration::AliveSupervisionConfig;
using score::mw::launch_manager::configuration::ApplicationType;
using score::mw::launch_manager::configuration::ComponentConfig;
using score::mw::launch_manager::configuration::Config;
using score::mw::launch_manager::configuration::ConfigBuilder;
using score::mw::launch_manager::configuration::FallbackRunTargetConfig;
using score::mw::launch_manager::configuration::ProcessState;
using score::mw::launch_manager::configuration::ReadyCondition;
using score::mw::launch_manager::configuration::RunTargetConfig;
using score::mw::launch_manager::configuration::WatchdogConfig;

Config makeMinimalConfig()
{
ComponentConfig component;
component.name = "comp_a";
component.description = "Component A";
component.component_properties.binary_name = "true";
component.component_properties.application_profile.application_type = ApplicationType::Native;
component.component_properties.application_profile.is_self_terminating = true;
component.component_properties.ready_condition = ReadyCondition{ProcessState::Running};
component.deployment_config.ready_timeout_ms = 500U;
component.deployment_config.shutdown_timeout_ms = 500U;
component.deployment_config.bin_dir = "/bin";
component.deployment_config.working_dir = "/workspaces/lifecycle";
component.deployment_config.sandbox.uid = 1000U;
component.deployment_config.sandbox.gid = 1000U;
component.deployment_config.sandbox.scheduling_policy = SCHED_OTHER;
component.deployment_config.sandbox.scheduling_priority = 0;

RunTargetConfig startup;
startup.name = "Startup";
startup.description = "Initial run target";
startup.depends_on = {"comp_a"};
startup.transition_timeout_ms = 5000U;
startup.recovery_action.run_target = "fallback_run_target";

FallbackRunTargetConfig fallback;
fallback.description = "Safe state";
fallback.transition_timeout_ms = 1500U;

AliveSupervisionConfig alive;
alive.evaluation_cycle_ms = 500U;

WatchdogConfig watchdog;
watchdog.device_file_path = "/dev/watchdog0";
watchdog.max_timeout_ms = 5000U;
watchdog.deactivate_on_shutdown = true;
watchdog.require_magic_close = false;

std::vector<ComponentConfig> components;
components.push_back(std::move(component));

std::vector<RunTargetConfig> run_targets;
run_targets.push_back(std::move(startup));

return ConfigBuilder{}
.setComponents(std::move(components))
.setRunTargets(std::move(run_targets))
.setInitialRunTarget("Startup")
.setFallbackRunTarget(std::move(fallback))
.setAliveSupervision(alive)
.setWatchdog(watchdog)
.build();
}

class ProcessGroupManagerWatchdogTest : public Test
{
protected:
void SetUp() override
{
RecordProperty("TestType", "unit-test");
RecordProperty("DerivationTechnique", "explorative-testing");

auto alive_monitor_thread = std::make_unique<NiceMock<MockAliveMonitorThread>>();
alive_monitor_thread_ = alive_monitor_thread.get();
ON_CALL(*alive_monitor_thread_, start()).WillByDefault(Return(true));

auto recovery_client = std::make_shared<NiceMock<MockRecoveryClient>>();
recovery_client_ = recovery_client.get();
ON_CALL(*recovery_client_, getNextRequest()).WillByDefault(Return(std::nullopt));
ON_CALL(*recovery_client_, hasOverflow()).WillByDefault(Return(false));
ON_CALL(*recovery_client_, sendRecoveryRequest(_)).WillByDefault(Return(true));

auto process_state_notifier = std::make_unique<NiceMock<MockProcessStateNotifier>>();
process_state_notifier_ = process_state_notifier.get();
ON_CALL(*process_state_notifier_, constructReceiver())
.WillByDefault(Return(ByMove(std::unique_ptr<score::lcm::IProcessStateReceiver>{})));
ON_CALL(*process_state_notifier_, queuePosixProcess(_)).WillByDefault(Return(true));

auto watchdog = std::make_unique<StrictMock<MockWatchdogIf>>();
watchdog_ = watchdog.get();

process_group_manager_ = std::make_unique<ProcessGroupManager>(std::move(alive_monitor_thread),
std::move(recovery_client),
std::move(process_state_notifier),
std::move(watchdog));
}

void TearDown() override
{
process_group_manager_->deinitialize();
}

MockAliveMonitorThread* alive_monitor_thread_{};
MockRecoveryClient* recovery_client_{};
MockProcessStateNotifier* process_state_notifier_{};
MockWatchdogIf* watchdog_{};
std::unique_ptr<ProcessGroupManager> process_group_manager_;
};

TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogMethodsCalledInSequence_WhenInitializeCalled)
{
// Given
const auto config = makeMinimalConfig();

// Expected
InSequence sequence;
EXPECT_CALL(*alive_monitor_thread_, start()).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, init(_, _)).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, enable()).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, disable()).Times(1);
EXPECT_CALL(*alive_monitor_thread_, stop()).Times(1);

// When
auto initialize_result = process_group_manager_->initialize(config);

// Then
EXPECT_TRUE(initialize_result);
}

TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogServicePerCycle_WhenRunCalled)
{
// Given
const auto config = makeMinimalConfig();

// Expected
EXPECT_CALL(*alive_monitor_thread_, start()).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, init(_, _)).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, enable()).WillOnce(Return(true));
// Call cancel() to exit the run() loop after at least one cycle of serviceWatchdog() is called
EXPECT_CALL(*watchdog_, serviceWatchdog()).Times(AtLeast(1)).WillRepeatedly([this]() {
process_group_manager_->cancel();
});
// Called in deinitialize() after run() returns
EXPECT_CALL(*watchdog_, disable()).Times(1);
EXPECT_CALL(*alive_monitor_thread_, stop()).Times(1);

// When
ASSERT_TRUE(process_group_manager_->initialize(config));
auto run_result = process_group_manager_->run();

// Then
EXPECT_TRUE(run_result);
}

TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogFired_WhenRecoveryClientOverflowsDuringRunCall)
{
// Given
const auto config = makeMinimalConfig();

// Expected
EXPECT_CALL(*alive_monitor_thread_, start()).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, init(_, _)).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, enable()).WillOnce(Return(true));
ON_CALL(*recovery_client_, hasOverflow()).WillByDefault(Return(true));
EXPECT_CALL(*watchdog_, fireWatchdogReaction()).Times(AtLeast(1));
EXPECT_CALL(*watchdog_, serviceWatchdog()).Times(AtLeast(1)).WillRepeatedly([this]() {
process_group_manager_->cancel();
});
EXPECT_CALL(*watchdog_, disable()).Times(1);
EXPECT_CALL(*alive_monitor_thread_, stop()).Times(1);

// When
ASSERT_TRUE(process_group_manager_->initialize(config));
auto run_result = process_group_manager_->run();

// Then
EXPECT_TRUE(run_result);
}

TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogDisabled_WhenDeinitializeCalled)
{
// Given
const auto config = makeMinimalConfig();

// Expected
EXPECT_CALL(*alive_monitor_thread_, start()).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, init(_, _)).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, enable()).WillOnce(Return(true));
EXPECT_CALL(*watchdog_, disable()).Times(1);
EXPECT_CALL(*alive_monitor_thread_, stop()).Times(1);

// When
auto initialize_result = process_group_manager_->initialize(config);

// Then
EXPECT_TRUE(initialize_result);
}
#endif

} // namespace
} // namespace score::lcm::internal
13 changes: 13 additions & 0 deletions score/launch_manager/src/daemon/src/process_state_client/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ cc_library(
],
)

cc_library(
name = "iprocess_state_notifier_mock",
testonly = True,
hdrs = ["iprocess_state_notifier_mock.hpp"],
include_prefix = "score/mw/launch_manager/process_state_client",
strip_include_prefix = "/score/launch_manager/src/daemon/src/process_state_client",
visibility = ["//score:__subpackages__"],
deps = [
":iprocess_state_notifier",
"@googletest//:gtest",
],
)

cc_library(
name = "process_state_notifier",
srcs = ["process_state_notifier.cpp"],
Expand Down
Loading