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
37 changes: 36 additions & 1 deletion score/launch_manager/src/daemon/src/osal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
load("@rules_cc//cc:defs.bzl", "cc_library")
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")

cc_library(
name = "return_types",
Expand Down Expand Up @@ -109,6 +109,41 @@ cc_library(
],
)

cc_library(
name = "inotify_range",
hdrs = [
"inotify_range.hpp",
],
include_prefix = "score/mw/launch_manager/osal",
strip_include_prefix = "/score/launch_manager/src/daemon/src/osal",
visibility = [
"//score:__subpackages__",
"//tests:__subpackages__",
],
deps = [
"//score/launch_manager/src/daemon/src/common:log",
"@score_baselibs//score/language/futurecpp",
"@score_baselibs//score/language/safecpp/string_view:zstring_view",
"@score_baselibs//score/os/utils/inotify:inotify_event",
"@score_baselibs//score/os/utils/inotify:inotify_instance",
"@score_baselibs//score/os/utils/inotify:inotify_instance_impl",
"@score_baselibs//score/os/utils/inotify:inotify_watch_descriptor",
"@score_baselibs//score/result",
],
)

cc_test(
name = "inotify_range_UT",
srcs = [
"inotify_range_UT.cpp",
],
deps = [
":inotify_range",
"@googletest//:gtest_main",
"@score_baselibs//score/os/utils/inotify:inotify_instance_mock",
],
)

