From 94a38b6be3d43ea64ea093917d7f38bd4fbf7312 Mon Sep 17 00:00:00 2001 From: Cameron Craig Date: Thu, 23 Jul 2026 14:46:31 +0000 Subject: [PATCH 1/6] feat: Add mocks and stubs for process group manager tests --- .../daemon/src/process_group_manager/BUILD | 13 +++++ .../alive_monitor_thread_mock.hpp | 41 ++++++++++++++++ .../src/daemon/src/recovery_client/BUILD | 15 +++++- .../recovery_client/irecovery_client_mock.h | 41 ++++++++++++++++ .../iprocess_state_notifier_mock.hpp | 40 +++++++++++++++ .../src/daemon/src/watchdog/BUILD | 16 +++--- .../daemon/src/watchdog/IWatchdogIfMock.hpp | 49 +++++++++++++++++++ 7 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/alive_monitor_thread_mock.hpp create mode 100644 score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h create mode 100644 score/launch_manager/src/daemon/src/supervision_control_client/iprocess_state_notifier_mock.hpp create mode 100644 score/launch_manager/src/daemon/src/watchdog/IWatchdogIfMock.hpp diff --git a/score/launch_manager/src/daemon/src/process_group_manager/BUILD b/score/launch_manager/src/daemon/src/process_group_manager/BUILD index 4f1ba54df..407278ed0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/BUILD +++ b/score/launch_manager/src/daemon/src/process_group_manager/BUILD @@ -45,6 +45,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"], diff --git a/score/launch_manager/src/daemon/src/process_group_manager/alive_monitor_thread_mock.hpp b/score/launch_manager/src/daemon/src/process_group_manager/alive_monitor_thread_mock.hpp new file mode 100644 index 000000000..d8f28ee61 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/alive_monitor_thread_mock.hpp @@ -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 + +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 diff --git a/score/launch_manager/src/daemon/src/recovery_client/BUILD b/score/launch_manager/src/daemon/src/recovery_client/BUILD index ed4574813..61934e466 100644 --- a/score/launch_manager/src/daemon/src/recovery_client/BUILD +++ b/score/launch_manager/src/daemon/src/recovery_client/BUILD @@ -32,7 +32,20 @@ cc_library( ], ) -lm_cc_test( +cc_library( + name = "recovery_client_mock", + testonly = True, + hdrs = ["irecovery_client_mock.h"], + include_prefix = "score/mw/launch_manager/recovery_client", + strip_include_prefix = "/score/launch_manager/src/daemon/src/recovery_client", + visibility = ["//score:__subpackages__"], + deps = [ + ":recovery_client", + "@googletest//:gtest", + ], +) + +cc_test( name = "recovery_client_UT", srcs = ["recovery_client_UT.cpp"], deps = [ diff --git a/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h b/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h new file mode 100644 index 000000000..bfefdf451 --- /dev/null +++ b/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h @@ -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_IRECOVERYCLIENT_MOCK_H_ +#define SCORE_LCM_IRECOVERYCLIENT_MOCK_H_ + +#include "score/mw/launch_manager/recovery_client/irecovery_client.h" + +#include + +namespace score +{ +namespace lcm +{ + +/// @brief Reusable gmock mock for IRecoveryClient, for use by tests of components that either request recovery +/// (e.g. Alive Monitor) or process recovery requests (e.g. ProcessGroupManager). +class MockRecoveryClient : public IRecoveryClient +{ + public: + MOCK_METHOD(bool, + sendRecoveryRequest, + (const score::lcm::IdentifierHash& process_group_identifier), + (noexcept, override)); + MOCK_METHOD(std::optional, getNextRequest, (), (noexcept, override)); + MOCK_METHOD(bool, hasOverflow, (), (const, noexcept, override)); +}; + +} // namespace lcm +} // namespace score + +#endif // SCORE_LCM_IRECOVERYCLIENT_MOCK_H_ diff --git a/score/launch_manager/src/daemon/src/supervision_control_client/iprocess_state_notifier_mock.hpp b/score/launch_manager/src/daemon/src/supervision_control_client/iprocess_state_notifier_mock.hpp new file mode 100644 index 000000000..52d8d7745 --- /dev/null +++ b/score/launch_manager/src/daemon/src/supervision_control_client/iprocess_state_notifier_mock.hpp @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef IPROCESSSTATE_NOTIFIER_MOCK_HPP_INCLUDED +#define IPROCESSSTATE_NOTIFIER_MOCK_HPP_INCLUDED + +#include "score/mw/launch_manager/process_state_client/iprocess_state_notifier.hpp" + +#include + +#include + +namespace score +{ +namespace lcm +{ + +/// @brief Reusable gmock mock for IProcessStateNotifier, for use by tests of components that notify PHM of process +/// state changes. +class MockProcessStateNotifier : public IProcessStateNotifier +{ + public: + MOCK_METHOD(std::unique_ptr, constructReceiver, (), (override)); + MOCK_METHOD(bool, queuePosixProcess, (const score::lcm::PosixProcess& f_posixProcess), (noexcept, override)); +}; + +} // namespace lcm +} // namespace score + +#endif // IPROCESSSTATE_NOTIFIER_MOCK_HPP_INCLUDED diff --git a/score/launch_manager/src/daemon/src/watchdog/BUILD b/score/launch_manager/src/daemon/src/watchdog/BUILD index 9628e9541..6eae12a79 100644 --- a/score/launch_manager/src/daemon/src/watchdog/BUILD +++ b/score/launch_manager/src/daemon/src/watchdog/BUILD @@ -37,12 +37,16 @@ cc_library( ], ) -lm_cc_test( - name = "watchdog_factory_UT", - srcs = ["WatchdogFactory_UT.cpp"], +cc_library( + name = "i_watchdog_if_mock", + testonly = True, + hdrs = ["IWatchdogIfMock.hpp"], + include_prefix = "score/mw/launch_manager/watchdog", + strip_include_prefix = "/score/launch_manager/src/daemon/src/watchdog", + visibility = ["//score:__subpackages__"], deps = [ - ":watchdog_factory", - "//score/launch_manager/src/daemon/src/watchdog/details:watchdog_impl", - "@googletest//:gtest_main", + ":i_watchdog_if", + "@googletest//:gtest", ], ) + diff --git a/score/launch_manager/src/daemon/src/watchdog/IWatchdogIfMock.hpp b/score/launch_manager/src/daemon/src/watchdog/IWatchdogIfMock.hpp new file mode 100644 index 000000000..1a4c246a2 --- /dev/null +++ b/score/launch_manager/src/daemon/src/watchdog/IWatchdogIfMock.hpp @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef IWATCHDOGIFMOCK_HPP_INCLUDED +#define IWATCHDOGIFMOCK_HPP_INCLUDED + +#include "score/mw/launch_manager/watchdog/IWatchdogIf.hpp" + +#include + +namespace score +{ +namespace lcm +{ +namespace watchdog +{ + +/// @brief Reusable gmock mock for IWatchdogIf, for use by tests of components that service the watchdog. +class MockWatchdogIf : public IWatchdogIf +{ + public: + MockWatchdogIf() = default; + + MOCK_METHOD(bool, + init, + (const score::mw::launch_manager::configuration::WatchdogConfig& watchdog_config, + std::int64_t cycle_time_ns), + (noexcept, override)); + MOCK_METHOD(bool, enable, (), (noexcept, override)); + MOCK_METHOD(void, disable, (), (noexcept, override)); + MOCK_METHOD(void, serviceWatchdog, (), (noexcept, override)); + MOCK_METHOD(void, fireWatchdogReaction, (), (noexcept, override)); +}; + +} // namespace watchdog +} // namespace lcm +} // namespace score + +#endif // IWATCHDOGIFMOCK_HPP_INCLUDED From 15fc0579fb0302ba5be52fe0da2883165dcc5c94 Mon Sep 17 00:00:00 2001 From: Cameron Craig Date: Thu, 23 Jul 2026 14:51:38 +0000 Subject: [PATCH 2/6] test: Add unit tests for ProcessGroupManager watchdog --- .../src/process_group_manager/details/BUILD | 21 +- .../details/process_group_manager_UT.cpp | 256 ++++++++++++++++++ .../recovery_client/irecovery_client_mock.h | 3 +- 3 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp 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 65bfaf79c..6d668e59a 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 @@ -184,7 +184,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 = [ ":component_event_queue", ":component_of", @@ -323,6 +326,22 @@ lm_cc_test( ], ) +cc_test( + name = "process_group_manager_UT", + timeout = "long", + srcs = ["process_group_manager_UT.cpp"], + 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"], diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp new file mode 100644 index 000000000..fc242a4b1 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp @@ -0,0 +1,256 @@ +/******************************************************************************** + * 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 +#include + +#include +#include +#include +#include +#include +#include + +#include "score/mw/launch_manager/configuration/config.hpp" + +using namespace testing; + +namespace score::lcm::internal +{ +namespace +{ + +using score::lcm::MockProcessStateNotifier; +using score::lcm::MockRecoveryClient; +using score::lcm::watchdog::MockWatchdogIf; + +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 components; + components.push_back(std::move(component)); + + std::vector 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>(); + alive_monitor_thread_ = alive_monitor_thread.get(); + ON_CALL(*alive_monitor_thread_, start()).WillByDefault(Return(true)); + + auto recovery_client = std::make_shared>(); + recovery_client_ = recovery_client.get(); + // Capture the callback the ProcessGroupManager registers so tests can inject recovery + // requests as if they came from the Alive Monitor. + ON_CALL(*recovery_client_, setRecoveryRequestCallback(_)).WillByDefault(SaveArg<0>(&recovery_callback_)); + ON_CALL(*recovery_client_, sendRecoveryRequest(_)).WillByDefault(Return(true)); + + auto process_state_notifier = std::make_unique>(); + process_state_notifier_ = process_state_notifier.get(); + ON_CALL(*process_state_notifier_, constructReceiver()) + .WillByDefault(Return(ByMove(std::unique_ptr{}))); + ON_CALL(*process_state_notifier_, queuePosixProcess(_)).WillByDefault(Return(true)); + + auto watchdog = std::make_unique>(); + watchdog_ = watchdog.get(); + + process_group_manager_ = std::make_unique(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_{}; + score::lcm::IRecoveryClient::RecoveryRequestCallback recovery_callback_{}; + MockProcessStateNotifier* process_state_notifier_{}; + MockWatchdogIf* watchdog_{}; + std::unique_ptr 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)); + 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)); + + // Deliver more recovery requests through the registered callback than the component event + // queue can hold (capacity == number of OS processes * 3; makeMinimalConfig has a single + // component), forcing the queue to drop events and latch its (sticky) overflow flag. run() + // observes the overflow and fires the watchdog reaction. + ASSERT_TRUE(recovery_callback_); + for (int i = 0; i < 16; ++i) + { + recovery_callback_(score::lcm::IdentifierHash{"overflow_probe"}); + } + + 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); +} + +} // namespace +} // namespace score::lcm::internal diff --git a/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h b/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h index bfefdf451..ead8d88b6 100644 --- a/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h +++ b/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h @@ -27,12 +27,11 @@ namespace lcm class MockRecoveryClient : public IRecoveryClient { public: + MOCK_METHOD(void, setRecoveryRequestCallback, (RecoveryRequestCallback callback), (noexcept, override)); MOCK_METHOD(bool, sendRecoveryRequest, (const score::lcm::IdentifierHash& process_group_identifier), (noexcept, override)); - MOCK_METHOD(std::optional, getNextRequest, (), (noexcept, override)); - MOCK_METHOD(bool, hasOverflow, (), (const, noexcept, override)); }; } // namespace lcm From 34925d05aa2da0924bf688054049307b8d3ede23 Mon Sep 17 00:00:00 2001 From: Cameron Craig Date: Mon, 10 Aug 2026 13:42:59 +0100 Subject: [PATCH 3/6] chore: Fix minor review issues fix: Re-add missing bazel rule for watchdog factory fix: use lm_cc_test rule chore: Fix minor review issues - Unused include - Unused bazel dep - add const variable for magic number - Reword a comment fix: Formatting --- .../src/process_group_manager/details/BUILD | 6 ++-- .../details/process_group_manager_UT.cpp | 28 +++++++++---------- .../src/daemon/src/recovery_client/BUILD | 2 +- .../recovery_client/irecovery_client_mock.h | 9 +++--- .../src/daemon/src/watchdog/BUILD | 11 +++++++- .../daemon/src/watchdog/IWatchdogIfMock.hpp | 10 +++---- 6 files changed, 37 insertions(+), 29 deletions(-) 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 6d668e59a..630188174 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 @@ -186,7 +186,6 @@ cc_library( strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", visibility = [ "//score/launch_manager/src/daemon/src/process_group_manager:__pkg__", - "//score/launch_manager/src/daemon/src/process_group_manager/details:__pkg__", ], deps = [ ":component_event_queue", @@ -326,15 +325,14 @@ lm_cc_test( ], ) -cc_test( +lm_cc_test( name = "process_group_manager_UT", timeout = "long", srcs = ["process_group_manager_UT.cpp"], deps = [ - ":process_group_manager_impl", "//score/launch_manager/src/daemon/src/configuration:config", + "//score/launch_manager/src/daemon/src/process_group_manager", "//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", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp index fc242a4b1..dd24416e2 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include @@ -134,10 +133,11 @@ class ProcessGroupManagerWatchdogTest : public Test auto watchdog = std::make_unique>(); watchdog_ = watchdog.get(); - process_group_manager_ = std::make_unique(std::move(alive_monitor_thread), - std::move(recovery_client), - std::move(process_state_notifier), - std::move(watchdog)); + process_group_manager_ = std::make_unique( + std::move(alive_monitor_thread), + std::move(recovery_client), + std::move(process_state_notifier), + std::move(watchdog)); } void TearDown() override @@ -202,6 +202,9 @@ TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogFired_W { // Given const auto config = makeMinimalConfig(); + // More than the component event queue can hold (capacity == number of OS processes * 3; makeMinimalConfig has a + // single component) + constexpr int kNumRecoveryRequests = 16; // Expected EXPECT_CALL(*alive_monitor_thread_, start()).WillOnce(Return(true)); @@ -217,12 +220,11 @@ TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogFired_W // When ASSERT_TRUE(process_group_manager_->initialize(config)); - // Deliver more recovery requests through the registered callback than the component event - // queue can hold (capacity == number of OS processes * 3; makeMinimalConfig has a single - // component), forcing the queue to drop events and latch its (sticky) overflow flag. run() - // observes the overflow and fires the watchdog reaction. + // Deliver more recovery requests than the component event queue can hold, + // forcing the queue to drop events and latch its sticky overflow flag. + // run() observes the overflow and fires the watchdog reaction. ASSERT_TRUE(recovery_callback_); - for (int i = 0; i < 16; ++i) + for (int i = 0; i < kNumRecoveryRequests; ++i) { recovery_callback_(score::lcm::IdentifierHash{"overflow_probe"}); } @@ -246,10 +248,8 @@ TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogDisable EXPECT_CALL(*alive_monitor_thread_, stop()).Times(1); // When - auto initialize_result = process_group_manager_->initialize(config); - - // Then - EXPECT_TRUE(initialize_result); + ASSERT_TRUE(process_group_manager_->initialize(config)); + process_group_manager_->deinitialize(); } } // namespace diff --git a/score/launch_manager/src/daemon/src/recovery_client/BUILD b/score/launch_manager/src/daemon/src/recovery_client/BUILD index 61934e466..2066a6103 100644 --- a/score/launch_manager/src/daemon/src/recovery_client/BUILD +++ b/score/launch_manager/src/daemon/src/recovery_client/BUILD @@ -45,7 +45,7 @@ cc_library( ], ) -cc_test( +lm_cc_test( name = "recovery_client_UT", srcs = ["recovery_client_UT.cpp"], deps = [ diff --git a/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h b/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h index ead8d88b6..7c0a7f4b3 100644 --- a/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h +++ b/score/launch_manager/src/daemon/src/recovery_client/irecovery_client_mock.h @@ -28,10 +28,11 @@ class MockRecoveryClient : public IRecoveryClient { public: MOCK_METHOD(void, setRecoveryRequestCallback, (RecoveryRequestCallback callback), (noexcept, override)); - MOCK_METHOD(bool, - sendRecoveryRequest, - (const score::lcm::IdentifierHash& process_group_identifier), - (noexcept, override)); + MOCK_METHOD( + bool, + sendRecoveryRequest, + (const score::lcm::IdentifierHash& process_group_identifier), + (noexcept, override)); }; } // namespace lcm diff --git a/score/launch_manager/src/daemon/src/watchdog/BUILD b/score/launch_manager/src/daemon/src/watchdog/BUILD index 6eae12a79..5fdf87cf9 100644 --- a/score/launch_manager/src/daemon/src/watchdog/BUILD +++ b/score/launch_manager/src/daemon/src/watchdog/BUILD @@ -37,6 +37,16 @@ cc_library( ], ) +lm_cc_test( + name = "watchdog_factory_UT", + srcs = ["WatchdogFactory_UT.cpp"], + deps = [ + ":watchdog_factory", + "//score/launch_manager/src/daemon/src/watchdog/details:watchdog_impl", + "@googletest//:gtest_main", + ], +) + cc_library( name = "i_watchdog_if_mock", testonly = True, @@ -49,4 +59,3 @@ cc_library( "@googletest//:gtest", ], ) - diff --git a/score/launch_manager/src/daemon/src/watchdog/IWatchdogIfMock.hpp b/score/launch_manager/src/daemon/src/watchdog/IWatchdogIfMock.hpp index 1a4c246a2..4509c823e 100644 --- a/score/launch_manager/src/daemon/src/watchdog/IWatchdogIfMock.hpp +++ b/score/launch_manager/src/daemon/src/watchdog/IWatchdogIfMock.hpp @@ -31,11 +31,11 @@ class MockWatchdogIf : public IWatchdogIf public: MockWatchdogIf() = default; - MOCK_METHOD(bool, - init, - (const score::mw::launch_manager::configuration::WatchdogConfig& watchdog_config, - std::int64_t cycle_time_ns), - (noexcept, override)); + MOCK_METHOD( + bool, + init, + (const score::mw::launch_manager::configuration::WatchdogConfig& watchdog_config, std::int64_t cycle_time_ns), + (noexcept, override)); MOCK_METHOD(bool, enable, (), (noexcept, override)); MOCK_METHOD(void, disable, (), (noexcept, override)); MOCK_METHOD(void, serviceWatchdog, (), (noexcept, override)); From ff874254752dc5ad4f1dc5e25e5b85857c6fae33 Mon Sep 17 00:00:00 2001 From: Cameron Craig Date: Mon, 10 Aug 2026 16:01:44 +0100 Subject: [PATCH 4/6] fix: GivenMinimalConfig_ExpectWatchdogDisabled_WhenDeinitializeCalled --- .../details/process_group_manager_UT.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp index dd24416e2..0257cc9a4 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp @@ -244,8 +244,10 @@ TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogDisable 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); + // We are explicitly calling deinitialize() in this test for readability, + // so disable() and stop() are expected to be called twice: once in deinitialize() and once in TearDown(). + EXPECT_CALL(*watchdog_, disable()).Times(2); + EXPECT_CALL(*alive_monitor_thread_, stop()).Times(2); // When ASSERT_TRUE(process_group_manager_->initialize(config)); From c83b4e4435742c98282137aa536155233ae221cb Mon Sep 17 00:00:00 2001 From: Cameron Craig Date: Tue, 11 Aug 2026 11:26:15 +0100 Subject: [PATCH 5/6] refactor: use supervision control notifier --- .../src/process_group_manager/details/BUILD | 3 +- .../details/process_group_manager_UT.cpp | 19 +++++------ .../src/supervision_control_client/BUILD | 13 ++++++++ .../mock_supervision_control_notifier.hpp | 32 +++++++++++++++++++ 4 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/supervision_control_client/mock_supervision_control_notifier.hpp 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 630188174..550f2cd8a 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 @@ -333,8 +333,9 @@ lm_cc_test( "//score/launch_manager/src/daemon/src/configuration:config", "//score/launch_manager/src/daemon/src/process_group_manager", "//score/launch_manager/src/daemon/src/process_group_manager:ialive_monitor_thread_mock", - "//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/supervision_control_client:isupervision_control_notifier", + "//score/launch_manager/src/daemon/src/supervision_control_client:mock_supervision_control_notifier", "//score/launch_manager/src/daemon/src/watchdog:i_watchdog_if_mock", "@googletest//:gtest_main", ], diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp index 0257cc9a4..da0f021f9 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp @@ -14,8 +14,8 @@ #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/supervision_control_client/mock_supervision_control_notifier.hpp" #include "score/mw/launch_manager/watchdog/IWatchdogIfMock.hpp" #include @@ -36,8 +36,8 @@ namespace score::lcm::internal namespace { -using score::lcm::MockProcessStateNotifier; using score::lcm::MockRecoveryClient; +using score::lcm::MockSupervisionControlNotifier; using score::lcm::watchdog::MockWatchdogIf; using score::mw::launch_manager::configuration::AliveSupervisionConfig; @@ -124,11 +124,12 @@ class ProcessGroupManagerWatchdogTest : public Test ON_CALL(*recovery_client_, setRecoveryRequestCallback(_)).WillByDefault(SaveArg<0>(&recovery_callback_)); ON_CALL(*recovery_client_, sendRecoveryRequest(_)).WillByDefault(Return(true)); - auto process_state_notifier = std::make_unique>(); - process_state_notifier_ = process_state_notifier.get(); - ON_CALL(*process_state_notifier_, constructReceiver()) - .WillByDefault(Return(ByMove(std::unique_ptr{}))); - ON_CALL(*process_state_notifier_, queuePosixProcess(_)).WillByDefault(Return(true)); + auto supervision_control_notifier = std::make_unique>(); + supervision_control_notifier_ = supervision_control_notifier.get(); + ON_CALL(*supervision_control_notifier_, constructReceiver()) + .WillByDefault(Return(ByMove(std::unique_ptr{}))); + ON_CALL(*supervision_control_notifier_, reportActivation(_, _)).WillByDefault(Return(true)); + ON_CALL(*supervision_control_notifier_, reportDeactivation(_, _)).WillByDefault(Return(true)); auto watchdog = std::make_unique>(); watchdog_ = watchdog.get(); @@ -136,7 +137,7 @@ class ProcessGroupManagerWatchdogTest : public Test process_group_manager_ = std::make_unique( std::move(alive_monitor_thread), std::move(recovery_client), - std::move(process_state_notifier), + std::move(supervision_control_notifier), std::move(watchdog)); } @@ -148,7 +149,7 @@ class ProcessGroupManagerWatchdogTest : public Test MockAliveMonitorThread* alive_monitor_thread_{}; MockRecoveryClient* recovery_client_{}; score::lcm::IRecoveryClient::RecoveryRequestCallback recovery_callback_{}; - MockProcessStateNotifier* process_state_notifier_{}; + MockSupervisionControlNotifier* supervision_control_notifier_{}; MockWatchdogIf* watchdog_{}; std::unique_ptr process_group_manager_; }; diff --git a/score/launch_manager/src/daemon/src/supervision_control_client/BUILD b/score/launch_manager/src/daemon/src/supervision_control_client/BUILD index 26c77445a..1a5e5a213 100644 --- a/score/launch_manager/src/daemon/src/supervision_control_client/BUILD +++ b/score/launch_manager/src/daemon/src/supervision_control_client/BUILD @@ -74,6 +74,19 @@ cc_library( ], ) +cc_library( + name = "mock_supervision_control_notifier", + testonly = True, + hdrs = ["mock_supervision_control_notifier.hpp"], + include_prefix = "score/mw/launch_manager/supervision_control_client", + strip_include_prefix = "/score/launch_manager/src/daemon/src/supervision_control_client", + visibility = ["//score:__subpackages__"], + deps = [ + ":isupervision_control_notifier", + "@googletest//:gtest_main", + ], +) + cc_library( name = "supervision_control_notifier", srcs = ["supervision_control_notifier.cpp"], diff --git a/score/launch_manager/src/daemon/src/supervision_control_client/mock_supervision_control_notifier.hpp b/score/launch_manager/src/daemon/src/supervision_control_client/mock_supervision_control_notifier.hpp new file mode 100644 index 000000000..56a43b8e1 --- /dev/null +++ b/score/launch_manager/src/daemon/src/supervision_control_client/mock_supervision_control_notifier.hpp @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef MOCK_SUPERVISION_CONTROL_NOTIFIER_HPP_INCLUDED +#define MOCK_SUPERVISION_CONTROL_NOTIFIER_HPP_INCLUDED + +#include "score/mw/launch_manager/supervision_control_client/isupervision_control_notifier.hpp" +#include + +namespace score::lcm +{ + +class MockSupervisionControlNotifier : public ISupervisionControlNotifier +{ + public: + MOCK_METHOD(bool, reportActivation, (IdentifierHash id, timespec time), (override, noexcept)); + MOCK_METHOD(bool, reportDeactivation, (IdentifierHash id, timespec time), (override, noexcept)); + MOCK_METHOD(std::unique_ptr, constructReceiver, (), (override)); +}; + +} // namespace score::lcm + +#endif // MOCK_SUPERVISION_CONTROL_NOTIFIER_HPP_INCLUDED From e1cec425a19b949686aa81095731fa7a048514ff Mon Sep 17 00:00:00 2001 From: Cameron Craig Date: Tue, 11 Aug 2026 15:28:52 +0100 Subject: [PATCH 6/6] fix: Remove long timeout, add helper for EXPECT calls --- .../src/process_group_manager/details/BUILD | 1 - .../details/process_group_manager_UT.cpp | 23 +++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) 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 550f2cd8a..a25d42e3c 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 @@ -327,7 +327,6 @@ lm_cc_test( lm_cc_test( name = "process_group_manager_UT", - timeout = "long", srcs = ["process_group_manager_UT.cpp"], deps = [ "//score/launch_manager/src/daemon/src/configuration:config", diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp index da0f021f9..2f3278a6e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_group_manager_UT.cpp @@ -108,6 +108,13 @@ Config makeMinimalConfig() class ProcessGroupManagerWatchdogTest : public Test { protected: + void expectNormalStartup() + { + EXPECT_CALL(*alive_monitor_thread_, start()).WillOnce(Return(true)); + EXPECT_CALL(*watchdog_, init(_, _)).WillOnce(Return(true)); + EXPECT_CALL(*watchdog_, enable()).WillOnce(Return(true)); + } + void SetUp() override { RecordProperty("TestType", "unit-test"); @@ -161,9 +168,7 @@ TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogMethods // 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)); + expectNormalStartup(); EXPECT_CALL(*watchdog_, disable()).Times(1); EXPECT_CALL(*alive_monitor_thread_, stop()).Times(1); @@ -180,9 +185,7 @@ TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogService 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)); + expectNormalStartup(); // 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(); @@ -208,9 +211,7 @@ TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogFired_W constexpr int kNumRecoveryRequests = 16; // Expected - EXPECT_CALL(*alive_monitor_thread_, start()).WillOnce(Return(true)); - EXPECT_CALL(*watchdog_, init(_, _)).WillOnce(Return(true)); - EXPECT_CALL(*watchdog_, enable()).WillOnce(Return(true)); + expectNormalStartup(); EXPECT_CALL(*watchdog_, fireWatchdogReaction()).Times(AtLeast(1)); EXPECT_CALL(*watchdog_, serviceWatchdog()).Times(AtLeast(1)).WillRepeatedly([this]() { process_group_manager_->cancel(); @@ -242,9 +243,7 @@ TEST_F(ProcessGroupManagerWatchdogTest, GivenMinimalConfig_ExpectWatchdogDisable 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)); + expectNormalStartup(); // We are explicitly calling deinitialize() in this test for readability, // so disable() and stop() are expected to be called twice: once in deinitialize() and once in TearDown(). EXPECT_CALL(*watchdog_, disable()).Times(2);