From 6b0ca190ebcb0f9815946c49dadc5ae40fc79960 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:30:28 +0100 Subject: [PATCH 01/17] Initial test cases --- .../src/process_group_manager/details/BUILD | 20 ++ .../details/process_launcher_UT.cpp | 304 ++++++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_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 08b1f22db..747eb64f5 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 @@ -384,6 +384,26 @@ cc_library( ], ) +lm_cc_test( + name = "process_launcher_UT", + srcs = ["process_launcher_UT.cpp"], + linkopts = [ + "-Wl,--wrap=fork", + "-Wl,--wrap=execve", + "-Wl,--wrap=kill", + "-Wl,--wrap=wait", + "-Wl,--wrap=access", + "-Wl,--wrap=shm_open", + "-Wl,--wrap=ftruncate", + "-Wl,--wrap=shm_unlink", + ], + linkstatic = True, + deps = [ + ":process_launcher", + "@googletest//:gtest_main", + ], +) + cc_library( name = "dependency_graph", hdrs = [ diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp new file mode 100644 index 000000000..567918fe4 --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -0,0 +1,304 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include +#include +#include +#include + +#include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" + +using namespace testing; + +// NOLINTBEGIN - clang-tidy does not like syscalls :D + +class SyscallMock +{ + public: + MOCK_METHOD(pid_t, fork, (), ()); + MOCK_METHOD(int, execve, (const char*, char* const[], char* const[]), ()); + MOCK_METHOD(int, kill, (pid_t, int), ()); + MOCK_METHOD(pid_t, wait, (int*), ()); + MOCK_METHOD(int, shm_open, (const char*, int, mode_t), ()); + MOCK_METHOD(int, shm_unlink, (const char*), ()); + MOCK_METHOD(int, ftruncate, (int, off_t), ()); + MOCK_METHOD(int, access, (const char*, int), ()); +}; + +std::unique_ptr g_syscall_mock = nullptr; + +extern "C" { +// wrap for fork +extern pid_t __real_fork(void); + +pid_t __wrap_fork(void) +{ + if (g_syscall_mock) + { + return g_syscall_mock->fork(); + } + + return __real_fork(); +} + +// wrap for execve +extern int __real_execve(const char*, char* const[], char* const[]); + +int __wrap_execve(const char* filename, char* const argv[], char* const envp[]) +{ + if (g_syscall_mock) + { + return g_syscall_mock->execve(filename, argv, envp); + } + + return __real_execve(filename, argv, envp); +} + +// wrap for kill +extern int __real_kill(pid_t, int); + +int __wrap_kill(pid_t pid, int sig) +{ + if (g_syscall_mock) + { + return g_syscall_mock->kill(pid, sig); + } + + return __real_kill(pid, sig); +} + +// wrap for wait +extern pid_t __real_wait(int*); + +pid_t __wrap_wait(int* status) +{ + if (g_syscall_mock) + { + return g_syscall_mock->wait(status); + } + + return __real_wait(status); +} + +// wrap for shm_open +extern int __real_shm_open(const char*, int, mode_t); + +int __wrap_shm_open(const char* name, int oflag, mode_t mode) +{ + if (g_syscall_mock) + { + return g_syscall_mock->shm_open(name, oflag, mode); + } + + return __real_shm_open(name, oflag, mode); +} + +// wrap for shm_unlink +extern int __real_shm_unlink(const char*); + +int __wrap_shm_unlink(const char* name) +{ + if (g_syscall_mock) + { + return g_syscall_mock->shm_unlink(name); + } + + return __real_shm_unlink(name); +} + +// wrap for ftruncate +extern int __real_ftruncate(int fildes, off_t length); + +int __wrap_ftruncate(int fildes, off_t length) +{ + if (g_syscall_mock) + { + return g_syscall_mock->ftruncate(fildes, length); + } + + return __real_ftruncate(fildes, length); +} + +// wrap for access +extern int __real_access(const char* name, int type); + +int __wrap_access(const char* name, int type) +{ + if (g_syscall_mock) + { + return g_syscall_mock->access(name, type); + } + + return __real_access(name, type); +} +} + +// NOLINTEND + +using namespace score::mw::lifecycle::internal::osal; + +class ProcessLauncherTest : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "equivalence-classes"); + + g_syscall_mock = std::make_unique(); + + // Used by logging framework + EXPECT_CALL(*g_syscall_mock, access).WillRepeatedly(Invoke(__real_access)); + } + + void TearDown() override + { + g_syscall_mock.reset(); + } + + ProcessLauncher process_launcher{}; +}; + +TEST_F(ProcessLauncherTest, waitForTerminationSuccess) +{ + RecordProperty("Description", "Test that waitForTermination calls `wait` and sets the correct values"); + + const pid_t pid = 7; + const uint32_t status = 9; + + EXPECT_CALL(*g_syscall_mock, wait).WillOnce(DoAll(SetArgPointee<0>(status), Return(pid))); + + ProcessID out_pid; + int32_t out_status; + const auto res = process_launcher.waitForTermination(out_pid, out_status); + + EXPECT_EQ(out_pid, pid); + EXPECT_EQ(out_status, status); + EXPECT_EQ(res, OsalReturnType::kSuccess); +} + +TEST_F(ProcessLauncherTest, waitForTerminationFails) +{ + RecordProperty("Description", "Test that waitForTermination reacts correctly to a failed `wait` syscall"); + + EXPECT_CALL(*g_syscall_mock, wait).WillOnce(SetErrnoAndReturn(WNOHANG, -1)); + + ProcessID out_pid; + int32_t out_status; + const auto res = process_launcher.waitForTermination(out_pid, out_status); + + EXPECT_EQ(res, OsalReturnType::kFail); +} + +class TerminationTest : public ProcessLauncherTest +{ +}; + +TEST_F(TerminationTest, requestTerminationSuccess) +{ + RecordProperty( + "Description", + "Test that requestTermination invokes `kill` with the provided pid and SIGTERM and returns the correct osal " + "result"); + const pid_t pid = 7; + + EXPECT_CALL(*g_syscall_mock, kill(pid, SIGTERM)).WillOnce(Return(0)); + + const OsalReturnType res = process_launcher.requestTermination(pid); + + EXPECT_EQ(res, OsalReturnType::kSuccess); +} + +TEST_F(TerminationTest, requestTerminationFailure) +{ + RecordProperty( + "Description", "Test that requestTermination returns the correct osal result when the `kill` syscall fails"); + const pid_t pid = 7; + + EXPECT_CALL(*g_syscall_mock, kill).WillOnce(SetErrnoAndReturn(ESRCH, -1)); + + const OsalReturnType res = process_launcher.requestTermination(pid); + + EXPECT_EQ(res, OsalReturnType::kFail); +} + +TEST_F(TerminationTest, requestTerminationInvalid) +{ + RecordProperty("Description", "Test that requestTermination rejects invalid PIDs"); + + const pid_t pid = -1; + + EXPECT_CALL(*g_syscall_mock, kill).Times(0); + + const OsalReturnType res = process_launcher.requestTermination(pid); + + EXPECT_EQ(res, OsalReturnType::kFail); +} + +TEST_F(TerminationTest, forceTerminationSuccess) +{ + RecordProperty( + "Description", + "Test that forceTermination invokes `kill` with the provided pid and SIGKILL and returns the correct osal " + "result"); + const pid_t pid = 7; + + EXPECT_CALL(*g_syscall_mock, kill(pid, SIGKILL)).WillOnce(Return(0)); + + const OsalReturnType res = process_launcher.forceTermination(pid); + + EXPECT_EQ(res, OsalReturnType::kSuccess); +} + +TEST_F(TerminationTest, forceTerminationSearchFailure) +{ + RecordProperty( + "Description", + "Test that forceTermination returns the correct osal result when the `kill` syscall fails due to a missing " + "process"); + const pid_t pid = 7; + + EXPECT_CALL(*g_syscall_mock, kill).WillOnce(SetErrnoAndReturn(ESRCH, -1)); + + const OsalReturnType res = process_launcher.forceTermination(pid); + + EXPECT_EQ(res, OsalReturnType::kFail); +} + +TEST_F(TerminationTest, forceTerminationPermFailure) +{ + RecordProperty( + "Description", + "Test that forceTermination returns the correct osal result when the `kill` syscall fails due to a permission " + "error"); + const pid_t pid = 7; + + EXPECT_CALL(*g_syscall_mock, kill).WillOnce(SetErrnoAndReturn(EPERM, -1)); + + const OsalReturnType res = process_launcher.forceTermination(pid); + + EXPECT_EQ(res, OsalReturnType::kFail); +} + +TEST_F(TerminationTest, forceTerminationInvalid) +{ + RecordProperty("Description", "Test that forceTermination rejects invalid PIDs"); + + const pid_t pid = -1; + + EXPECT_CALL(*g_syscall_mock, kill).Times(0); + + const OsalReturnType res = process_launcher.forceTermination(pid); + + EXPECT_EQ(res, OsalReturnType::kFail); +} From 5a5057824626dd3cdb4bc429ef2a06b98a4cfcd0 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Mon, 21 Sep 2026 09:12:26 +0100 Subject: [PATCH 02/17] Header changes --- score/launch_manager/src/daemon/src/osal/BUILD | 5 ----- score/launch_manager/src/daemon/src/osal/ifile_waiter.hpp | 2 +- score/launch_manager/src/daemon/src/osal/ipc_comms.hpp | 2 +- score/launch_manager/src/daemon/src/osal/semaphore.hpp | 2 +- score/launch_manager/src/daemon/src/osal/wait_for_file.hpp | 2 +- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/score/launch_manager/src/daemon/src/osal/BUILD b/score/launch_manager/src/daemon/src/osal/BUILD index b5a576bf7..e1a27e41d 100644 --- a/score/launch_manager/src/daemon/src/osal/BUILD +++ b/score/launch_manager/src/daemon/src/osal/BUILD @@ -25,7 +25,6 @@ cc_library( name = "semaphore", srcs = ["details/posix/semaphore.cpp"], hdrs = [ - "return_types.hpp", "semaphore.hpp", ], include_prefix = "score/mw/launch_manager/osal", @@ -38,7 +37,6 @@ cc_library( name = "wait_for_file", srcs = ["details/posix/wait_for_file.cpp"], hdrs = [ - "return_types.hpp", "wait_for_file.hpp", ], include_prefix = "score/mw/launch_manager/osal", @@ -73,7 +71,6 @@ cc_library( name = "ifile_waiter", hdrs = [ "ifile_waiter.hpp", - "return_types.hpp", ], include_prefix = "score/mw/launch_manager/osal", strip_include_prefix = "/score/launch_manager/src/daemon/src/osal", @@ -165,8 +162,6 @@ cc_library( name = "ipc_comms", hdrs = [ "ipc_comms.hpp", - "return_types.hpp", - "semaphore.hpp", ], include_prefix = "score/mw/launch_manager/osal", strip_include_prefix = "/score/launch_manager/src/daemon/src/osal", diff --git a/score/launch_manager/src/daemon/src/osal/ifile_waiter.hpp b/score/launch_manager/src/daemon/src/osal/ifile_waiter.hpp index e0bafa096..5d8fc1ad9 100644 --- a/score/launch_manager/src/daemon/src/osal/ifile_waiter.hpp +++ b/score/launch_manager/src/daemon/src/osal/ifile_waiter.hpp @@ -20,7 +20,7 @@ #include "score/mw/launch_manager/configuration/component_config.hpp" #include -#include "return_types.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" namespace score::mw::lifecycle::internal::osal { diff --git a/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp b/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp index a3a054171..fa047108c 100644 --- a/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp +++ b/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp @@ -18,7 +18,7 @@ #include #include "score/mw/launch_manager/common/log.hpp" -#include "semaphore.hpp" +#include "score/mw/launch_manager/osal/semaphore.hpp" namespace score::mw::lifecycle::internal::osal { diff --git a/score/launch_manager/src/daemon/src/osal/semaphore.hpp b/score/launch_manager/src/daemon/src/osal/semaphore.hpp index 52203dde4..e9d7ea3b9 100644 --- a/score/launch_manager/src/daemon/src/osal/semaphore.hpp +++ b/score/launch_manager/src/daemon/src/osal/semaphore.hpp @@ -17,7 +17,7 @@ #include #include -#include "return_types.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" namespace score::mw::lifecycle::internal::osal { diff --git a/score/launch_manager/src/daemon/src/osal/wait_for_file.hpp b/score/launch_manager/src/daemon/src/osal/wait_for_file.hpp index 497913b02..b39710bf3 100644 --- a/score/launch_manager/src/daemon/src/osal/wait_for_file.hpp +++ b/score/launch_manager/src/daemon/src/osal/wait_for_file.hpp @@ -23,7 +23,7 @@ #include #include -#include "return_types.hpp" +#include "score/mw/launch_manager/osal/return_types.hpp" namespace score::mw::lifecycle::internal::osal { From 25641702281b87edf4f72910c9bf3d26a4953d47 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:37:58 +0100 Subject: [PATCH 03/17] Startprocess UT --- .../src/configuration/component_config.hpp | 8 +- .../src/process_group_manager/details/BUILD | 10 + .../details/process_launcher.cpp | 8 +- .../details/process_launcher_UT.cpp | 536 +++++++++++++++++- 4 files changed, 542 insertions(+), 20 deletions(-) diff --git a/score/launch_manager/src/daemon/src/configuration/component_config.hpp b/score/launch_manager/src/daemon/src/configuration/component_config.hpp index 21322f819..9aea85b20 100644 --- a/score/launch_manager/src/daemon/src/configuration/component_config.hpp +++ b/score/launch_manager/src/daemon/src/configuration/component_config.hpp @@ -74,7 +74,7 @@ using ReadyCondition = std::variant; struct ComponentProperties { std::string binary_name; - ApplicationProfile application_profile; + ApplicationProfile application_profile{}; std::vector depends_on; std::vector process_arguments; @@ -104,15 +104,15 @@ struct DeploymentConfig std::optional ready_recovery_action; // Currently only SwitchRunTargetAction is supported here, RestartAction to be added in the future std::optional recovery_action; - Sandbox sandbox; + Sandbox sandbox{}; }; struct ComponentConfig { std::string name; std::string description; - ComponentProperties component_properties; - DeploymentConfig deployment_config; + ComponentProperties component_properties{}; + DeploymentConfig deployment_config{}; }; } // namespace score::mw::lifecycle::internal::configuration 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 747eb64f5..cd76a5743 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 @@ -396,6 +396,16 @@ lm_cc_test( "-Wl,--wrap=shm_open", "-Wl,--wrap=ftruncate", "-Wl,--wrap=shm_unlink", + "-Wl,--wrap=mmap", + "-Wl,--wrap=munmap", + "-Wl,--wrap=setpgid", + "-Wl,--wrap=setgid", + "-Wl,--wrap=setuid", + "-Wl,--wrap=sched_setscheduler", + "-Wl,--wrap=chdir", + "-Wl,--wrap=setrlimit", + "-Wl,--wrap=getpid", + "-Wl,--wrap=fcntl", ], linkstatic = True, deps = [ diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp index 34cdbae9c..e01c8039c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp @@ -172,10 +172,8 @@ void changeSecurityPolicy(const score::mw::lifecycle::internal::configuration::S namespace score::mw::lifecycle::internal::osal { -OsalReturnType ProcessLauncher::startProcess( - ProcessID& pid, - IpcCommsP& block, - const score::mw::lifecycle::internal::configuration::ComponentConfig& config) +OsalReturnType +ProcessLauncher::startProcess(ProcessID& pid, IpcCommsP& block, const configuration::ComponentConfig& config) { OsalReturnType result = OsalReturnType::kFail; @@ -194,7 +192,7 @@ OsalReturnType ProcessLauncher::startProcess( bool comms_result = true; auto app_type = config.component_properties.application_profile.application_type; - if (app_type != score::mw::lifecycle::internal::configuration::ApplicationType::Native) + if (app_type != configuration::ApplicationType::Native) { comms_result = setupComms(block, fd, config); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 567918fe4..678e3656e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -33,6 +33,19 @@ class SyscallMock MOCK_METHOD(int, shm_unlink, (const char*), ()); MOCK_METHOD(int, ftruncate, (int, off_t), ()); MOCK_METHOD(int, access, (const char*, int), ()); + MOCK_METHOD(void*, mmap, (void* __addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset), ()); + MOCK_METHOD(int, munmap, (void* __addr, size_t __len), ()); + MOCK_METHOD(void, sysexit, (int status), ()); + MOCK_METHOD(int, setpgid, (__pid_t __pid, __pid_t __pgid), ()); + MOCK_METHOD(int, setgid, (__gid_t __gid), ()); + MOCK_METHOD(int, setuid, (__uid_t __uid), ()); + MOCK_METHOD(int, sched_setscheduler, (__pid_t __pid, int __policy, const struct sched_param*), ()); + MOCK_METHOD(int, chdir, (const char* __path), ()); + MOCK_METHOD(int, setrlimit, (__rlimit_resource_t __resource, const struct rlimit* __rlimits), ()); + MOCK_METHOD(int, setSecurityPolicy, (const char* policy), ()); + MOCK_METHOD(__pid_t, getpid, (), ()); + MOCK_METHOD(int, fcntl, (int __fd, int __cmd), ()); + MOCK_METHOD(int, setgroups, (size_t n, const gid_t* groups), ()); }; std::unique_ptr g_syscall_mock = nullptr; @@ -141,11 +154,161 @@ int __wrap_access(const char* name, int type) return __real_access(name, type); } + +// wrap for mmap +extern void* __real_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset); + +void* __wrap_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset) +{ + if (g_syscall_mock) + { + return g_syscall_mock->mmap(__addr, __len, __prot, __flags, __fd, __offset); + } + + return __real_mmap(__addr, __len, __prot, __flags, __fd, __offset); +} + +// wrap for munmap +extern int __real_munmap(void* __addr, size_t __len); + +int __wrap_munmap(void* __addr, size_t __len) +{ + if (g_syscall_mock) + { + return g_syscall_mock->munmap(__addr, __len); + } + + return __real_munmap(__addr, __len); +} + +// wrap for setpgid +extern int __real_setpgid(__pid_t __pid, __pid_t __pgid); + +int __wrap_setpgid(__pid_t __pid, __pid_t __pgid) +{ + if (g_syscall_mock) + { + return g_syscall_mock->setpgid(__pid, __pgid); + } + + return __real_setpgid(__pid, __pgid); +} + +// wrap for setgid +extern int __real_setgid(__gid_t __gid); + +int __wrap_setgid(__gid_t __gid) +{ + if (g_syscall_mock) + { + return g_syscall_mock->setgid(__gid); + } + + return __real_setgid(__gid); +} + +// wrap for setuid +extern int __real_setuid(__uid_t __uid); + +int __wrap_setuid(__uid_t __uid) +{ + if (g_syscall_mock) + { + return g_syscall_mock->setuid(__uid); + } + + return __real_setuid(__uid); +} + +// wrap for sched_setscheduler +extern int __real_sched_setscheduler(__pid_t __pid, int __policy, const struct sched_param* __param); + +int __wrap_sched_setscheduler(__pid_t __pid, int __policy, const struct sched_param* __param) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sched_setscheduler(__pid, __policy, __param); + } + + return __real_sched_setscheduler(__pid, __policy, __param); +} + +// wrap for chdir +extern int __real_chdir(const char* __path); + +int __wrap_chdir(const char* __path) +{ + if (g_syscall_mock) + { + return g_syscall_mock->chdir(__path); + } + + return __real_chdir(__path); } +// wrap for setrlimit +extern int __real_setrlimit(__rlimit_resource_t __resource, const struct rlimit* __rlimits); + +int __wrap_setrlimit(__rlimit_resource_t __resource, const struct rlimit* __rlimits) +{ + if (g_syscall_mock) + { + return g_syscall_mock->setrlimit(__resource, __rlimits); + } + + return __real_setrlimit(__resource, __rlimits); +} + +// wrap for getpid +extern int __real_getpid(); + +__pid_t __wrap_getpid() +{ + if (g_syscall_mock) + { + return g_syscall_mock->getpid(); + } + + return __real_getpid(); +} + +// wrap for fcntl +extern int __real_fcntl(int __fd, int __cmd, ...); + +int __wrap_fcntl(int __fd, int __cmd, ...) +{ + if (g_syscall_mock) + { + return g_syscall_mock->fcntl(__fd, __cmd); + } + + return __real_fcntl(__fd, __cmd); +} +} + +namespace score::mw::lifecycle::internal::osal +{ +void sysexit(int status) +{ + g_syscall_mock->sysexit(status); +} + +int setSecurityPolicy(const char* policy) +{ + return g_syscall_mock->setSecurityPolicy(policy); +} + +int setgroups(size_t n, const gid_t* groups) +{ + return g_syscall_mock->setgroups(n, groups); +} + +} // namespace score::mw::lifecycle::internal::osal + // NOLINTEND using namespace score::mw::lifecycle::internal::osal; +using namespace score::mw::lifecycle::internal; class ProcessLauncherTest : public ::testing::Test { @@ -156,17 +319,19 @@ class ProcessLauncherTest : public ::testing::Test RecordProperty("DerivationTechnique", "equivalence-classes"); g_syscall_mock = std::make_unique(); + process_launcher = std::make_unique(); // Used by logging framework - EXPECT_CALL(*g_syscall_mock, access).WillRepeatedly(Invoke(__real_access)); + ON_CALL(*g_syscall_mock, access).WillByDefault(Invoke(__real_access)); } void TearDown() override { + process_launcher.reset(); g_syscall_mock.reset(); } - ProcessLauncher process_launcher{}; + std::unique_ptr process_launcher; }; TEST_F(ProcessLauncherTest, waitForTerminationSuccess) @@ -180,7 +345,7 @@ TEST_F(ProcessLauncherTest, waitForTerminationSuccess) ProcessID out_pid; int32_t out_status; - const auto res = process_launcher.waitForTermination(out_pid, out_status); + const auto res = process_launcher->waitForTermination(out_pid, out_status); EXPECT_EQ(out_pid, pid); EXPECT_EQ(out_status, status); @@ -195,7 +360,7 @@ TEST_F(ProcessLauncherTest, waitForTerminationFails) ProcessID out_pid; int32_t out_status; - const auto res = process_launcher.waitForTermination(out_pid, out_status); + const auto res = process_launcher->waitForTermination(out_pid, out_status); EXPECT_EQ(res, OsalReturnType::kFail); } @@ -214,7 +379,7 @@ TEST_F(TerminationTest, requestTerminationSuccess) EXPECT_CALL(*g_syscall_mock, kill(pid, SIGTERM)).WillOnce(Return(0)); - const OsalReturnType res = process_launcher.requestTermination(pid); + const OsalReturnType res = process_launcher->requestTermination(pid); EXPECT_EQ(res, OsalReturnType::kSuccess); } @@ -227,7 +392,7 @@ TEST_F(TerminationTest, requestTerminationFailure) EXPECT_CALL(*g_syscall_mock, kill).WillOnce(SetErrnoAndReturn(ESRCH, -1)); - const OsalReturnType res = process_launcher.requestTermination(pid); + const OsalReturnType res = process_launcher->requestTermination(pid); EXPECT_EQ(res, OsalReturnType::kFail); } @@ -240,7 +405,7 @@ TEST_F(TerminationTest, requestTerminationInvalid) EXPECT_CALL(*g_syscall_mock, kill).Times(0); - const OsalReturnType res = process_launcher.requestTermination(pid); + const OsalReturnType res = process_launcher->requestTermination(pid); EXPECT_EQ(res, OsalReturnType::kFail); } @@ -255,7 +420,7 @@ TEST_F(TerminationTest, forceTerminationSuccess) EXPECT_CALL(*g_syscall_mock, kill(pid, SIGKILL)).WillOnce(Return(0)); - const OsalReturnType res = process_launcher.forceTermination(pid); + const OsalReturnType res = process_launcher->forceTermination(pid); EXPECT_EQ(res, OsalReturnType::kSuccess); } @@ -270,7 +435,7 @@ TEST_F(TerminationTest, forceTerminationSearchFailure) EXPECT_CALL(*g_syscall_mock, kill).WillOnce(SetErrnoAndReturn(ESRCH, -1)); - const OsalReturnType res = process_launcher.forceTermination(pid); + const OsalReturnType res = process_launcher->forceTermination(pid); EXPECT_EQ(res, OsalReturnType::kFail); } @@ -285,7 +450,7 @@ TEST_F(TerminationTest, forceTerminationPermFailure) EXPECT_CALL(*g_syscall_mock, kill).WillOnce(SetErrnoAndReturn(EPERM, -1)); - const OsalReturnType res = process_launcher.forceTermination(pid); + const OsalReturnType res = process_launcher->forceTermination(pid); EXPECT_EQ(res, OsalReturnType::kFail); } @@ -298,7 +463,356 @@ TEST_F(TerminationTest, forceTerminationInvalid) EXPECT_CALL(*g_syscall_mock, kill).Times(0); - const OsalReturnType res = process_launcher.forceTermination(pid); + const OsalReturnType res = process_launcher->forceTermination(pid); EXPECT_EQ(res, OsalReturnType::kFail); } + +class StartProcessTest : public ProcessLauncherTest +{ + protected: + void SetUp() override + { + ProcessLauncherTest::SetUp(); + + config_.name = "TestComponent"; + config_.component_properties.binary_name = "TestProcess"; + config_.component_properties.application_profile.application_type = configuration::ApplicationType::Native; + config_.deployment_config.bin_dir = "/bin"; + config_.deployment_config.working_dir = "/tmp"; + config_.deployment_config.sandbox.max_memory_usage = std::nullopt; + config_.deployment_config.sandbox.max_cpu_usage = std::nullopt; + config_.deployment_config.sandbox.max_memory_usage = std::nullopt; + config_.deployment_config.sandbox.security_policy = std::nullopt; + config_.deployment_config.sandbox.scheduling_priority = 1; + config_.deployment_config.sandbox.scheduling_policy = SCHED_RR; + config_.deployment_config.sandbox.gid = 1; + config_.deployment_config.sandbox.uid = 1; + config_.deployment_config.sandbox.supplementary_group_ids = {}; + } + + void TearDown() override + { + sync_.reset(); + + ProcessLauncherTest::TearDown(); + } + + IpcCommsSync* StubShmObject() + { + const int fd = 123; + void* data = static_cast(test_buffer.data()); + EXPECT_CALL(*g_syscall_mock, shm_open).WillOnce(Return(fd)); + EXPECT_CALL(*g_syscall_mock, mmap(_, _, _, _, fd, _)).WillOnce(Return(data)); + EXPECT_CALL(*g_syscall_mock, ftruncate(fd, _)).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, munmap(data, sizeof(IpcCommsSync))).WillOnce(Return(0)); + + return static_cast(data); + } + + configuration::ComponentConfig config_ = {}; + ProcessID pid_; + IpcCommsP sync_; + + alignas(IpcCommsSync) std::array test_buffer; +}; + +TEST_F(StartProcessTest, startProcessNoFile) +{ + RecordProperty("Description", "Test that startProcess returns a failure if the provided path does not exist"); + + EXPECT_CALL(*g_syscall_mock, access(_, _)).WillOnce(Return(-1)); + + EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); +} + +TEST_F(StartProcessTest, startProcessNoPath) +{ + RecordProperty("Description", "Test that startProcess returns a failure if the provided path is empty"); + + config_.component_properties.binary_name = ""; + EXPECT_CALL(*g_syscall_mock, access).Times(0); // Access should not be called on an empty path + + EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); +} + +TEST_F(StartProcessTest, startProcessForkFailed) +{ + RecordProperty("Description", "Test that startProcess returns a failure if the provided path is empty"); + + EXPECT_CALL(*g_syscall_mock, access(_, _)).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, fork).WillOnce(Return(-1)); + + EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); +} + +class SetSchedulingAndSecurityTest : public StartProcessTest +{ + protected: + void SetUp() override + { + StartProcessTest::SetUp(); + + EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, fork).WillOnce(Return(0)); // Test the child process + } +}; + +TEST_F(SetSchedulingAndSecurityTest, setpgidFails) +{ + RecordProperty("Description", "Verify that the forked process exits if setting pgid fails"); + + EXPECT_CALL(*g_syscall_mock, setpgid).WillOnce(Return(-1)); + EXPECT_CALL(*g_syscall_mock, sched_setscheduler).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(SetSchedulingAndSecurityTest, setgidFails) +{ + RecordProperty("Description", "Verify that the forked process exits if setting gid fails"); + + EXPECT_CALL(*g_syscall_mock, setpgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, sched_setscheduler).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(-1)); + EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(SetSchedulingAndSecurityTest, setuidFails) +{ + RecordProperty("Description", "Verify that the forked process exits if setting uid fails"); + + EXPECT_CALL(*g_syscall_mock, setpgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, sched_setscheduler).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(-1)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(SetSchedulingAndSecurityTest, setschedFails) +{ + RecordProperty("Description", "Verify that the forked process exits if setting the scheduler fails"); + + EXPECT_CALL(*g_syscall_mock, setpgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, sched_setscheduler).WillOnce(Return(-1)); + EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(SetSchedulingAndSecurityTest, setschedClampedUpper) +{ + RecordProperty( + "Description", "Verify that if the configured priority is higher than the OS supports, it is clamped"); + + config_.deployment_config.sandbox.scheduling_policy = SCHED_FIFO; + const int too_high = 1000; + config_.deployment_config.sandbox.scheduling_priority = too_high; + + EXPECT_CALL(*g_syscall_mock, setpgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); + + EXPECT_CALL(*g_syscall_mock, sched_setscheduler(_, _, Field(&sched_param::sched_priority, Lt(too_high)))) + .WillOnce(Return(0)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(SetSchedulingAndSecurityTest, setschedClampedLower) +{ + RecordProperty( + "Description", "Verify that if the configured priority is lower than the OS supports, it is clamped"); + + config_.deployment_config.sandbox.scheduling_policy = SCHED_FIFO; + const int too_low = -10; + config_.deployment_config.sandbox.scheduling_priority = too_low; + + EXPECT_CALL(*g_syscall_mock, setpgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); + + EXPECT_CALL(*g_syscall_mock, sched_setscheduler(_, _, Field(&sched_param::sched_priority, Gt(too_low)))) + .WillOnce(Return(0)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(SetSchedulingAndSecurityTest, setgroupsFails) +{ + RecordProperty("Description", "Verify that if setting supplementary gids fails, the forked process exits"); + + config_.deployment_config.sandbox.supplementary_group_ids = {1, 2, 3}; + + EXPECT_CALL(*g_syscall_mock, setpgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, sched_setscheduler).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgroups).WillOnce(Return(-1)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(StartProcessTest, chdirFails) +{ + RecordProperty("Description", "Verify that the forked process exits if changing the working dir fails"); + + EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, chdir).WillOnce(Return(-1)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(StartProcessTest, changeSecurityPolicyFails) +{ + RecordProperty("Description", "Verify that the forked process exits if changing the security policy fails"); + + config_.deployment_config.sandbox.security_policy = "security"; + + EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setSecurityPolicy).WillOnce(Return(-1)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(StartProcessTest, setRLimitFails) +{ + RecordProperty("Description", "Verify that the forked process exits if setting a limit fails"); + + config_.deployment_config.sandbox.max_cpu_usage = 500; + + EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setrlimit).WillOnce(Return(-1)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +TEST_F(StartProcessTest, execveFails) +{ + RecordProperty("Description", "Verify that the forked process exits if execve fails"); + + EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, execve).WillOnce(Return(-1)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +// Define a matcher that checks a null-terminated char** against a vector of strings +MATCHER_P(ArgvMatches, expected_args, "") +{ + for (size_t i = 0; i < expected_args.size(); ++i) + { + // Check if the argument is null prematurely, or if the string doesn't match + if (arg[i] == nullptr || std::string(arg[i]) != expected_args[i]) + { + *result_listener << "mismatch at index " << i << " (expected: \"" << expected_args[i] << "\", got: \"" + << (arg[i] ? arg[i] : "NULL") << "\")"; + return false; + } + } + // execve argv must be null-terminated; ensure the last expected element is followed by NULL + return arg[expected_args.size()] == nullptr; +} + +MATCHER_P(CArrayMatches, expected, "") +{ + for (size_t i = 0; i < expected.size(); ++i) + { + if (arg[i] != expected[i]) + { + *result_listener << "mismatch at index " << i << " (expected: " << expected[i] << ", got: " << arg[i] + << ")"; + return false; + } + } + return true; +} + +TEST_F(StartProcessTest, startProcessChildSuccess) +{ + RecordProperty("Description", "Verify that when startProcess succeeds, the forked process is configured correctly"); + + const pid_t forked_pid = 23; + const int scheduler = SCHED_FIFO; + const int uid = 11; + const int gid = 13; + const std::vector sgids = {1, 2, 3}; + const std::uint64_t mem_limit = 4096; + const std::uint32_t cpu_limit = 500; + const std::string security_policy = "security"; + const std::vector args_in = {"-c 2", "--argument yes"}; + const std::vector expected_launch_args = {"/bin/TestProcess", args_in[0], args_in[1]}; + + config_.component_properties.application_profile.application_type = configuration::ApplicationType::Reporting; + config_.deployment_config.sandbox.scheduling_policy = scheduler; + config_.deployment_config.sandbox.uid = uid; + config_.deployment_config.sandbox.gid = gid; + config_.deployment_config.sandbox.supplementary_group_ids = sgids; + config_.deployment_config.sandbox.max_memory_usage = mem_limit; + config_.deployment_config.sandbox.max_cpu_usage = cpu_limit; + config_.deployment_config.sandbox.security_policy = security_policy; + config_.deployment_config.environmental_variables.add("environment", "yes"); + config_.deployment_config.environmental_variables.add("errors", "no"); + config_.component_properties.process_arguments = args_in; + + IpcCommsSync* block = StubShmObject(); + + EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, fork).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, getpid).WillRepeatedly(Return(forked_pid)); + EXPECT_CALL(*g_syscall_mock, fcntl).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setpgid(0, forked_pid)); + EXPECT_CALL(*g_syscall_mock, sched_setscheduler(0, scheduler, _)).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid(uid)).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgid(gid)).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgroups(sgids.size(), CArrayMatches(sgids))).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, chdir(StrEq("/tmp"))).WillOnce(Return(0)); + EXPECT_CALL( + *g_syscall_mock, + setrlimit(RLIMIT_DATA, AllOf(Field(&rlimit::rlim_cur, mem_limit), Field(&rlimit::rlim_max, mem_limit)))); + EXPECT_CALL( + *g_syscall_mock, + setrlimit(RLIMIT_AS, AllOf(Field(&rlimit::rlim_cur, mem_limit), Field(&rlimit::rlim_max, mem_limit)))); + EXPECT_CALL( + *g_syscall_mock, + setrlimit(RLIMIT_CPU, AllOf(Field(&rlimit::rlim_cur, cpu_limit), Field(&rlimit::rlim_max, cpu_limit)))); + EXPECT_CALL(*g_syscall_mock, setSecurityPolicy(StrEq(security_policy.data()))).WillOnce(Return(0)); + EXPECT_CALL( + *g_syscall_mock, + execve( + StrEq(expected_launch_args[0]), + ArgvMatches(expected_launch_args), + config_.deployment_config.environmental_variables.envp())) + .WillOnce(Return(0)); + + EXPECT_CALL(*g_syscall_mock, sysexit).Times(0); // No failures + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + + EXPECT_EQ(block->pid_, forked_pid); + EXPECT_EQ(block->comms_type_, CommsType::kReporting); +} From ef38fb86bb58b252db6d8a1fa3fa863856896f2f Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Mon, 21 Sep 2026 16:51:06 +0100 Subject: [PATCH 04/17] Startprocess gaps --- .../details/process_launcher.cpp | 49 ++++-------- .../details/process_launcher_UT.cpp | 76 +++++++++++++++++++ 2 files changed, 91 insertions(+), 34 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp index e01c8039c..8c30fc6a8 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp @@ -82,8 +82,6 @@ void setLimit(const int resource, const std::size_t amount, const std::string_vi /// @details The implementation should be async signal safe. void handleComms(score::mw::lifecycle::internal::osal::ChildProcessConfig& param) { - // kNoComms !fd3 & !fd4 - // kReporting fd3 & !fd4 if (!param.shared_block) { // kNoComms, fds are CLOEXEC @@ -96,26 +94,17 @@ void handleComms(score::mw::lifecycle::internal::osal::ChildProcessConfig& param // It must be ensured that sync_fd (f3) remains open depending on // the communication type. Flag FD_CLOEXEC is cleared conditionally to ensure that the // respective file descriptor remains open after the execve call. - switch (param.shared_block->comms_type_) + SCORE_LANGUAGE_FUTURECPP_ASSERT_DBG_MESSAGE( + param.shared_block->comms_type_ != CommsType::kNoComms, + "This case means param.shared_block == nullptr and is expected to be handled above"); + + SCORE_LANGUAGE_FUTURECPP_ASSERT_DBG_MESSAGE( + param.shared_block->comms_type_ == CommsType::kReporting, "This is the only remaining comms type in use"); + + if (-1 == fcntl(IpcCommsSync::sync_fd, F_SETFD, 0)) { - case CommsType::kNoComms: - // in the current implementation this case means param.shared_block == nullptr and is handled above - break; - case CommsType::kReporting: - if (-1 == fcntl(IpcCommsSync::sync_fd, F_SETFD, 0)) - { - static_cast(signal_safe_log_errno(errno, "fcntl at line ", __LINE__, " failed")); - sysexit(EXIT_FAILURE); - } - break; - default: - static_cast(signal_safe_log( - "at line ", - __LINE__, - " unknown CommsType ", - static_cast(param.shared_block->comms_type_))); - sysexit(EXIT_FAILURE); - break; + static_cast(signal_safe_log_errno(errno, "fcntl at line ", __LINE__, " failed")); + sysexit(EXIT_FAILURE); } } @@ -248,8 +237,6 @@ ProcessLauncher::startProcess(ProcessID& pid, IpcCommsP& block, const configurat bool ProcessLauncher::setupComms(IpcCommsP& block, int& fd, const configuration::ComponentConfig& config) { - const auto app_type = config.component_properties.application_profile.application_type; - size_t length = sizeof(IpcCommsSync); constexpr std::string_view kShmNamePrefix{"/ipc_shared_mem"}; @@ -294,17 +281,11 @@ bool ProcessLauncher::setupComms(IpcCommsP& block, int& fd, const configuration: } // Map application type to CommsType for backward compatibility - switch (app_type) - { - case configuration::ApplicationType::Native: - block->comms_type_ = CommsType::kNoComms; - break; - case configuration::ApplicationType::Reporting: - case configuration::ApplicationType::ReportingAndSupervised: - default: - block->comms_type_ = CommsType::kReporting; - break; - } + SCORE_LANGUAGE_FUTURECPP_ASSERT_DBG_MESSAGE( + config.component_properties.application_profile.application_type != configuration::ApplicationType::Native, + "We should not set up the comms object if the application type is native. This used to be the kNoComms case"); + + block->comms_type_ = CommsType::kReporting; if (!initializeSemaphores(block)) { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 678e3656e..6e4ec4d5a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -498,6 +498,12 @@ class StartProcessTest : public ProcessLauncherTest ProcessLauncherTest::TearDown(); } + void ExpectChildProcessStarts() + { + EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, fork).WillOnce(Return(0)); + } + IpcCommsSync* StubShmObject() { const int fd = 123; @@ -546,6 +552,63 @@ TEST_F(StartProcessTest, startProcessForkFailed) EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); } +TEST_F(StartProcessTest, handleCommsFailsFnctl) +{ + RecordProperty( + "Description", "Verify that the forked process exits if setting up comms fails due to a syscall failure"); + + config_.component_properties.application_profile.application_type = configuration::ApplicationType::Reporting; + ExpectChildProcessStarts(); + StubShmObject(); + EXPECT_CALL(*g_syscall_mock, fcntl).WillOnce(Return(-1)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + +class SetupCommsTest : public StartProcessTest +{ + void SetUp() override + { + StartProcessTest::SetUp(); + + config_.component_properties.application_profile.application_type = configuration::ApplicationType::Reporting; + + EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + } +}; + +TEST_F(SetupCommsTest, shmOpenFails) +{ + RecordProperty("Description", "Verify that if opening shared memory fails, startProcess fails"); + + EXPECT_CALL(*g_syscall_mock, shm_open).WillOnce(Return(-1)); + + EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); +} + +TEST_F(SetupCommsTest, ftruncateFails) +{ + RecordProperty("Description", "Verify that if truncating shared memory fails, startProcess fails"); + + EXPECT_CALL(*g_syscall_mock, shm_open).WillOnce(Return(123)); + EXPECT_CALL(*g_syscall_mock, ftruncate).WillOnce(Return(-1)); + + EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); +} + +TEST_F(SetupCommsTest, getCommsFails) +{ + RecordProperty("Description", "Verify that if truncating shared memory fails, startProcess fails"); + + EXPECT_CALL(*g_syscall_mock, shm_open).WillOnce(Return(123)); + EXPECT_CALL(*g_syscall_mock, ftruncate).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, mmap).WillOnce(Return(MAP_FAILED)); + + EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); +} + class SetSchedulingAndSecurityTest : public StartProcessTest { protected: @@ -709,6 +772,19 @@ TEST_F(StartProcessTest, setRLimitFails) static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process } +TEST_F(StartProcessTest, setRLimitIgnore) +{ + RecordProperty("Description", "Verify that rlimits are not set if the configured value is 0"); + + config_.deployment_config.sandbox.max_cpu_usage = 0; + config_.deployment_config.sandbox.max_memory_usage = 0; + + EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setrlimit).Times(0); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + TEST_F(StartProcessTest, execveFails) { RecordProperty("Description", "Verify that the forked process exits if execve fails"); From 73300b612e4e9a328666758b4d285ccf8977416e Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 22 Sep 2026 11:10:44 +0100 Subject: [PATCH 05/17] Test client methods --- .../details/process_launcher_UT.cpp | 107 +++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 6e4ec4d5a..270fe7037 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" @@ -331,7 +332,27 @@ class ProcessLauncherTest : public ::testing::Test g_syscall_mock.reset(); } + std::shared_ptr GetInitialisedIpc() + { + std::memset(test_buffer.data(), 0, test_buffer.size()); + auto* sync = reinterpret_cast(test_buffer.data()); + + sync->comms_type_ = score::mw::lifecycle::internal::osal::CommsType::kNoComms; + sync->pid_ = 0; + EXPECT_EQ(sync->reply_sync_.init(0, false), OsalReturnType::kSuccess); + EXPECT_EQ(sync->send_sync_.init(0, false), OsalReturnType::kSuccess); + + std::shared_ptr shared{sync, [](IpcCommsSync* ptr) { + EXPECT_EQ(ptr->reply_sync_.deinit(), OsalReturnType::kSuccess); + EXPECT_EQ(ptr->send_sync_.deinit(), OsalReturnType::kSuccess); + }}; + + return shared; + } + std::unique_ptr process_launcher; + + alignas(IpcCommsSync) std::array test_buffer; }; TEST_F(ProcessLauncherTest, waitForTerminationSuccess) @@ -519,8 +540,6 @@ class StartProcessTest : public ProcessLauncherTest configuration::ComponentConfig config_ = {}; ProcessID pid_; IpcCommsP sync_; - - alignas(IpcCommsSync) std::array test_buffer; }; TEST_F(StartProcessTest, startProcessNoFile) @@ -892,3 +911,87 @@ TEST_F(StartProcessTest, startProcessChildSuccess) EXPECT_EQ(block->pid_, forked_pid); EXPECT_EQ(block->comms_type_, CommsType::kReporting); } + +class ClientMethodsTest : public ProcessLauncherTest +{ +}; + +using namespace std::chrono_literals; + +TEST_F(ProcessLauncherTest, ignoreRunningNoSync) +{ + RecordProperty("Description", "Verify that ignoreRunning correctly handles a null pointer"); + + std::shared_ptr sync; + + EXPECT_EQ(process_launcher->ignoreRunning(sync), OsalReturnType::kFail); +} + +TEST_F(ProcessLauncherTest, ignoreRunningSuccess) +{ + RecordProperty("Description", "Verify that ignoreRunning posts on the reply semaphore"); + + std::shared_ptr sync = GetInitialisedIpc(); + OsalReturnType waitRes = OsalReturnType::kFail; + + auto waiter = std::thread{[&waitRes, &sync]() { + waitRes = sync->reply_sync_.timedWait(5000ms); + }}; + + EXPECT_EQ(process_launcher->ignoreRunning(sync), OsalReturnType::kSuccess); + waiter.join(); + EXPECT_EQ(waitRes, OsalReturnType::kSuccess); +} + +TEST_F(ProcessLauncherTest, kRunningNoSync) +{ + RecordProperty("Description", "Verify that waitForkRunning correctly handles a null pointer"); + + std::shared_ptr sync; + + EXPECT_EQ(process_launcher->waitForkRunning(sync, 1ms), OsalReturnType::kFail); +} + +TEST_F(ProcessLauncherTest, kRunningSuccess) +{ + RecordProperty( + "Description", + "Verify that waitForkRunning waits for a notification, posts a reply, and then waits for another notification " + "before proceeding"); + + std::shared_ptr sync = GetInitialisedIpc(); + OsalReturnType waitRes = OsalReturnType::kFail; + OsalReturnType postRes = OsalReturnType::kFail; + + auto waiter = std::thread{[&waitRes, &postRes, sync]() { + std::cout << "start t" << std::endl; + postRes = sync->send_sync_.post(); + std::cout << "posted" << std::endl; + if (postRes == OsalReturnType::kSuccess) + { + waitRes = sync->reply_sync_.timedWait(5000ms); + std::cout << "waited" << std::endl; + } + if (waitRes == OsalReturnType::kSuccess) + { + postRes = sync->send_sync_.post(); + std::cout << "posted again" << std::endl; + } + }}; + + EXPECT_EQ(process_launcher->waitForkRunning(sync, 5000ms), OsalReturnType::kSuccess); + waiter.join(); + ASSERT_EQ(postRes, OsalReturnType::kSuccess) << "Posting on the semaphore failed (test problem)"; + EXPECT_EQ(waitRes, OsalReturnType::kSuccess); +} + +TEST_F(ProcessLauncherTest, kRunningTimeout) +{ + RecordProperty( + "Description", + "Verify that waitForkRunning returns a timeout failure if no notification is received within the timeout"); + + std::shared_ptr sync = GetInitialisedIpc(); + + EXPECT_EQ(process_launcher->waitForkRunning(sync, 1ms), OsalReturnType::kTimeout); +} From ca79cc40cc82910e4b5bdca5c118859d9ac106c9 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 22 Sep 2026 11:24:33 +0100 Subject: [PATCH 06/17] Move some methods --- .../src/daemon/src/osal/ipc_comms.hpp | 47 +++++++++++++++++++ .../details/process_launcher.cpp | 37 +-------------- .../details/process_launcher.hpp | 5 -- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp b/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp index fa047108c..530f16900 100644 --- a/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp +++ b/score/launch_manager/src/daemon/src/osal/ipc_comms.hpp @@ -99,6 +99,53 @@ struct IpcCommsSync final return ret; } + /// @brief Initializes semaphores within a given shared memory block. + /// @param[in] block Pointer to the shared memory block where semaphores will be initialized. + /// @return True if semaphore initialization is successful, false otherwise. + /// @details This is static instead of a member function because, even though not needed currently, any vtable + /// lookups would be UB. + static bool initializeSemaphores(IpcCommsP shared_block) + { + bool result = true; + + if (osal::OsalReturnType::kFail == shared_block->send_sync_.init(0U, true) || + osal::OsalReturnType::kFail == shared_block->reply_sync_.init(0U, true)) + { + result = false; + LM_LOG_ERROR() << "Semaphore init failed: Unable to initialize send_sync or reply_sync semaphore."; + } + + return result; + } + + /// @brief Deinitializes semaphores within a given shared memory block. + /// @param[in] block Pointer to the shared memory block. + /// @details This is static instead of a member function because, even though not needed currently, any vtable + /// lookups would be UB. + static void deinit(IpcCommsP shared_block) + { + // We are not interested in the result of msync, just whether it worked or not. + // If it did not work, the child process has probably crashed and corrupted the shared memory + // so we should not try to deinitialize the semaphores. + // mincore would be more appropriate here, but is not available on QNX + if (msync(shared_block.get(), sizeof(IpcCommsSync), MS_ASYNC) == 0) + { + if (shared_block->send_sync_.deinit() != OsalReturnType::kSuccess) + { + LM_LOG_WARN() << "Failed to deinitialize send_sync semaphore."; + } + if (shared_block->reply_sync_.deinit() != OsalReturnType::kSuccess) + { + LM_LOG_WARN() << "Failed to deinitialize reply_sync semaphore."; + } + } + else + { + LM_LOG_WARN() << "Skipping semaphore deinitialization - shared memory region appears invalid:" + << errno_message(errno); + } + } + private: /// @brief Deleter to release IpcCommsSync object /// This is passed to the constructor of a shared pointer diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp index 8c30fc6a8..de2288796 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp @@ -287,7 +287,7 @@ bool ProcessLauncher::setupComms(IpcCommsP& block, int& fd, const configuration: block->comms_type_ = CommsType::kReporting; - if (!initializeSemaphores(block)) + if (!IpcCommsSync::initializeSemaphores(block)) { LM_LOG_ERROR() << "Semaphore init failed:" << config.name << "Unable to initialize send_sync or reply_sync semaphore."; @@ -297,20 +297,6 @@ bool ProcessLauncher::setupComms(IpcCommsP& block, int& fd, const configuration: return true; } -bool ProcessLauncher::initializeSemaphores(IpcCommsP shared_block) -{ - bool result = true; - - if (osal::OsalReturnType::kFail == shared_block->send_sync_.init(0U, true) || - osal::OsalReturnType::kFail == shared_block->reply_sync_.init(0U, true)) - { - result = false; - LM_LOG_ERROR() << "Semaphore init failed: Unable to initialize send_sync or reply_sync semaphore."; - } - - return result; -} - /// @details The implementation should be async signal safe. OsalReturnType ProcessLauncher::setSchedulingAndSecurity(const configuration::Sandbox& config) { @@ -546,26 +532,7 @@ OsalReturnType ProcessLauncher::waitForkRunning(IpcCommsP sync, std::chrono::mil result = sync->send_sync_.timedWait(std::chrono::milliseconds(100)); } - // We are not interested in the result of msync, just whether it worked or not. - // If it did not work, the child process has probably crashed and corrupted the shared memory - // so we should not try to deinitialize the semaphores. - // mincore would be more appropriate here, but is not available on QNX - if (msync(sync.get(), sizeof(IpcCommsSync), MS_ASYNC) == 0) - { - if (sync->send_sync_.deinit() != OsalReturnType::kSuccess) - { - LM_LOG_WARN() << "Failed to deinitialize send_sync semaphore."; - } - if (sync->reply_sync_.deinit() != OsalReturnType::kSuccess) - { - LM_LOG_WARN() << "Failed to deinitialize reply_sync semaphore."; - } - } - else - { - LM_LOG_WARN() << "Skipping semaphore deinitialization - shared memory region appears invalid:" - << errno_message(errno); - } + IpcCommsSync::deinit(sync); return result; } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp index 2756ecf2e..547b1966f 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp @@ -56,11 +56,6 @@ class ProcessLauncher final : public IProcess bool setupComms(IpcCommsP& sync, int& fd, const score::mw::lifecycle::internal::configuration::ComponentConfig& config); - /// @brief Initializes semaphores within a given shared memory block. - /// @param[in] block Pointer to the shared memory block where semaphores will be initialized. - /// @return True if semaphore initialization is successful, false otherwise. - bool initializeSemaphores(IpcCommsP block); - /// @brief Handles the execution of the child process after forking. /// @param[in] param Reference to child process configuration. void handleChildProcess(ChildProcessConfig& param); From 683b8da215e40a169d98206b569001a401621000 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:34:58 +0100 Subject: [PATCH 07/17] Combine wait functions --- .../details/process_info_node.cpp | 2 +- .../details/process_info_node_UT.cpp | 8 ++--- .../details/process_launcher.cpp | 30 +++++++------------ .../details/process_launcher.hpp | 5 +--- .../details/process_launcher_UT.cpp | 14 ++------- .../src/process_group_manager/iprocess.hpp | 11 ++----- .../process_group_manager/mock_iprocess.hpp | 7 +++-- 7 files changed, 27 insertions(+), 50 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp index 7b704bfa3..08afd9aa4 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node.cpp @@ -370,7 +370,7 @@ score::cpp::expected_blank ProcessInfoNode::handlePr { // currently we do not support multiple ready conditions so we need // to ignore the krunning signal. - auto wait_res = process_handling_.process_interface_->ignoreRunning(sync_); + auto wait_res = process_handling_.process_interface_->waitForkRunning(sync_, std::nullopt); static_cast(wait_res); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node_UT.cpp index 371006b71..aa2b80584 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_info_node_UT.cpp @@ -797,7 +797,7 @@ TEST_F(ProcessInfoNodeFileStateTest, ConditionAlreadyMet_ReturnsSuccess) std::chrono::milliseconds{50}, std::chrono::milliseconds{5}); expectSuccessfulProcessLaunch(); - EXPECT_CALL(mock_processIf_, ignoreRunning(_)).WillOnce(Return(osal::OsalReturnType::kSuccess)); + EXPECT_CALL(mock_processIf_, waitForkRunning(_, Eq(std::nullopt))).WillOnce(Return(osal::OsalReturnType::kSuccess)); EXPECT_CALL( mock_file_waiter_, waitForFile( @@ -824,7 +824,7 @@ TEST_F(ProcessInfoNodeFileStateTest, NotExistingCondition_ReturnsSuccess) auto node = createFileStateProcessInfoNode("/var/run/gone", configuration::FileExistenceState::NotExisting); expectSuccessfulProcessLaunch(); - EXPECT_CALL(mock_processIf_, ignoreRunning(_)).WillOnce(Return(osal::OsalReturnType::kSuccess)); + EXPECT_CALL(mock_processIf_, waitForkRunning(_, Eq(std::nullopt))).WillOnce(Return(osal::OsalReturnType::kSuccess)); EXPECT_CALL(mock_file_waiter_, waitForFile(_, Eq(configuration::FileExistenceState::NotExisting), _, _, _)) .WillOnce(Return(osal::OsalReturnType::kSuccess)); @@ -837,7 +837,7 @@ TEST_F(ProcessInfoNodeFileStateTest, NotExistingCondition_ReturnsSuccess) TEST_F(ProcessInfoNodeFileStateTest, NativeApplication_DoesNotIgnoreRunning_ReturnsSuccess) { - RecordProperty("Description", "A FileState ready condition with a native process hall not call ignoreRunning."); + RecordProperty("Description", "A FileState ready condition with a native process hall not call waitForkRunning."); auto node = createFileStateProcessInfoNode( "/var/run/ready", configuration::FileExistenceState::Exists, configuration::ApplicationType::Native); @@ -859,7 +859,7 @@ TEST_F(ProcessInfoNodeFileStateTest, WaitForFileTimesOut_ReturnsActivationTimedO auto node = createFileStateProcessInfoNode("/var/run/ready", configuration::FileExistenceState::Exists); expectSuccessfulProcessLaunch(); - EXPECT_CALL(mock_processIf_, ignoreRunning(_)).WillOnce(Return(osal::OsalReturnType::kSuccess)); + EXPECT_CALL(mock_processIf_, waitForkRunning(_, Eq(std::nullopt))).WillOnce(Return(osal::OsalReturnType::kSuccess)); EXPECT_CALL(mock_file_waiter_, waitForFile(_, _, _, _, _)).WillOnce(Return(osal::OsalReturnType::kTimeout)); // Simulate the OS handler reporting the killed process's exit once termination is requested. expectOsAcknowledgesTermination(node.get()); diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp index de2288796..5307b98b0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp @@ -491,43 +491,35 @@ OsalReturnType ProcessLauncher::waitForTermination(osal::ProcessID& pid, int32_t return result; } -OsalReturnType ProcessLauncher::ignoreRunning(IpcCommsP sync) +OsalReturnType ProcessLauncher::waitForkRunning(IpcCommsP sync, std::optional timeout) { + OsalReturnType result = OsalReturnType::kSuccess; + if (!sync) { LM_LOG_ERROR() << "Invalid shared memory pointer: The shared memory pointer is null."; return OsalReturnType::kFail; } - const auto post_res = sync->reply_sync_.post(); - if (post_res == OsalReturnType::kFail) + if (timeout.has_value()) { - LM_LOG_ERROR() << "Semaphore post failed"; - return OsalReturnType::kFail; + result = sync->send_sync_.timedWait(timeout.value()); } - return OsalReturnType::kSuccess; -} -OsalReturnType ProcessLauncher::waitForkRunning(IpcCommsP sync, std::chrono::milliseconds timeout) -{ - OsalReturnType result = OsalReturnType::kSuccess; + const auto post_res = sync->reply_sync_.post(); - if (!sync) + if (post_res == OsalReturnType::kFail) { - LM_LOG_ERROR() << "Invalid shared memory pointer: The shared memory pointer is null."; - return OsalReturnType::kFail; + LM_LOG_ERROR() << "Semaphore post failed"; + result = OsalReturnType::kFail; } - const auto time_res = sync->send_sync_.timedWait(timeout); - const auto post_res = sync->reply_sync_.post(); - - if ((time_res == OsalReturnType::kFail) || (post_res == OsalReturnType::kFail)) + if (result == OsalReturnType::kFail) { LM_LOG_ERROR() << "Semaphore timedWait or post failed: Unable to wait or post on semaphores within the " "specified timeout."; - result = OsalReturnType::kFail; } - else + else if (timeout.has_value()) { result = sync->send_sync_.timedWait(std::chrono::milliseconds(100)); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp index 547b1966f..26c3c83c8 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.hpp @@ -40,10 +40,7 @@ class ProcessLauncher final : public IProcess OsalReturnType waitForTermination(ProcessID& pid, int32_t& status) override; /// @see IProcess::waitForkRunning() for details - OsalReturnType waitForkRunning(IpcCommsP sync, std::chrono::milliseconds timeout) override; - - /// @see IProcess::waitForkRunning() for details - OsalReturnType ignoreRunning(IpcCommsP sync) override; + OsalReturnType waitForkRunning(IpcCommsP sync, std::optional timeout) override; private: /// @brief Creates shared memory for communication between processes. diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 270fe7037..5b45a56c4 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -918,18 +918,10 @@ class ClientMethodsTest : public ProcessLauncherTest using namespace std::chrono_literals; -TEST_F(ProcessLauncherTest, ignoreRunningNoSync) -{ - RecordProperty("Description", "Verify that ignoreRunning correctly handles a null pointer"); - - std::shared_ptr sync; - - EXPECT_EQ(process_launcher->ignoreRunning(sync), OsalReturnType::kFail); -} - TEST_F(ProcessLauncherTest, ignoreRunningSuccess) { - RecordProperty("Description", "Verify that ignoreRunning posts on the reply semaphore"); + RecordProperty( + "Description", "Verify that waitForkRunning without a timeout posts on the reply semaphore without waiting"); std::shared_ptr sync = GetInitialisedIpc(); OsalReturnType waitRes = OsalReturnType::kFail; @@ -938,7 +930,7 @@ TEST_F(ProcessLauncherTest, ignoreRunningSuccess) waitRes = sync->reply_sync_.timedWait(5000ms); }}; - EXPECT_EQ(process_launcher->ignoreRunning(sync), OsalReturnType::kSuccess); + EXPECT_EQ(process_launcher->waitForkRunning(sync, std::nullopt), OsalReturnType::kSuccess); waiter.join(); EXPECT_EQ(waitRes, OsalReturnType::kSuccess); } diff --git a/score/launch_manager/src/daemon/src/process_group_manager/iprocess.hpp b/score/launch_manager/src/daemon/src/process_group_manager/iprocess.hpp index 2a80ee4dc..a9ab5d1bd 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/iprocess.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/iprocess.hpp @@ -110,16 +110,9 @@ class IProcess /// @brief This method wait for kRunning to be received from the process that was started /// @param sync The valid pointer returned from startProcess. Must not be NULL - /// @param timeout How long to wait for kRunning + /// @param timeout How long to wait for kRunning. If nullopt is provided, kRunning notifications are ignored /// @return kFail if sync is NULL or a timeout occurs, kSuccess otherwise - - virtual OsalReturnType waitForkRunning(IpcCommsP sync, std::chrono::milliseconds timeout) = 0; - - /// @brief Ignores a kRunning signal. - /// @param sync The pointer returned from startProcess. - virtual OsalReturnType ignoreRunning(IpcCommsP sync) = 0; - - // virtual OsalReturnType respondToRunning(IpcCommsP sync, std::chrono::milliseconds timeout) = 0; + virtual OsalReturnType waitForkRunning(IpcCommsP sync, std::optional timeout) = 0; }; } // namespace score::mw::lifecycle::internal::osal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/mock_iprocess.hpp b/score/launch_manager/src/daemon/src/process_group_manager/mock_iprocess.hpp index d8864ee82..c65926752 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/mock_iprocess.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/mock_iprocess.hpp @@ -32,8 +32,11 @@ class MockIProcess : public IProcess MOCK_METHOD(OsalReturnType, requestTermination, (ProcessID pid), (override)); MOCK_METHOD(OsalReturnType, forceTermination, (ProcessID pid), (override)); MOCK_METHOD(OsalReturnType, waitForTermination, (ProcessID & pid, int32_t& status), (override)); - MOCK_METHOD(OsalReturnType, waitForkRunning, (IpcCommsP sync, std::chrono::milliseconds timeout), (override)); - MOCK_METHOD(OsalReturnType, ignoreRunning, (IpcCommsP sync), (override)); + MOCK_METHOD( + OsalReturnType, + waitForkRunning, + (IpcCommsP sync, std::optional timeout), + (override)); }; } // namespace score::mw::lifecycle::internal::osal From b9e489d5d5e510d6efdd7b107786564eba75e67a Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:09:03 +0100 Subject: [PATCH 08/17] Wrap semaphores --- .../src/process_group_manager/details/BUILD | 4 + .../details/process_launcher_UT.cpp | 99 ++++++++++++++++++- 2 files changed, 98 insertions(+), 5 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 cd76a5743..4e0630d50 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 @@ -406,6 +406,10 @@ lm_cc_test( "-Wl,--wrap=setrlimit", "-Wl,--wrap=getpid", "-Wl,--wrap=fcntl", + "-Wl,--wrap=sem_init", + "-Wl,--wrap=sem_destroy", + "-Wl,--wrap=sem_trywait", + "-Wl,--wrap=sem_post", ], linkstatic = True, deps = [ diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 5b45a56c4..ee9cbd42e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -47,6 +47,10 @@ class SyscallMock MOCK_METHOD(__pid_t, getpid, (), ()); MOCK_METHOD(int, fcntl, (int __fd, int __cmd), ()); MOCK_METHOD(int, setgroups, (size_t n, const gid_t* groups), ()); + MOCK_METHOD(int, sem_init, (sem_t * __sem, int __pshared, unsigned int __value), ()); + MOCK_METHOD(int, sem_destroy, (sem_t * __sem), ()); + MOCK_METHOD(int, sem_trywait, (sem_t * __sem), ()); + MOCK_METHOD(int, sem_post, (sem_t * __sem), ()); }; std::unique_ptr g_syscall_mock = nullptr; @@ -285,6 +289,58 @@ int __wrap_fcntl(int __fd, int __cmd, ...) return __real_fcntl(__fd, __cmd); } + +// wrap for sem_init +extern int __real_sem_init(sem_t* __sem, int __pshared, unsigned int __value); + +int __wrap_sem_init(sem_t* __sem, int __pshared, unsigned int __value) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sem_init(__sem, __pshared, __value); + } + + return __real_sem_init(__sem, __pshared, __value); +} + +// wrap for sem_destroy +extern int __real_sem_destroy(sem_t* __sem); + +int __wrap_sem_destroy(sem_t* __sem) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sem_destroy(__sem); + } + + return __real_sem_destroy(__sem); +} + +// wrap for sem_trywait +extern int __real_sem_trywait(sem_t* __sem); + +int __wrap_sem_trywait(sem_t* __sem) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sem_trywait(__sem); + } + + return __real_sem_trywait(__sem); +} + +// wrap for sem_post +extern int __real_sem_post(sem_t* __sem); + +int __wrap_sem_post(sem_t* __sem) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sem_post(__sem); + } + + return __real_sem_post(__sem); +} } namespace score::mw::lifecycle::internal::osal @@ -350,6 +406,14 @@ class ProcessLauncherTest : public ::testing::Test return shared; } + void UseRealSemaphores() + { + ON_CALL(*g_syscall_mock, sem_init).WillByDefault(Invoke(__real_sem_init)); + ON_CALL(*g_syscall_mock, sem_destroy).WillByDefault(Invoke(__real_sem_destroy)); + ON_CALL(*g_syscall_mock, sem_trywait).WillByDefault(Invoke(__real_sem_trywait)); + ON_CALL(*g_syscall_mock, sem_post).WillByDefault(Invoke(__real_sem_post)); + } + std::unique_ptr process_launcher; alignas(IpcCommsSync) std::array test_buffer; @@ -628,6 +692,16 @@ TEST_F(SetupCommsTest, getCommsFails) EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); } +TEST_F(SetupCommsTest, initSemaphoresFails) +{ + RecordProperty("Description", "Verify that if setting up the semaphores fails, startProcess fails"); + + StubShmObject(); + EXPECT_CALL(*g_syscall_mock, sem_init).WillRepeatedly(Return(-1)); + + EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); +} + class SetSchedulingAndSecurityTest : public StartProcessTest { protected: @@ -880,6 +954,7 @@ TEST_F(StartProcessTest, startProcessChildSuccess) EXPECT_CALL(*g_syscall_mock, fork).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, getpid).WillRepeatedly(Return(forked_pid)); EXPECT_CALL(*g_syscall_mock, fcntl).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, sem_init).Times(2).WillRepeatedly(Return(0)); EXPECT_CALL(*g_syscall_mock, setpgid(0, forked_pid)); EXPECT_CALL(*g_syscall_mock, sched_setscheduler(0, scheduler, _)).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, setuid(uid)).WillOnce(Return(0)); @@ -918,11 +993,26 @@ class ClientMethodsTest : public ProcessLauncherTest using namespace std::chrono_literals; +TEST_F(ProcessLauncherTest, ignoreRunningNoPost) +{ + RecordProperty("Description", "Verify that ignoreRunning correctly handles a failed semaphore post"); + + EXPECT_CALL(*g_syscall_mock, sem_init).WillRepeatedly(Return(0)); + EXPECT_CALL(*g_syscall_mock, sem_destroy).WillRepeatedly(Return(0)); + std::shared_ptr sync = GetInitialisedIpc(); + EXPECT_CALL(*g_syscall_mock, sem_post).WillOnce(SetErrnoAndReturn(EINVAL, -1)); + + EXPECT_EQ(process_launcher->waitForkRunning(sync, std::nullopt), OsalReturnType::kFail); +} + TEST_F(ProcessLauncherTest, ignoreRunningSuccess) { RecordProperty( - "Description", "Verify that waitForkRunning without a timeout posts on the reply semaphore without waiting"); + "Description", + "Verify that waitForkRunning without a timeout posts on the reply semaphore without waiting and returns a " + "success"); + UseRealSemaphores(); std::shared_ptr sync = GetInitialisedIpc(); OsalReturnType waitRes = OsalReturnType::kFail; @@ -951,23 +1041,20 @@ TEST_F(ProcessLauncherTest, kRunningSuccess) "Verify that waitForkRunning waits for a notification, posts a reply, and then waits for another notification " "before proceeding"); + UseRealSemaphores(); std::shared_ptr sync = GetInitialisedIpc(); OsalReturnType waitRes = OsalReturnType::kFail; OsalReturnType postRes = OsalReturnType::kFail; auto waiter = std::thread{[&waitRes, &postRes, sync]() { - std::cout << "start t" << std::endl; postRes = sync->send_sync_.post(); - std::cout << "posted" << std::endl; if (postRes == OsalReturnType::kSuccess) { waitRes = sync->reply_sync_.timedWait(5000ms); - std::cout << "waited" << std::endl; } if (waitRes == OsalReturnType::kSuccess) { postRes = sync->send_sync_.post(); - std::cout << "posted again" << std::endl; } }}; @@ -983,6 +1070,8 @@ TEST_F(ProcessLauncherTest, kRunningTimeout) "Description", "Verify that waitForkRunning returns a timeout failure if no notification is received within the timeout"); + UseRealSemaphores(); + std::shared_ptr sync = GetInitialisedIpc(); EXPECT_EQ(process_launcher->waitForkRunning(sync, 1ms), OsalReturnType::kTimeout); From 278bfb7f045e9690cb97525a0b8e1d01b00ade0b Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:18:55 +0100 Subject: [PATCH 09/17] Resolve all uninteresting call warnings --- .../details/process_launcher.cpp | 2 +- .../details/process_launcher_UT.cpp | 121 +++++++++++++----- 2 files changed, 90 insertions(+), 33 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp index 5307b98b0..f1ffdd642 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp @@ -67,7 +67,7 @@ void applyLimitOrDie(const int resource, const rlimit& limit, const std::string_ /// @brief Sets the limit if given a non-zero value, otherwise skips. /// @details The implementation should be async signal safe. /// @warning This will sysexit if the set is not succesful. -void setLimit(const int resource, const std::size_t amount, const std::string_view rlimit_name) noexcept +void setLimit(const int resource, const std::size_t amount, const std::string_view rlimit_name) noexcept(false) { if (amount == 0U) { diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index ee9cbd42e..7b5e8e25c 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" @@ -45,7 +46,7 @@ class SyscallMock MOCK_METHOD(int, setrlimit, (__rlimit_resource_t __resource, const struct rlimit* __rlimits), ()); MOCK_METHOD(int, setSecurityPolicy, (const char* policy), ()); MOCK_METHOD(__pid_t, getpid, (), ()); - MOCK_METHOD(int, fcntl, (int __fd, int __cmd), ()); + MOCK_METHOD(int, fcntl, (int __fd, int __cmd, void* arg), ()); MOCK_METHOD(int, setgroups, (size_t n, const gid_t* groups), ()); MOCK_METHOD(int, sem_init, (sem_t * __sem, int __pshared, unsigned int __value), ()); MOCK_METHOD(int, sem_destroy, (sem_t * __sem), ()); @@ -282,12 +283,19 @@ extern int __real_fcntl(int __fd, int __cmd, ...); int __wrap_fcntl(int __fd, int __cmd, ...) { + void* arg = nullptr; + va_list args; + + va_start(args, __cmd); + arg = va_arg(args, void*); + va_end(args); + if (g_syscall_mock) { - return g_syscall_mock->fcntl(__fd, __cmd); + return g_syscall_mock->fcntl(__fd, __cmd, arg); } - return __real_fcntl(__fd, __cmd); + return __real_fcntl(__fd, __cmd, arg); } // wrap for sem_init @@ -380,6 +388,9 @@ class ProcessLauncherTest : public ::testing::Test // Used by logging framework ON_CALL(*g_syscall_mock, access).WillByDefault(Invoke(__real_access)); + ON_CALL(*g_syscall_mock, fcntl).WillByDefault(Invoke(__real_fcntl)); + EXPECT_CALL(*g_syscall_mock, access).Times(AnyNumber()); + EXPECT_CALL(*g_syscall_mock, fcntl).Times(AnyNumber()); } void TearDown() override @@ -412,6 +423,10 @@ class ProcessLauncherTest : public ::testing::Test ON_CALL(*g_syscall_mock, sem_destroy).WillByDefault(Invoke(__real_sem_destroy)); ON_CALL(*g_syscall_mock, sem_trywait).WillByDefault(Invoke(__real_sem_trywait)); ON_CALL(*g_syscall_mock, sem_post).WillByDefault(Invoke(__real_sem_post)); + EXPECT_CALL(*g_syscall_mock, sem_init).Times(AtLeast(1)); + EXPECT_CALL(*g_syscall_mock, sem_destroy).Times(AtLeast(1)); + EXPECT_CALL(*g_syscall_mock, sem_trywait).Times(AnyNumber()); + EXPECT_CALL(*g_syscall_mock, sem_post).Times(AnyNumber()); } std::unique_ptr process_launcher; @@ -553,6 +568,11 @@ TEST_F(TerminationTest, forceTerminationInvalid) EXPECT_EQ(res, OsalReturnType::kFail); } +// The sysexit mock can throw this to interrupt execution +struct SysExitException +{ +}; + class StartProcessTest : public ProcessLauncherTest { protected: @@ -560,6 +580,8 @@ class StartProcessTest : public ProcessLauncherTest { ProcessLauncherTest::SetUp(); + EXPECT_CALL(*g_syscall_mock, getpid).Times(AnyNumber()).WillRepeatedly(Return(forked_pid)); + config_.name = "TestComponent"; config_.component_properties.binary_name = "TestProcess"; config_.component_properties.application_profile.application_type = configuration::ApplicationType::Native; @@ -589,11 +611,25 @@ class StartProcessTest : public ProcessLauncherTest EXPECT_CALL(*g_syscall_mock, fork).WillOnce(Return(0)); } + void ExpectSuccessfulSchedulingAndSecurity() + { + EXPECT_CALL(*g_syscall_mock, setpgid).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(*g_syscall_mock, sched_setscheduler).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgid).Times(AnyNumber()).WillRepeatedly(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid).Times(AnyNumber()).WillRepeatedly(Return(0)); + } + + void ExpectSuccessfulChdir() + { + EXPECT_CALL(*g_syscall_mock, chdir).Times(AnyNumber()).WillRepeatedly(Return(0)); + } + IpcCommsSync* StubShmObject() { const int fd = 123; void* data = static_cast(test_buffer.data()); EXPECT_CALL(*g_syscall_mock, shm_open).WillOnce(Return(fd)); + EXPECT_CALL(*g_syscall_mock, shm_unlink).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, mmap(_, _, _, _, fd, _)).WillOnce(Return(data)); EXPECT_CALL(*g_syscall_mock, ftruncate(fd, _)).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, munmap(data, sizeof(IpcCommsSync))).WillOnce(Return(0)); @@ -601,6 +637,15 @@ class StartProcessTest : public ProcessLauncherTest return static_cast(data); } + void ExpectSetupComms() + { + StubShmObject(); + + EXPECT_CALL(*g_syscall_mock, sem_init).Times(AnyNumber()).WillRepeatedly(Return(0)); + } + + const pid_t forked_pid = 23; + configuration::ComponentConfig config_ = {}; ProcessID pid_; IpcCommsP sync_; @@ -642,12 +687,12 @@ TEST_F(StartProcessTest, handleCommsFailsFnctl) config_.component_properties.application_profile.application_type = configuration::ApplicationType::Reporting; ExpectChildProcessStarts(); - StubShmObject(); + ExpectSetupComms(); EXPECT_CALL(*g_syscall_mock, fcntl).WillOnce(Return(-1)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } class SetupCommsTest : public StartProcessTest @@ -676,6 +721,7 @@ TEST_F(SetupCommsTest, ftruncateFails) RecordProperty("Description", "Verify that if truncating shared memory fails, startProcess fails"); EXPECT_CALL(*g_syscall_mock, shm_open).WillOnce(Return(123)); + EXPECT_CALL(*g_syscall_mock, shm_unlink).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, ftruncate).WillOnce(Return(-1)); EXPECT_EQ(process_launcher->startProcess(pid_, sync_, config_), OsalReturnType::kFail); @@ -686,6 +732,7 @@ TEST_F(SetupCommsTest, getCommsFails) RecordProperty("Description", "Verify that if truncating shared memory fails, startProcess fails"); EXPECT_CALL(*g_syscall_mock, shm_open).WillOnce(Return(123)); + EXPECT_CALL(*g_syscall_mock, shm_unlink).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, ftruncate).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, mmap).WillOnce(Return(MAP_FAILED)); @@ -709,8 +756,9 @@ class SetSchedulingAndSecurityTest : public StartProcessTest { StartProcessTest::SetUp(); - EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); - EXPECT_CALL(*g_syscall_mock, fork).WillOnce(Return(0)); // Test the child process + ExpectChildProcessStarts(); + ExpectSuccessfulChdir(); + EXPECT_CALL(*g_syscall_mock, execve).Times(AtMost(1)); } }; @@ -723,9 +771,9 @@ TEST_F(SetSchedulingAndSecurityTest, setpgidFails) EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } TEST_F(SetSchedulingAndSecurityTest, setgidFails) @@ -737,9 +785,9 @@ TEST_F(SetSchedulingAndSecurityTest, setgidFails) EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(-1)); EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } TEST_F(SetSchedulingAndSecurityTest, setuidFails) @@ -751,9 +799,9 @@ TEST_F(SetSchedulingAndSecurityTest, setuidFails) EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(-1)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } TEST_F(SetSchedulingAndSecurityTest, setschedFails) @@ -765,9 +813,9 @@ TEST_F(SetSchedulingAndSecurityTest, setschedFails) EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } TEST_F(SetSchedulingAndSecurityTest, setschedClampedUpper) @@ -820,21 +868,22 @@ TEST_F(SetSchedulingAndSecurityTest, setgroupsFails) EXPECT_CALL(*g_syscall_mock, sched_setscheduler).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, setgroups).WillOnce(Return(-1)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } TEST_F(StartProcessTest, chdirFails) { RecordProperty("Description", "Verify that the forked process exits if changing the working dir fails"); - EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + ExpectChildProcessStarts(); + ExpectSuccessfulSchedulingAndSecurity(); EXPECT_CALL(*g_syscall_mock, chdir).WillOnce(Return(-1)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } TEST_F(StartProcessTest, changeSecurityPolicyFails) @@ -843,12 +892,14 @@ TEST_F(StartProcessTest, changeSecurityPolicyFails) config_.deployment_config.sandbox.security_policy = "security"; - EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + ExpectChildProcessStarts(); + ExpectSuccessfulSchedulingAndSecurity(); + ExpectSuccessfulChdir(); EXPECT_CALL(*g_syscall_mock, setSecurityPolicy).WillOnce(Return(-1)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } TEST_F(StartProcessTest, setRLimitFails) @@ -857,12 +908,14 @@ TEST_F(StartProcessTest, setRLimitFails) config_.deployment_config.sandbox.max_cpu_usage = 500; - EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + ExpectChildProcessStarts(); + ExpectSuccessfulSchedulingAndSecurity(); + ExpectSuccessfulChdir(); EXPECT_CALL(*g_syscall_mock, setrlimit).WillOnce(Return(-1)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } TEST_F(StartProcessTest, setRLimitIgnore) @@ -872,8 +925,11 @@ TEST_F(StartProcessTest, setRLimitIgnore) config_.deployment_config.sandbox.max_cpu_usage = 0; config_.deployment_config.sandbox.max_memory_usage = 0; - EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + ExpectChildProcessStarts(); + ExpectSuccessfulSchedulingAndSecurity(); + ExpectSuccessfulChdir(); EXPECT_CALL(*g_syscall_mock, setrlimit).Times(0); + EXPECT_CALL(*g_syscall_mock, execve).Times(AtMost(1)); static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process } @@ -882,12 +938,14 @@ TEST_F(StartProcessTest, execveFails) { RecordProperty("Description", "Verify that the forked process exits if execve fails"); - EXPECT_CALL(*g_syscall_mock, access).WillOnce(Return(0)); + ExpectChildProcessStarts(); + ExpectSuccessfulSchedulingAndSecurity(); + ExpectSuccessfulChdir(); EXPECT_CALL(*g_syscall_mock, execve).WillOnce(Return(-1)); - EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)); + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); - static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } // Define a matcher that checks a null-terminated char** against a vector of strings @@ -925,7 +983,6 @@ TEST_F(StartProcessTest, startProcessChildSuccess) { RecordProperty("Description", "Verify that when startProcess succeeds, the forked process is configured correctly"); - const pid_t forked_pid = 23; const int scheduler = SCHED_FIFO; const int uid = 11; const int gid = 13; From 6915b5c960c8a756ca7cf32ba6931146c58d4299 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:39:27 +0100 Subject: [PATCH 10/17] Fixes after rebase --- .../process_group_manager/details/process_launcher_UT.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 7b5e8e25c..1ea4bd3b5 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -22,7 +22,7 @@ using namespace testing; -// NOLINTBEGIN - clang-tidy does not like syscalls :D +// NOLINTBEGIN - clang-tidy does not like syscalls class SyscallMock { @@ -585,7 +585,7 @@ class StartProcessTest : public ProcessLauncherTest config_.name = "TestComponent"; config_.component_properties.binary_name = "TestProcess"; config_.component_properties.application_profile.application_type = configuration::ApplicationType::Native; - config_.deployment_config.bin_dir = "/bin"; + config_.deployment_config.executable_path = "/bin/TestProcess"; config_.deployment_config.working_dir = "/tmp"; config_.deployment_config.sandbox.max_memory_usage = std::nullopt; config_.deployment_config.sandbox.max_cpu_usage = std::nullopt; @@ -991,7 +991,8 @@ TEST_F(StartProcessTest, startProcessChildSuccess) const std::uint32_t cpu_limit = 500; const std::string security_policy = "security"; const std::vector args_in = {"-c 2", "--argument yes"}; - const std::vector expected_launch_args = {"/bin/TestProcess", args_in[0], args_in[1]}; + const std::vector expected_launch_args = { + config_.deployment_config.executable_path, args_in[0], args_in[1]}; config_.component_properties.application_profile.application_type = configuration::ApplicationType::Reporting; config_.deployment_config.sandbox.scheduling_policy = scheduler; From a6166c0330dd1551858c0c4bcfca2e58cec8c811 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:14:59 +0100 Subject: [PATCH 11/17] Respond to review --- .../details/process_launcher.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp index f1ffdd642..5a36e0163 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp @@ -33,6 +33,7 @@ #include "score/mw/launch_manager/process_group_manager/iprocess.hpp" #include #include +#include #include #include #include @@ -94,12 +95,13 @@ void handleComms(score::mw::lifecycle::internal::osal::ChildProcessConfig& param // It must be ensured that sync_fd (f3) remains open depending on // the communication type. Flag FD_CLOEXEC is cleared conditionally to ensure that the // respective file descriptor remains open after the execve call. - SCORE_LANGUAGE_FUTURECPP_ASSERT_DBG_MESSAGE( - param.shared_block->comms_type_ != CommsType::kNoComms, - "This case means param.shared_block == nullptr and is expected to be handled above"); + assert( + param.shared_block->comms_type_ != CommsType::kNoComms && + "Invalid comms type, kNoComms is expected to be handled above"); - SCORE_LANGUAGE_FUTURECPP_ASSERT_DBG_MESSAGE( - param.shared_block->comms_type_ == CommsType::kReporting, "This is the only remaining comms type in use"); + assert( + param.shared_block->comms_type_ == CommsType::kReporting && + "Invalid comms type, a communicating process must be kReporting"); if (-1 == fcntl(IpcCommsSync::sync_fd, F_SETFD, 0)) { @@ -180,7 +182,7 @@ ProcessLauncher::startProcess(ProcessID& pid, IpcCommsP& block, const configurat block = nullptr; bool comms_result = true; - auto app_type = config.component_properties.application_profile.application_type; + const auto& app_type = config.component_properties.application_profile.application_type; if (app_type != configuration::ApplicationType::Native) { comms_result = setupComms(block, fd, config); From 758d91824cdf376fa31c9f842a09bbdf8873ce3a Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:19:36 +0100 Subject: [PATCH 12/17] Replace weird types --- .../details/process_launcher_UT.cpp | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 1ea4bd3b5..ba9ebff87 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -35,17 +35,17 @@ class SyscallMock MOCK_METHOD(int, shm_unlink, (const char*), ()); MOCK_METHOD(int, ftruncate, (int, off_t), ()); MOCK_METHOD(int, access, (const char*, int), ()); - MOCK_METHOD(void*, mmap, (void* __addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset), ()); + MOCK_METHOD(void*, mmap, (void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset), ()); MOCK_METHOD(int, munmap, (void* __addr, size_t __len), ()); MOCK_METHOD(void, sysexit, (int status), ()); - MOCK_METHOD(int, setpgid, (__pid_t __pid, __pid_t __pgid), ()); - MOCK_METHOD(int, setgid, (__gid_t __gid), ()); - MOCK_METHOD(int, setuid, (__uid_t __uid), ()); - MOCK_METHOD(int, sched_setscheduler, (__pid_t __pid, int __policy, const struct sched_param*), ()); + MOCK_METHOD(int, setpgid, (pid_t __pid, pid_t __pgid), ()); + MOCK_METHOD(int, setgid, (gid_t __gid), ()); + MOCK_METHOD(int, setuid, (uid_t __uid), ()); + MOCK_METHOD(int, sched_setscheduler, (pid_t __pid, int __policy, const struct sched_param*), ()); MOCK_METHOD(int, chdir, (const char* __path), ()); - MOCK_METHOD(int, setrlimit, (__rlimit_resource_t __resource, const struct rlimit* __rlimits), ()); + MOCK_METHOD(int, setrlimit, (int __resource, const struct rlimit* __rlimits), ()); MOCK_METHOD(int, setSecurityPolicy, (const char* policy), ()); - MOCK_METHOD(__pid_t, getpid, (), ()); + MOCK_METHOD(pid_t, getpid, (), ()); MOCK_METHOD(int, fcntl, (int __fd, int __cmd, void* arg), ()); MOCK_METHOD(int, setgroups, (size_t n, const gid_t* groups), ()); MOCK_METHOD(int, sem_init, (sem_t * __sem, int __pshared, unsigned int __value), ()); @@ -162,9 +162,9 @@ int __wrap_access(const char* name, int type) } // wrap for mmap -extern void* __real_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset); +extern void* __real_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset); -void* __wrap_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset) +void* __wrap_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset) { if (g_syscall_mock) { @@ -188,9 +188,9 @@ int __wrap_munmap(void* __addr, size_t __len) } // wrap for setpgid -extern int __real_setpgid(__pid_t __pid, __pid_t __pgid); +extern int __real_setpgid(pid_t __pid, pid_t __pgid); -int __wrap_setpgid(__pid_t __pid, __pid_t __pgid) +int __wrap_setpgid(pid_t __pid, pid_t __pgid) { if (g_syscall_mock) { @@ -201,9 +201,9 @@ int __wrap_setpgid(__pid_t __pid, __pid_t __pgid) } // wrap for setgid -extern int __real_setgid(__gid_t __gid); +extern int __real_setgid(gid_t __gid); -int __wrap_setgid(__gid_t __gid) +int __wrap_setgid(gid_t __gid) { if (g_syscall_mock) { @@ -214,9 +214,9 @@ int __wrap_setgid(__gid_t __gid) } // wrap for setuid -extern int __real_setuid(__uid_t __uid); +extern int __real_setuid(uid_t __uid); -int __wrap_setuid(__uid_t __uid) +int __wrap_setuid(uid_t __uid) { if (g_syscall_mock) { @@ -227,9 +227,9 @@ int __wrap_setuid(__uid_t __uid) } // wrap for sched_setscheduler -extern int __real_sched_setscheduler(__pid_t __pid, int __policy, const struct sched_param* __param); +extern int __real_sched_setscheduler(pid_t __pid, int __policy, const struct sched_param* __param); -int __wrap_sched_setscheduler(__pid_t __pid, int __policy, const struct sched_param* __param) +int __wrap_sched_setscheduler(pid_t __pid, int __policy, const struct sched_param* __param) { if (g_syscall_mock) { @@ -253,9 +253,9 @@ int __wrap_chdir(const char* __path) } // wrap for setrlimit -extern int __real_setrlimit(__rlimit_resource_t __resource, const struct rlimit* __rlimits); +extern int __real_setrlimit(int __resource, const struct rlimit* __rlimits); -int __wrap_setrlimit(__rlimit_resource_t __resource, const struct rlimit* __rlimits) +int __wrap_setrlimit(int __resource, const struct rlimit* __rlimits) { if (g_syscall_mock) { @@ -268,7 +268,7 @@ int __wrap_setrlimit(__rlimit_resource_t __resource, const struct rlimit* __rlim // wrap for getpid extern int __real_getpid(); -__pid_t __wrap_getpid() +pid_t __wrap_getpid() { if (g_syscall_mock) { From 0d8084e2f926425c3ab8fbd5e97d0bd2c0b0eebf Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Thu, 24 Sep 2026 09:05:33 +0100 Subject: [PATCH 13/17] Move mock to new file --- .../src/process_group_manager/details/BUILD | 13 + .../details/mock_proc_launch_syscalls.hpp | 367 ++++++++++++++++++ .../details/process_launcher_UT.cpp | 351 +---------------- 3 files changed, 381 insertions(+), 350 deletions(-) create mode 100644 score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.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 4e0630d50..6f267ffeb 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 @@ -384,6 +384,18 @@ cc_library( ], ) +cc_library( + name = "mock_proc_launch_syscalls", + testonly = True, + hdrs = ["mock_proc_launch_syscalls.hpp"], + include_prefix = "score/mw/launch_manager/process_group_manager/details", + strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details", + visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"], + deps = [ + "@googletest//:gtest_main", + ], +) + lm_cc_test( name = "process_launcher_UT", srcs = ["process_launcher_UT.cpp"], @@ -413,6 +425,7 @@ lm_cc_test( ], linkstatic = True, deps = [ + ":mock_proc_launch_syscalls", ":process_launcher", "@googletest//:gtest_main", ], diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp new file mode 100644 index 000000000..6b834a90d --- /dev/null +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp @@ -0,0 +1,367 @@ +/******************************************************************************** + * 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_PROC_LAUNCH_SYSCALLS +#define MOCK_PROC_LAUNCH_SYSCALLS + +// NOLINTBEGIN - clang-tidy does not like syscalls + +class SyscallMock +{ + public: + MOCK_METHOD(pid_t, fork, (), ()); + MOCK_METHOD(int, execve, (const char*, char* const[], char* const[]), ()); + MOCK_METHOD(int, kill, (pid_t, int), ()); + MOCK_METHOD(pid_t, wait, (int*), ()); + MOCK_METHOD(int, shm_open, (const char*, int, mode_t), ()); + MOCK_METHOD(int, shm_unlink, (const char*), ()); + MOCK_METHOD(int, ftruncate, (int, off_t), ()); + MOCK_METHOD(int, access, (const char*, int), ()); + MOCK_METHOD(void*, mmap, (void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset), ()); + MOCK_METHOD(int, munmap, (void* __addr, size_t __len), ()); + MOCK_METHOD(void, sysexit, (int status), ()); + MOCK_METHOD(int, setpgid, (pid_t __pid, pid_t __pgid), ()); + MOCK_METHOD(int, setgid, (gid_t __gid), ()); + MOCK_METHOD(int, setuid, (uid_t __uid), ()); + MOCK_METHOD(int, sched_setscheduler, (pid_t __pid, int __policy, const struct sched_param*), ()); + MOCK_METHOD(int, chdir, (const char* __path), ()); + MOCK_METHOD(int, setrlimit, (int __resource, const struct rlimit* __rlimits), ()); + MOCK_METHOD(int, setSecurityPolicy, (const char* policy), ()); + MOCK_METHOD(pid_t, getpid, (), ()); + MOCK_METHOD(int, fcntl, (int __fd, int __cmd, void* arg), ()); + MOCK_METHOD(int, setgroups, (size_t n, const gid_t* groups), ()); + MOCK_METHOD(int, sem_init, (sem_t * __sem, int __pshared, unsigned int __value), ()); + MOCK_METHOD(int, sem_destroy, (sem_t * __sem), ()); + MOCK_METHOD(int, sem_trywait, (sem_t * __sem), ()); + MOCK_METHOD(int, sem_post, (sem_t * __sem), ()); +}; + +std::unique_ptr g_syscall_mock = nullptr; + +extern "C" { +// wrap for fork +extern pid_t __real_fork(void); + +pid_t __wrap_fork(void) +{ + if (g_syscall_mock) + { + return g_syscall_mock->fork(); + } + + return __real_fork(); +} + +// wrap for execve +extern int __real_execve(const char*, char* const[], char* const[]); + +int __wrap_execve(const char* filename, char* const argv[], char* const envp[]) +{ + if (g_syscall_mock) + { + return g_syscall_mock->execve(filename, argv, envp); + } + + return __real_execve(filename, argv, envp); +} + +// wrap for kill +extern int __real_kill(pid_t, int); + +int __wrap_kill(pid_t pid, int sig) +{ + if (g_syscall_mock) + { + return g_syscall_mock->kill(pid, sig); + } + + return __real_kill(pid, sig); +} + +// wrap for wait +extern pid_t __real_wait(int*); + +pid_t __wrap_wait(int* status) +{ + if (g_syscall_mock) + { + return g_syscall_mock->wait(status); + } + + return __real_wait(status); +} + +// wrap for shm_open +extern int __real_shm_open(const char*, int, mode_t); + +int __wrap_shm_open(const char* name, int oflag, mode_t mode) +{ + if (g_syscall_mock) + { + return g_syscall_mock->shm_open(name, oflag, mode); + } + + return __real_shm_open(name, oflag, mode); +} + +// wrap for shm_unlink +extern int __real_shm_unlink(const char*); + +int __wrap_shm_unlink(const char* name) +{ + if (g_syscall_mock) + { + return g_syscall_mock->shm_unlink(name); + } + + return __real_shm_unlink(name); +} + +// wrap for ftruncate +extern int __real_ftruncate(int fildes, off_t length); + +int __wrap_ftruncate(int fildes, off_t length) +{ + if (g_syscall_mock) + { + return g_syscall_mock->ftruncate(fildes, length); + } + + return __real_ftruncate(fildes, length); +} + +// wrap for access +extern int __real_access(const char* name, int type); + +int __wrap_access(const char* name, int type) +{ + if (g_syscall_mock) + { + return g_syscall_mock->access(name, type); + } + + return __real_access(name, type); +} + +// wrap for mmap +extern void* __real_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset); + +void* __wrap_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset) +{ + if (g_syscall_mock) + { + return g_syscall_mock->mmap(__addr, __len, __prot, __flags, __fd, __offset); + } + + return __real_mmap(__addr, __len, __prot, __flags, __fd, __offset); +} + +// wrap for munmap +extern int __real_munmap(void* __addr, size_t __len); + +int __wrap_munmap(void* __addr, size_t __len) +{ + if (g_syscall_mock) + { + return g_syscall_mock->munmap(__addr, __len); + } + + return __real_munmap(__addr, __len); +} + +// wrap for setpgid +extern int __real_setpgid(pid_t __pid, pid_t __pgid); + +int __wrap_setpgid(pid_t __pid, pid_t __pgid) +{ + if (g_syscall_mock) + { + return g_syscall_mock->setpgid(__pid, __pgid); + } + + return __real_setpgid(__pid, __pgid); +} + +// wrap for setgid +extern int __real_setgid(gid_t __gid); + +int __wrap_setgid(gid_t __gid) +{ + if (g_syscall_mock) + { + return g_syscall_mock->setgid(__gid); + } + + return __real_setgid(__gid); +} + +// wrap for setuid +extern int __real_setuid(uid_t __uid); + +int __wrap_setuid(uid_t __uid) +{ + if (g_syscall_mock) + { + return g_syscall_mock->setuid(__uid); + } + + return __real_setuid(__uid); +} + +// wrap for sched_setscheduler +extern int __real_sched_setscheduler(pid_t __pid, int __policy, const struct sched_param* __param); + +int __wrap_sched_setscheduler(pid_t __pid, int __policy, const struct sched_param* __param) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sched_setscheduler(__pid, __policy, __param); + } + + return __real_sched_setscheduler(__pid, __policy, __param); +} + +// wrap for chdir +extern int __real_chdir(const char* __path); + +int __wrap_chdir(const char* __path) +{ + if (g_syscall_mock) + { + return g_syscall_mock->chdir(__path); + } + + return __real_chdir(__path); +} + +// wrap for setrlimit +extern int __real_setrlimit(int __resource, const struct rlimit* __rlimits); + +int __wrap_setrlimit(int __resource, const struct rlimit* __rlimits) +{ + if (g_syscall_mock) + { + return g_syscall_mock->setrlimit(__resource, __rlimits); + } + + return __real_setrlimit(__resource, __rlimits); +} + +// wrap for getpid +extern int __real_getpid(); + +pid_t __wrap_getpid() +{ + if (g_syscall_mock) + { + return g_syscall_mock->getpid(); + } + + return __real_getpid(); +} + +// wrap for fcntl +extern int __real_fcntl(int __fd, int __cmd, ...); + +int __wrap_fcntl(int __fd, int __cmd, ...) +{ + void* arg = nullptr; + va_list args; + + va_start(args, __cmd); + arg = va_arg(args, void*); + va_end(args); + + if (g_syscall_mock) + { + return g_syscall_mock->fcntl(__fd, __cmd, arg); + } + + return __real_fcntl(__fd, __cmd, arg); +} + +// wrap for sem_init +extern int __real_sem_init(sem_t* __sem, int __pshared, unsigned int __value); + +int __wrap_sem_init(sem_t* __sem, int __pshared, unsigned int __value) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sem_init(__sem, __pshared, __value); + } + + return __real_sem_init(__sem, __pshared, __value); +} + +// wrap for sem_destroy +extern int __real_sem_destroy(sem_t* __sem); + +int __wrap_sem_destroy(sem_t* __sem) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sem_destroy(__sem); + } + + return __real_sem_destroy(__sem); +} + +// wrap for sem_trywait +extern int __real_sem_trywait(sem_t* __sem); + +int __wrap_sem_trywait(sem_t* __sem) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sem_trywait(__sem); + } + + return __real_sem_trywait(__sem); +} + +// wrap for sem_post +extern int __real_sem_post(sem_t* __sem); + +int __wrap_sem_post(sem_t* __sem) +{ + if (g_syscall_mock) + { + return g_syscall_mock->sem_post(__sem); + } + + return __real_sem_post(__sem); +} +} + +namespace score::mw::lifecycle::internal::osal +{ +void sysexit(int status) +{ + g_syscall_mock->sysexit(status); +} + +int setSecurityPolicy(const char* policy) +{ + return g_syscall_mock->setSecurityPolicy(policy); +} + +int setgroups(size_t n, const gid_t* groups) +{ + return g_syscall_mock->setgroups(n, groups); +} + +} // namespace score::mw::lifecycle::internal::osal + +// NOLINTEND + +#endif diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index ba9ebff87..3dc4be169 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -18,360 +18,11 @@ #include #include +#include "score/mw/launch_manager/process_group_manager/details/mock_proc_launch_syscalls.hpp" #include "score/mw/launch_manager/process_group_manager/details/process_launcher.hpp" using namespace testing; -// NOLINTBEGIN - clang-tidy does not like syscalls - -class SyscallMock -{ - public: - MOCK_METHOD(pid_t, fork, (), ()); - MOCK_METHOD(int, execve, (const char*, char* const[], char* const[]), ()); - MOCK_METHOD(int, kill, (pid_t, int), ()); - MOCK_METHOD(pid_t, wait, (int*), ()); - MOCK_METHOD(int, shm_open, (const char*, int, mode_t), ()); - MOCK_METHOD(int, shm_unlink, (const char*), ()); - MOCK_METHOD(int, ftruncate, (int, off_t), ()); - MOCK_METHOD(int, access, (const char*, int), ()); - MOCK_METHOD(void*, mmap, (void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset), ()); - MOCK_METHOD(int, munmap, (void* __addr, size_t __len), ()); - MOCK_METHOD(void, sysexit, (int status), ()); - MOCK_METHOD(int, setpgid, (pid_t __pid, pid_t __pgid), ()); - MOCK_METHOD(int, setgid, (gid_t __gid), ()); - MOCK_METHOD(int, setuid, (uid_t __uid), ()); - MOCK_METHOD(int, sched_setscheduler, (pid_t __pid, int __policy, const struct sched_param*), ()); - MOCK_METHOD(int, chdir, (const char* __path), ()); - MOCK_METHOD(int, setrlimit, (int __resource, const struct rlimit* __rlimits), ()); - MOCK_METHOD(int, setSecurityPolicy, (const char* policy), ()); - MOCK_METHOD(pid_t, getpid, (), ()); - MOCK_METHOD(int, fcntl, (int __fd, int __cmd, void* arg), ()); - MOCK_METHOD(int, setgroups, (size_t n, const gid_t* groups), ()); - MOCK_METHOD(int, sem_init, (sem_t * __sem, int __pshared, unsigned int __value), ()); - MOCK_METHOD(int, sem_destroy, (sem_t * __sem), ()); - MOCK_METHOD(int, sem_trywait, (sem_t * __sem), ()); - MOCK_METHOD(int, sem_post, (sem_t * __sem), ()); -}; - -std::unique_ptr g_syscall_mock = nullptr; - -extern "C" { -// wrap for fork -extern pid_t __real_fork(void); - -pid_t __wrap_fork(void) -{ - if (g_syscall_mock) - { - return g_syscall_mock->fork(); - } - - return __real_fork(); -} - -// wrap for execve -extern int __real_execve(const char*, char* const[], char* const[]); - -int __wrap_execve(const char* filename, char* const argv[], char* const envp[]) -{ - if (g_syscall_mock) - { - return g_syscall_mock->execve(filename, argv, envp); - } - - return __real_execve(filename, argv, envp); -} - -// wrap for kill -extern int __real_kill(pid_t, int); - -int __wrap_kill(pid_t pid, int sig) -{ - if (g_syscall_mock) - { - return g_syscall_mock->kill(pid, sig); - } - - return __real_kill(pid, sig); -} - -// wrap for wait -extern pid_t __real_wait(int*); - -pid_t __wrap_wait(int* status) -{ - if (g_syscall_mock) - { - return g_syscall_mock->wait(status); - } - - return __real_wait(status); -} - -// wrap for shm_open -extern int __real_shm_open(const char*, int, mode_t); - -int __wrap_shm_open(const char* name, int oflag, mode_t mode) -{ - if (g_syscall_mock) - { - return g_syscall_mock->shm_open(name, oflag, mode); - } - - return __real_shm_open(name, oflag, mode); -} - -// wrap for shm_unlink -extern int __real_shm_unlink(const char*); - -int __wrap_shm_unlink(const char* name) -{ - if (g_syscall_mock) - { - return g_syscall_mock->shm_unlink(name); - } - - return __real_shm_unlink(name); -} - -// wrap for ftruncate -extern int __real_ftruncate(int fildes, off_t length); - -int __wrap_ftruncate(int fildes, off_t length) -{ - if (g_syscall_mock) - { - return g_syscall_mock->ftruncate(fildes, length); - } - - return __real_ftruncate(fildes, length); -} - -// wrap for access -extern int __real_access(const char* name, int type); - -int __wrap_access(const char* name, int type) -{ - if (g_syscall_mock) - { - return g_syscall_mock->access(name, type); - } - - return __real_access(name, type); -} - -// wrap for mmap -extern void* __real_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset); - -void* __wrap_mmap(void* __addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset) -{ - if (g_syscall_mock) - { - return g_syscall_mock->mmap(__addr, __len, __prot, __flags, __fd, __offset); - } - - return __real_mmap(__addr, __len, __prot, __flags, __fd, __offset); -} - -// wrap for munmap -extern int __real_munmap(void* __addr, size_t __len); - -int __wrap_munmap(void* __addr, size_t __len) -{ - if (g_syscall_mock) - { - return g_syscall_mock->munmap(__addr, __len); - } - - return __real_munmap(__addr, __len); -} - -// wrap for setpgid -extern int __real_setpgid(pid_t __pid, pid_t __pgid); - -int __wrap_setpgid(pid_t __pid, pid_t __pgid) -{ - if (g_syscall_mock) - { - return g_syscall_mock->setpgid(__pid, __pgid); - } - - return __real_setpgid(__pid, __pgid); -} - -// wrap for setgid -extern int __real_setgid(gid_t __gid); - -int __wrap_setgid(gid_t __gid) -{ - if (g_syscall_mock) - { - return g_syscall_mock->setgid(__gid); - } - - return __real_setgid(__gid); -} - -// wrap for setuid -extern int __real_setuid(uid_t __uid); - -int __wrap_setuid(uid_t __uid) -{ - if (g_syscall_mock) - { - return g_syscall_mock->setuid(__uid); - } - - return __real_setuid(__uid); -} - -// wrap for sched_setscheduler -extern int __real_sched_setscheduler(pid_t __pid, int __policy, const struct sched_param* __param); - -int __wrap_sched_setscheduler(pid_t __pid, int __policy, const struct sched_param* __param) -{ - if (g_syscall_mock) - { - return g_syscall_mock->sched_setscheduler(__pid, __policy, __param); - } - - return __real_sched_setscheduler(__pid, __policy, __param); -} - -// wrap for chdir -extern int __real_chdir(const char* __path); - -int __wrap_chdir(const char* __path) -{ - if (g_syscall_mock) - { - return g_syscall_mock->chdir(__path); - } - - return __real_chdir(__path); -} - -// wrap for setrlimit -extern int __real_setrlimit(int __resource, const struct rlimit* __rlimits); - -int __wrap_setrlimit(int __resource, const struct rlimit* __rlimits) -{ - if (g_syscall_mock) - { - return g_syscall_mock->setrlimit(__resource, __rlimits); - } - - return __real_setrlimit(__resource, __rlimits); -} - -// wrap for getpid -extern int __real_getpid(); - -pid_t __wrap_getpid() -{ - if (g_syscall_mock) - { - return g_syscall_mock->getpid(); - } - - return __real_getpid(); -} - -// wrap for fcntl -extern int __real_fcntl(int __fd, int __cmd, ...); - -int __wrap_fcntl(int __fd, int __cmd, ...) -{ - void* arg = nullptr; - va_list args; - - va_start(args, __cmd); - arg = va_arg(args, void*); - va_end(args); - - if (g_syscall_mock) - { - return g_syscall_mock->fcntl(__fd, __cmd, arg); - } - - return __real_fcntl(__fd, __cmd, arg); -} - -// wrap for sem_init -extern int __real_sem_init(sem_t* __sem, int __pshared, unsigned int __value); - -int __wrap_sem_init(sem_t* __sem, int __pshared, unsigned int __value) -{ - if (g_syscall_mock) - { - return g_syscall_mock->sem_init(__sem, __pshared, __value); - } - - return __real_sem_init(__sem, __pshared, __value); -} - -// wrap for sem_destroy -extern int __real_sem_destroy(sem_t* __sem); - -int __wrap_sem_destroy(sem_t* __sem) -{ - if (g_syscall_mock) - { - return g_syscall_mock->sem_destroy(__sem); - } - - return __real_sem_destroy(__sem); -} - -// wrap for sem_trywait -extern int __real_sem_trywait(sem_t* __sem); - -int __wrap_sem_trywait(sem_t* __sem) -{ - if (g_syscall_mock) - { - return g_syscall_mock->sem_trywait(__sem); - } - - return __real_sem_trywait(__sem); -} - -// wrap for sem_post -extern int __real_sem_post(sem_t* __sem); - -int __wrap_sem_post(sem_t* __sem) -{ - if (g_syscall_mock) - { - return g_syscall_mock->sem_post(__sem); - } - - return __real_sem_post(__sem); -} -} - -namespace score::mw::lifecycle::internal::osal -{ -void sysexit(int status) -{ - g_syscall_mock->sysexit(status); -} - -int setSecurityPolicy(const char* policy) -{ - return g_syscall_mock->setSecurityPolicy(policy); -} - -int setgroups(size_t n, const gid_t* groups) -{ - return g_syscall_mock->setgroups(n, groups); -} - -} // namespace score::mw::lifecycle::internal::osal - -// NOLINTEND - using namespace score::mw::lifecycle::internal::osal; using namespace score::mw::lifecycle::internal; From 9df5a75bab445049388da1c6e4e9db5e6407bd15 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:56:55 +0100 Subject: [PATCH 14/17] Add includes --- .../details/mock_proc_launch_syscalls.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp index 6b834a90d..b855977f1 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp @@ -14,6 +14,24 @@ #ifndef MOCK_PROC_LAUNCH_SYSCALLS #define MOCK_PROC_LAUNCH_SYSCALLS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + // NOLINTBEGIN - clang-tidy does not like syscalls class SyscallMock From 91c3d2b81ac11cbedcbb41bd65eb72dc9338a73f Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Thu, 24 Sep 2026 14:42:00 +0100 Subject: [PATCH 15/17] Remove duplicate deinit --- .../src/process_group_manager/details/process_launcher_UT.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 3dc4be169..0715a812b 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -61,8 +61,6 @@ class ProcessLauncherTest : public ::testing::Test EXPECT_EQ(sync->send_sync_.init(0, false), OsalReturnType::kSuccess); std::shared_ptr shared{sync, [](IpcCommsSync* ptr) { - EXPECT_EQ(ptr->reply_sync_.deinit(), OsalReturnType::kSuccess); - EXPECT_EQ(ptr->send_sync_.deinit(), OsalReturnType::kSuccess); }}; return shared; From b274e7a3e246de1b2eecd90f1b7071302a0cbe04 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Thu, 24 Sep 2026 15:05:47 +0100 Subject: [PATCH 16/17] Wrap msync --- .../daemon/src/process_group_manager/details/BUILD | 1 + .../details/mock_proc_launch_syscalls.hpp | 14 ++++++++++++++ .../details/process_launcher_UT.cpp | 3 +++ 3 files changed, 18 insertions(+) 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 6f267ffeb..914c0c70e 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 @@ -422,6 +422,7 @@ lm_cc_test( "-Wl,--wrap=sem_destroy", "-Wl,--wrap=sem_trywait", "-Wl,--wrap=sem_post", + "-Wl,--wrap=msync", ], linkstatic = True, deps = [ diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp index b855977f1..404c4b135 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp @@ -62,6 +62,7 @@ class SyscallMock MOCK_METHOD(int, sem_destroy, (sem_t * __sem), ()); MOCK_METHOD(int, sem_trywait, (sem_t * __sem), ()); MOCK_METHOD(int, sem_post, (sem_t * __sem), ()); + MOCK_METHOD(int, msync, (void* __addr, size_t __len, int __flags), ()); }; std::unique_ptr g_syscall_mock = nullptr; @@ -359,6 +360,19 @@ int __wrap_sem_post(sem_t* __sem) return __real_sem_post(__sem); } + +// wrap for msync +extern int __real_msync(void* __addr, size_t __len, int __flags); + +int __wrap_msync(void* __addr, size_t __len, int __flags) +{ + if (g_syscall_mock) + { + return g_syscall_mock->msync(__addr, __len, __flags); + } + + return __real_msync(__addr, __len, __flags); +} } namespace score::mw::lifecycle::internal::osal diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 0715a812b..bbdb5ed25 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -59,6 +59,9 @@ class ProcessLauncherTest : public ::testing::Test sync->pid_ = 0; EXPECT_EQ(sync->reply_sync_.init(0, false), OsalReturnType::kSuccess); EXPECT_EQ(sync->send_sync_.init(0, false), OsalReturnType::kSuccess); + // Real msync calls would treat this fake shared memory as invalid because it isn't page aligned. + ON_CALL(*g_syscall_mock, msync(sync, _, _)).WillByDefault(Return(0)); + EXPECT_CALL(*g_syscall_mock, msync).Times(AnyNumber()); std::shared_ptr shared{sync, [](IpcCommsSync* ptr) { }}; From 35d985414c84e55486ee4142087b2c2f3079fef5 Mon Sep 17 00:00:00 2001 From: William Roebuck <244554584+WilliamRoebuck@users.noreply.github.com> Date: Thu, 24 Sep 2026 17:13:49 +0100 Subject: [PATCH 17/17] Mock semaphores --- .../details/process_launcher_UT.cpp | 49 +++++-------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index bbdb5ed25..7cc568f2e 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -69,16 +69,10 @@ class ProcessLauncherTest : public ::testing::Test return shared; } - void UseRealSemaphores() + void ExpectSemaphoreLifecycle() { - ON_CALL(*g_syscall_mock, sem_init).WillByDefault(Invoke(__real_sem_init)); - ON_CALL(*g_syscall_mock, sem_destroy).WillByDefault(Invoke(__real_sem_destroy)); - ON_CALL(*g_syscall_mock, sem_trywait).WillByDefault(Invoke(__real_sem_trywait)); - ON_CALL(*g_syscall_mock, sem_post).WillByDefault(Invoke(__real_sem_post)); - EXPECT_CALL(*g_syscall_mock, sem_init).Times(AtLeast(1)); - EXPECT_CALL(*g_syscall_mock, sem_destroy).Times(AtLeast(1)); - EXPECT_CALL(*g_syscall_mock, sem_trywait).Times(AnyNumber()); - EXPECT_CALL(*g_syscall_mock, sem_post).Times(AnyNumber()); + EXPECT_CALL(*g_syscall_mock, sem_init).Times(AtLeast(1)).WillRepeatedly(Return(0)); + EXPECT_CALL(*g_syscall_mock, sem_destroy).Times(AtLeast(1)).WillRepeatedly(Return(0)); } std::unique_ptr process_launcher; @@ -722,17 +716,11 @@ TEST_F(ProcessLauncherTest, ignoreRunningSuccess) "Verify that waitForkRunning without a timeout posts on the reply semaphore without waiting and returns a " "success"); - UseRealSemaphores(); + ExpectSemaphoreLifecycle(); std::shared_ptr sync = GetInitialisedIpc(); - OsalReturnType waitRes = OsalReturnType::kFail; - - auto waiter = std::thread{[&waitRes, &sync]() { - waitRes = sync->reply_sync_.timedWait(5000ms); - }}; + EXPECT_CALL(*g_syscall_mock, sem_post).Times(1); EXPECT_EQ(process_launcher->waitForkRunning(sync, std::nullopt), OsalReturnType::kSuccess); - waiter.join(); - EXPECT_EQ(waitRes, OsalReturnType::kSuccess); } TEST_F(ProcessLauncherTest, kRunningNoSync) @@ -751,27 +739,14 @@ TEST_F(ProcessLauncherTest, kRunningSuccess) "Verify that waitForkRunning waits for a notification, posts a reply, and then waits for another notification " "before proceeding"); - UseRealSemaphores(); + ExpectSemaphoreLifecycle(); std::shared_ptr sync = GetInitialisedIpc(); - OsalReturnType waitRes = OsalReturnType::kFail; - OsalReturnType postRes = OsalReturnType::kFail; - auto waiter = std::thread{[&waitRes, &postRes, sync]() { - postRes = sync->send_sync_.post(); - if (postRes == OsalReturnType::kSuccess) - { - waitRes = sync->reply_sync_.timedWait(5000ms); - } - if (waitRes == OsalReturnType::kSuccess) - { - postRes = sync->send_sync_.post(); - } - }}; + Expectation waits = EXPECT_CALL(*g_syscall_mock, sem_trywait).WillOnce(Return(0)); + Expectation posts = EXPECT_CALL(*g_syscall_mock, sem_post).After(waits).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, sem_trywait).After(posts).WillOnce(Return(0)); - EXPECT_EQ(process_launcher->waitForkRunning(sync, 5000ms), OsalReturnType::kSuccess); - waiter.join(); - ASSERT_EQ(postRes, OsalReturnType::kSuccess) << "Posting on the semaphore failed (test problem)"; - EXPECT_EQ(waitRes, OsalReturnType::kSuccess); + EXPECT_EQ(process_launcher->waitForkRunning(sync, 1ms), OsalReturnType::kSuccess); } TEST_F(ProcessLauncherTest, kRunningTimeout) @@ -780,9 +755,11 @@ TEST_F(ProcessLauncherTest, kRunningTimeout) "Description", "Verify that waitForkRunning returns a timeout failure if no notification is received within the timeout"); - UseRealSemaphores(); + ExpectSemaphoreLifecycle(); std::shared_ptr sync = GetInitialisedIpc(); + EXPECT_CALL(*g_syscall_mock, sem_trywait).WillRepeatedly(SetErrnoAndReturn(EAGAIN, -1)); + EXPECT_CALL(*g_syscall_mock, sem_post).Times(1); // Posts even if the wait fails... EXPECT_EQ(process_launcher->waitForkRunning(sync, 1ms), OsalReturnType::kTimeout); }