cc_library(
name = "osal",
visibility = ["//score:__subpackages__"],
Expand Down
168 changes: 168 additions & 0 deletions score/launch_manager/src/daemon/src/osal/inotify_range.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/********************************************************************************
* 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 INOTIFY_RANGE_HPP_
#define INOTIFY_RANGE_HPP_

#include <cstddef>
#include <iterator>
#include <memory>
#include <utility>

#include "score/assert.hpp"
#include "score/os/utils/inotify/inotify_event.h"
#include "score/os/utils/inotify/inotify_instance.h"

namespace score::mw::lifecycle
{

/// @brief A wrapper around score::os::InotiyInstance to get iterator syntax.
class INotifyRange
{
public:
explicit INotifyRange(std::unique_ptr<score::os::InotifyInstance> instance) noexcept
: instance_(std::move(instance))
{
}

~INotifyRange() = default;

INotifyRange(INotifyRange&& other) noexcept = default;
INotifyRange& operator=(INotifyRange&& other) noexcept = default;

INotifyRange(const INotifyRange& other) = delete;
INotifyRange& operator=(const INotifyRange& other) = delete;

/// @brief The iterator giving InotifyEvents.
/// @warning The iterator's lifetime must not exceed the `INotifyRange` it
/// was obtained from.
class iterator
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = score::os::InotifyEvent;
using difference_type = std::ptrdiff_t;
using pointer = const score::os::InotifyEvent*;
using reference = const score::os::InotifyEvent&;

explicit iterator(score::os::InotifyInstance* instance = nullptr) noexcept : instance_(instance)
{
if ((instance_ != nullptr) && !advance())
{
instance_ = nullptr;
}
}

[[nodiscard]] reference operator*() const
{
return events_[event_index_];
}

[[nodiscard]] pointer operator->() const
{
return &events_[event_index_];
}

iterator& operator++()
{
// we are end iterator
if (instance_ == nullptr)
{
return *this;
}

// become end iterator
if (!advance())
{
instance_ = nullptr;
}

return *this;
}

iterator operator++(int)
{
auto tmp = *this;
++(*this);
return tmp;
}

[[nodiscard]] bool operator==(const iterator& other) const
{
return instance_ == other.instance_;
}

[[nodiscard]] bool operator!=(const iterator& other) const
{
return !(*this == other);
}

private:
[[nodiscard]] bool advance()
{

SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(
instance_ != nullptr, "The parent INotifyRange must have been deleted, documented as UB!");

const bool events_in_internal_buffer = event_index_ + 1U < events_.size();
if (events_in_internal_buffer)
{
++event_index_;
return true;
}

auto result = instance_->Read();
if (!result.has_value())
{
return false;
}

events_ = std::move(result.value());

if (events_.empty())
{
return false;
}

event_index_ = 0U;
return true;
}

public:
/// @brief The underlying `InotifyInstance` instance.
score::os::InotifyInstance* instance_{nullptr};

/// @brief Internal buffer of events.
score::cpp::static_vector<score::os::InotifyEvent, score::os::InotifyInstance::max_events> events_{};

/// @brief Index to the next event to give from the internal buffer.
std::size_t event_index_{0U};
};

[[nodiscard]] iterator begin() noexcept
{
return iterator{instance_.get()};
}

[[nodiscard]] iterator end() noexcept
{
return iterator{nullptr};
}

private:
std::unique_ptr<score::os::InotifyInstance> instance_;
};

} // namespace score::mw::lifecycle

#endif // INOTIFY_RANGE_HPP_
110 changes: 110 additions & 0 deletions score/launch_manager/src/daemon/src/osal/inotify_range_UT.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/********************************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
#include "score/mw/launch_manager/osal/inotify_range.hpp"
#include "score/os/utils/inotify/inotify_instance_mock.h"
#include <gtest/gtest.h>
#include <memory>

namespace score::mw::lifecycle::testing
{

using score::os::Error;
using score::os::MakeFakeEvent;
using ::testing::Return;

using ReadRetT =
score::cpp::expected<score::cpp::static_vector<score::os::InotifyEvent, score::os::InotifyInstance::max_events>,
Error>;

using namespace std::string_view_literals;

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

TEST_F(INotifyRangeUT, NormalUsage)
{
/// Given we Read() one event
ReadRetT out{};
out->push_back(MakeFakeEvent(1,1,1, "hello"));
ReadRetT empty{};
auto mock_ptr = std::make_unique<score::os::InotifyInstanceMock>();
EXPECT_CALL(*mock_ptr, Read()).WillOnce(Return(out)).WillOnce(Return(empty));

INotifyRange range{std::move(mock_ptr)};

/// Expect the first value will be given.
auto iterator = range.begin();
auto event = *iterator;
EXPECT_EQ(event.GetCookie(), 1);

/// Expect incrementing will give you an end iterator.
iterator++;
EXPECT_EQ(iterator, range.end());
}

TEST_F(INotifyRangeUT, AdvanceWithinBufferedEvents)
{
/// Given we Read() 2 events, then 0 events
ReadRetT out{};
out->push_back(MakeFakeEvent(1, 1, 10, "first"));
out->push_back(MakeFakeEvent(2, 1, 20, "second"));
ReadRetT empty{};
auto mock_ptr = std::make_unique<score::os::InotifyInstanceMock>();
EXPECT_CALL(*mock_ptr, Read()).WillOnce(Return(out)).WillOnce(Return(empty));

INotifyRange range{std::move(mock_ptr)};

/// Expect that the first value will equal to the first event.
auto iterator = range.begin();
EXPECT_EQ(iterator->GetCookie(), 10);

/// Expect that the second value will equal to the second event.
++iterator;
EXPECT_EQ(iterator->GetCookie(), 20);

/// Expect that incrementing will give you an end iterator.
++iterator;
EXPECT_EQ(iterator, range.end());
}

TEST_F(INotifyRangeUT, AdvanceEndIterator)
{
auto mock_ptr = std::make_unique<score::os::InotifyInstanceMock>();
INotifyRange range{std::move(mock_ptr)};

/// Expect that incrementing end iterator still equals to an end iterator.
auto iterator = range.end();
iterator++;
EXPECT_EQ(iterator, range.end());
}

TEST_F(INotifyRangeUT, AdvanceError)
{
/// Given we get an Error from the Read().
auto mock_ptr = std::make_unique<score::os::InotifyInstanceMock>();
EXPECT_CALL(*mock_ptr, Read()).WillOnce(Return(score::cpp::unexpected(score::os::Error::createFromErrno(EINVAL))));

INotifyRange range{std::move(mock_ptr)};

/// Expect that the given iterator is empty.
EXPECT_EQ(range.begin(), range.end());
}

} // namespace score::mw::lifecycle::testing
Loading