-
Notifications
You must be signed in to change notification settings - Fork 32
Adding wait for file ready condition #500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d574fc5
acfdeaf
7a1acd8
3c6e734
2a9fdfe
f3abdfa
100c965
af40eab
f4673a4
97ee19a
89f113e
a3cece0
e96b615
47d3fac
6883f7b
d94d64e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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: | ||
| // 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 | ||
|
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 |
| 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( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does it make sense to split this into two methods: waitForFileExistence ? The list of arguments is getting quite long.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
Uh oh!
There was an error while loading. Please reload this page.