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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions score/launch_manager/src/daemon/src/osal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
load("@rules_cc//cc:defs.bzl", "cc_library")
load("//tests/utils/bazel:unit_test.bzl", "lm_cc_test")

cc_library(
name = "return_types",
Expand All @@ -33,6 +34,70 @@ cc_library(
deps = [":return_types"],
)

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",
strip_include_prefix = "/score/launch_manager/src/daemon/src/osal",
visibility = ["//score:__subpackages__"],
deps = [
":ifile_waiter",
":return_types",
"//score/launch_manager/src/daemon/src/configuration:component_config",
"@score_baselibs//score/language/safecpp/string_view:zstring_view",
"@score_baselibs//score/os:errno",
"@score_baselibs//score/os:stat",
"@score_baselibs//score/result:error",
],
)

lm_cc_test(
name = "wait_for_file_UT",
srcs = ["details/posix/wait_for_file_UT.cpp"],
deps = [
":wait_for_file",
"//score/launch_manager/src/daemon/src/configuration:component_config",
"@googletest//:gtest_main",
"@score_baselibs//score/language/safecpp/string_view:zstring_view",
"@score_baselibs//score/os:errno",
"@score_baselibs//score/os:stat",
"@score_baselibs//score/os/mocklib:stat_mock",
],
)

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",
visibility = ["//score:__subpackages__"],
deps = [
":return_types",
"//score/launch_manager/src/daemon/src/configuration:component_config",
"@score_baselibs//score/language/safecpp/string_view:zstring_view",
],
)

cc_library(
name = "mock_ifile_waiter",
testonly = True,
hdrs = ["mock_ifile_waiter.hpp"],
include_prefix = "score/mw/launch_manager/osal",
strip_include_prefix = "/score/launch_manager/src/daemon/src/osal",
visibility = ["//score:__subpackages__"],
deps = [
":ifile_waiter",
"@googletest//:gtest_main",
],
)

cc_library(
name = "sys_exit",
srcs = ["details/posix/sys_exit.cpp"],
Expand Down Expand Up @@ -127,5 +192,6 @@ cc_library(
":set_affinity",
":set_groups",
":sys_exit",
":wait_for_file",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/********************************************************************************
* 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 <algorithm>
#include <chrono>
#include <string_view>
#include <thread>

#include "score/os/errno.h"
#include "score/os/stat.h"
#include "score/result/error_code.h"

#include "score/mw/launch_manager/osal/wait_for_file.hpp"

namespace score::mw::lifecycle::internal::osal
{

OsalReturnType FileWaiter::waitForFile(
score::safecpp::zstring_view path,
configuration::FileExistenceState condition,
std::chrono::milliseconds timeout,
std::chrono::milliseconds poll_interval,
const score::cpp::stop_token& stop_token) const
{
// note: QNX has a wait_for API, however using the API call we wouldn't be
// able to check the stop_token between stat calls.

const bool wait_for_existence = (condition == configuration::FileExistenceState::Exists);
const auto deadline = std::chrono::steady_clock::now() + timeout;

while (true)
{
if (stop_token.stop_requested())
{
return OsalReturnType::kFail;
}

score::os::StatBuffer info{};

const auto result = stat_os_.stat(path.data(), info);
if (result.has_value())
{
if (wait_for_existence)
{
return OsalReturnType::kSuccess;
}
}
else
{
switch (result.error().GetOsDependentErrorCode())
{
case ENOENT:
Comment thread
MaciejKaszynski marked this conversation as resolved.
// treat file or dir not existing as the same
[[fallthrough]];
case ENOTDIR:
if (!wait_for_existence)
{
return OsalReturnType::kSuccess;
}
break;
case EINTR:
break; // retry
default:
return OsalReturnType::kFail;
}
}

const auto now = std::chrono::steady_clock::now();
if (now >= deadline)
{
return OsalReturnType::kTimeout;
}

// never sleep past the deadline
const auto remaining = deadline - now;
std::this_thread::sleep_for(std::min<std::chrono::steady_clock::duration>(poll_interval, remaining));
}
}

} // namespace score::mw::lifecycle::internal::osal
Comment thread
MaciejKaszynski marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/********************************************************************************
* 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 <gmock/gmock.h>
#include <gtest/gtest.h>

#include "score/os/errno.h"
#include "score/os/mocklib/stat_mock.h"

#include <cerrno>
#include <cstdint>

#include <chrono>

#include <score/stop_token.hpp>

#include "score/mw/launch_manager/osal/wait_for_file.hpp"

using score::mw::lifecycle::internal::configuration::FileExistenceState;
using score::mw::lifecycle::internal::osal::FileWaiter;
using score::mw::lifecycle::internal::osal::OsalReturnType;
using ::testing::_;

namespace
{

constexpr std::chrono::milliseconds kPollInterval{1U};
constexpr std::chrono::milliseconds kWaitTimeout{2U};

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

TEST_F(WaitForFileTest, FileExists)
{
RecordProperty(
"Description", "Verify that using FileExistenceState::Exists will return sucess if that stat returns success");

score::os::StatMock mock{};
EXPECT_CALL(mock, stat(_, _, true)).WillOnce(testing::Return(score::cpp::expected_blank<score::os::Error>{}));
EXPECT_EQ(
FileWaiter{mock}.waitForFile(
"/some/path", FileExistenceState::Exists, kWaitTimeout, kPollInterval, score::cpp::stop_token{}),
OsalReturnType::kSuccess);
}

TEST_F(WaitForFileTest, FileNotExisting)
{
RecordProperty(
"Description",
"Verify that using FileExistenceState::NotExisting will return sucess if that stat returns ENOTDIR");

score::os::StatMock mock{};
EXPECT_CALL(mock, stat(_, _, true))
.WillOnce(testing::Return(score::cpp::make_unexpected(score::os::Error::createFromErrno(ENOTDIR))));
EXPECT_EQ(
FileWaiter{mock}.waitForFile(
"/some/path", FileExistenceState::NotExisting, kWaitTimeout, kPollInterval, score::cpp::stop_token{}),
OsalReturnType::kSuccess);
}

TEST_F(WaitForFileTest, Timeout)
{
RecordProperty("Description", "Verify that if an error is repeatedly given then the timeout fires.");

score::os::StatMock mock{};
EXPECT_CALL(mock, stat(_, _, true))
.WillRepeatedly(testing::Return(score::cpp::make_unexpected(score::os::Error::createFromErrno(EINTR))));
EXPECT_EQ(
FileWaiter{mock}.waitForFile(
"/some/path", FileExistenceState::Exists, kWaitTimeout, kPollInterval, score::cpp::stop_token{}),
OsalReturnType::kTimeout);
}

TEST_F(WaitForFileTest, Error)
{
RecordProperty("Description", "Verify if a unexpected error is recieved from the state call the wait will fail.");

score::os::StatMock mock{};
EXPECT_CALL(mock, stat(_, _, true))
.WillRepeatedly(testing::Return(score::cpp::make_unexpected(score::os::Error::createFromErrno(EBADF))));
EXPECT_EQ(
FileWaiter{mock}.waitForFile(
"/some/path", FileExistenceState::Exists, kWaitTimeout, kPollInterval, score::cpp::stop_token{}),
OsalReturnType::kFail);
}

TEST_F(WaitForFileTest, NonNullTermindPathFails)
{
RecordProperty(
"Description", "Verify if using FileExistenceState::Exists the stat is re-polled after the interval.");

score::os::StatMock mock{};
EXPECT_CALL(mock, stat(_, _, true))
.WillOnce(testing::Return(score::cpp::make_unexpected(score::os::Error::createFromErrno(ENOTDIR))))
.WillOnce(testing::Return(score::cpp::expected_blank<score::os::Error>{}));

EXPECT_EQ(
FileWaiter{mock}.waitForFile(
"/some/path", FileExistenceState::Exists, kWaitTimeout, kPollInterval, score::cpp::stop_token{}),
OsalReturnType::kSuccess);
}

TEST_F(WaitForFileTest, StopRequested)
{
RecordProperty("Description", "Verify that a requested stop causes an early kFail return.");

score::os::StatMock mock{};
score::cpp::stop_source stop_source{};
static_cast<void>(stop_source.request_stop());

EXPECT_CALL(mock, stat(_, _, true)).Times(0);
EXPECT_EQ(
FileWaiter{mock}.waitForFile(
"/some/path", FileExistenceState::Exists, kWaitTimeout, kPollInterval, stop_source.get_token()),
OsalReturnType::kFail);
}

TEST_F(WaitForFileTest, FileNotExistingViaEnoent)
{
RecordProperty(
"Description", "Verify that using FileExistenceState::NotExisting will return success if stat returns ENOENT.");

score::os::StatMock mock{};
EXPECT_CALL(mock, stat(_, _, true))
.WillOnce(testing::Return(score::cpp::make_unexpected(score::os::Error::createFromErrno(ENOENT))));
EXPECT_EQ(
FileWaiter{mock}.waitForFile(
"/some/path", FileExistenceState::NotExisting, kWaitTimeout, kPollInterval, score::cpp::stop_token{}),
OsalReturnType::kSuccess);
}

TEST_F(WaitForFileTest, FileExistsWhileWaitingForNotExisting)
{
RecordProperty(
"Description",
"Verify that if FileExistenceState::NotExisting is requested but stat still succeeds, the wait "
"is re-polled instead of returning immediately.");

score::os::StatMock mock{};
EXPECT_CALL(mock, stat(_, _, true))
.WillOnce(testing::Return(score::cpp::expected_blank<score::os::Error>{}))
.WillOnce(testing::Return(score::cpp::make_unexpected(score::os::Error::createFromErrno(ENOENT))));

EXPECT_EQ(
FileWaiter{mock}.waitForFile(
"/some/path", FileExistenceState::NotExisting, kWaitTimeout, kPollInterval, score::cpp::stop_token{}),
OsalReturnType::kSuccess);
}

} // namespace
45 changes: 45 additions & 0 deletions score/launch_manager/src/daemon/src/osal/ifile_waiter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/********************************************************************************
* 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 OSAL_IFILE_WAITER_HPP_INCLUDED
#define OSAL_IFILE_WAITER_HPP_INCLUDED

#include <score/stop_token.hpp>

#include "score/language/safecpp/string_view/zstring_view.h"
#include "score/mw/launch_manager/configuration/component_config.hpp"
#include <chrono>

#include "return_types.hpp"

namespace score::mw::lifecycle::internal::osal
{

/// @brief Abstraction over wait_for_file() for mocking.
class IFileWaiter
{
public:
virtual ~IFileWaiter() = default;

/// @see wait_for_file() for more info.
virtual OsalReturnType waitForFile(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make sense to split this into two methods:

waitForFileExistence
waitForFileRemoval

?

The list of arguments is getting quite long.
I wonder if it would make sense to configure the poll_interval in the constructor?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, the implementation is 90% the same, and the difference between waiting for existence or deletion is only one if, which I think is easy enough to read so don't think making a separate private method makes sense either

                    if (!wait_for_existence)
                    {
                        return OsalReturnType::kSuccess;
                    }

score::safecpp::zstring_view path,
configuration::FileExistenceState condition,
std::chrono::milliseconds timeout,
std::chrono::milliseconds poll_interval,
const score::cpp::stop_token& stop_token) const = 0;
};

} // namespace score::mw::lifecycle::internal::osal

#endif // OSAL_IFILE_WAITER_HPP_INCLUDED
Loading
Loading