-
Notifications
You must be signed in to change notification settings - Fork 32
Add rt_running_when_process_exits integration test #466
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
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 |
|---|---|---|
|
|
@@ -51,15 +51,23 @@ ProcessInfoNode::ProcessInfoNode( | |
|
|
||
| IComponent::RequestResult ProcessInfoNode::tryReportCompletion(score::mw::lifecycle::ProcessState new_state) | ||
| { | ||
| if (new_state == ProcessState::kFailed) | ||
| { | ||
| // Didn't reach running or startup | ||
| return tryReportError(ComponentError::kErrorBeforeReady); | ||
| } | ||
|
|
||
| ProcessState desired_state{}; | ||
| bool has_process_state_condition = false; | ||
|
|
||
| const auto& ready_condition = config_.component_properties.ready_condition; | ||
|
|
||
| std::visit( | ||
| [&desired_state](auto&& arg) { | ||
| [&desired_state, &has_process_state_condition](auto&& arg) { | ||
| using ReadyCondT = std::decay_t<decltype(arg)>; | ||
| if constexpr (std::is_same_v<ReadyCondT, configuration::ProcessState>) | ||
| { | ||
| has_process_state_condition = true; | ||
| switch (arg) | ||
| { | ||
| case configuration::ProcessState::Running: | ||
|
|
@@ -73,12 +81,9 @@ IComponent::RequestResult ProcessInfoNode::tryReportCompletion(score::mw::lifecy | |
| }, | ||
| ready_condition); | ||
|
|
||
| if (new_state == ProcessState::kFailed) | ||
| { | ||
| // Didn't reach running or startup | ||
| return tryReportError(ComponentError::kErrorBeforeReady); | ||
| } | ||
| if (new_state == desired_state) | ||
| // Reaching the desired state or beyond satisfies the ready condition: a self-terminating process | ||
| // may already have exited (kTerminated) by the time completion is reported. | ||
| if (has_process_state_condition && new_state >= desired_state) | ||
| { | ||
| return tryReportSuccess(); | ||
| } | ||
|
|
@@ -274,7 +279,12 @@ IComponent::RequestResult ProcessInfoNode::startProcess(score::cpp::stop_token s | |
| } | ||
|
|
||
| setState(ProcessState::kRunning); // Can fail if we've terminated already | ||
| return tryReportCompletion(ProcessState::kRunning); | ||
|
|
||
| // A self-terminating process may already have exited before startup completed. tryHandleTermination() | ||
| // leaves such a node waiting for the startup thread, so report against the state actually reached. | ||
| const ProcessState reached_state = | ||
| (getState() == ProcessState::kTerminated) ? ProcessState::kTerminated : ProcessState::kRunning; | ||
|
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. Why not just put |
||
| return tryReportCompletion(reached_state); | ||
| } | ||
|
|
||
| void ProcessInfoNode::setupControlClientChannel() | ||
|
|
||
|
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. I don't think we should shorten the name to just |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # ******************************************************************************* | ||
| # 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 | ||
| # ******************************************************************************* | ||
| load("@rules_cc//cc:cc_binary.bzl", "cc_binary") | ||
| load("//tests/utils/bazel:integration.bzl", "integration_test") | ||
|
|
||
| cc_binary( | ||
| name = "filesystem_reader", | ||
| srcs = ["filesystem_reader.cpp"], | ||
| deps = [ | ||
| "//score/launch_manager:lifecycle_cc", | ||
| "//tests/utils/test_helper", | ||
| "//tests/utils/test_helper:process_utils", | ||
| "@googletest//:gtest_main", | ||
| ], | ||
| ) | ||
|
|
||
| cc_binary( | ||
| name = "control_client_test_driver", | ||
| srcs = ["control_client_test_driver.cpp"], | ||
| deps = [ | ||
| "//score/launch_manager:control_cc", | ||
| "//score/launch_manager:lifecycle_cc", | ||
| "//tests/utils/test_helper", | ||
| "@googletest//:gtest_main", | ||
| ], | ||
| ) | ||
|
|
||
| integration_test( | ||
| name = "rt_running_when_process_exits", | ||
| timeout = "short", | ||
| srcs = ["rt_running_when_process_exits.py"], | ||
| binaries = [ | ||
| ":control_client_test_driver", | ||
| ":filesystem_reader", | ||
| ":setup_filesystem.sh", | ||
| ":slow_setup.sh", | ||
| "//score/launch_manager", | ||
| ], | ||
| config = ":rt_running_when_process_exits.json", | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /******************************************************************************** | ||
| * 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 <gtest/gtest.h> | ||
|
|
||
| #include <filesystem> | ||
| #include <string_view> | ||
|
|
||
| #include "tests/utils/test_helper/test_helper.hpp" | ||
| #include <score/mw/lifecycle/control_client.h> | ||
| #include <score/mw/lifecycle/report_running.h> | ||
|
|
||
| namespace | ||
| { | ||
| /// @brief Marker file written by slow_setup.sh once it has finished (and is about to exit). | ||
| constexpr std::string_view kSlowSetupOutput = "slow_setup_output.txt"; | ||
| } // namespace | ||
|
|
||
| // Given a configuration with two run targets, each pulling in a self-terminating component whose | ||
| // ready condition is "Terminated" but which differ in whether that component has a dependent: | ||
| // | ||
| // - run_target_reader: filesystem_reader (ready "Running") depends on setup_filesystem_sh | ||
| // (self-terminating, ready "Terminated"). The terminated-ready | ||
| // component HAS a dependent. | ||
| // - run_target_slow_setup: depends directly on slow_setup_sh (self-terminating, ready | ||
| // "Terminated") which has NO dependent component. | ||
| // | ||
| // In both cases the run target must only report success once the terminated-ready component's | ||
| // process has actually exited. Without the fix, graph accounting for such a node happens as soon as | ||
| // the process is *started*, so ActivateRunTarget(...).Get() returns while the script is still | ||
| // running and its marker file has not been written yet. | ||
| TEST(RtRunningWhenProcessExits, ControlClientTestDriver) | ||
| { | ||
| score::mw::lifecycle::ControlClient client; | ||
| score::cpp::stop_token stop_token; | ||
|
|
||
| // kSlowSetupOutput is checked too: its later presence must be a reliable signal that | ||
| // slow_setup.sh terminated during *this* run, not leftover from a previous one. | ||
| ASSERT_TRUE(check_clean({test_end_location, kSlowSetupOutput})); | ||
|
|
||
| TEST_STEP("Report running") | ||
| { | ||
| score::mw::lifecycle::report_running(); | ||
| } | ||
|
|
||
| // The with-dependents case: filesystem_reader asserts on the prepared file and on the setup | ||
| // script process being gone, so the ordering is checked there. | ||
| TEST_STEP("Activate run target with a terminated-ready component that HAS a dependent") | ||
| { | ||
| auto result = client.ActivateRunTarget("run_target_reader").Get(stop_token); | ||
| EXPECT_TRUE(result.has_value()) << "Activating run_target_reader failed: " << result.error().Message(); | ||
| } | ||
|
|
||
| // The no-dependents case: activation must only complete once slow_setup.sh has terminated. | ||
| TEST_STEP("Activate run target with a terminated-ready component that has NO dependent") | ||
| { | ||
| auto result = client.ActivateRunTarget("run_target_slow_setup").Get(stop_token); | ||
| EXPECT_TRUE(result.has_value()) << "Activating run_target_slow_setup failed: " << result.error().Message(); | ||
|
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 |
||
| } | ||
|
|
||
| TEST_STEP("Verify slow_setup.sh had terminated before activation completed") | ||
| { | ||
| EXPECT_TRUE(std::filesystem::exists(kSlowSetupOutput)) | ||
| << "run_target_slow_setup reported success while slow_setup.sh was still running: its " | ||
| "output file has not been written yet. A run target depending on a terminated-ready " | ||
| "component must only become ready once that component's process has actually exited."; | ||
| } | ||
|
|
||
| TEST_STEP("Activate run target Off") | ||
| { | ||
| client.ActivateRunTarget("Off"); | ||
| } | ||
| } | ||
|
|
||
| int main() | ||
| { | ||
| return TestRunner(__FILE__, TerminationBehavior::kWait, TerminationNotification::kTestEnd).RunTests(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| /******************************************************************************** | ||
| * 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 <gtest/gtest.h> | ||
|
|
||
| #include <filesystem> | ||
| #include <fstream> | ||
| #include <string> | ||
| #include <string_view> | ||
|
|
||
| #include "tests/utils/test_helper/process_utils.hpp" | ||
| #include "tests/utils/test_helper/test_helper.hpp" | ||
| #include <score/mw/lifecycle/report_running.h> | ||
|
|
||
| namespace | ||
| { | ||
|
|
||
| constexpr std::string_view kSetupScriptName = "setup_filesystem.sh"; | ||
| constexpr std::string_view kSetupOutputFile = "setup_filesystem_output.txt"; | ||
|
|
||
| } // namespace | ||
|
|
||
| // Given a configuration with: | ||
| // - A self-terminating component "setup_filesystem_sh" (wrapping setup_filesystem.sh) whose | ||
| // ready condition is "Terminated". | ||
| // - A component "filesystem_reader" that depends on "setup_filesystem_sh". | ||
| // - An initial Run Target "Startup" that depends on "filesystem_reader". | ||
| // | ||
| // When the Launch Manager activates "Startup", it must first run setup_filesystem.sh to completion | ||
| // (the script writes a marker file and exits) before starting filesystem_reader. | ||
| TEST(RtRunningWhenProcessExits, FilesystemReader) | ||
| { | ||
| ASSERT_TRUE(check_clean({test_end_location})); | ||
|
|
||
| TEST_STEP("Report running") | ||
| { | ||
| score::mw::lifecycle::report_running(); | ||
| } | ||
|
|
||
| TEST_STEP("Read file prepared by setup_filesystem.sh") | ||
| { | ||
| ASSERT_TRUE(std::filesystem::exists(kSetupOutputFile)) | ||
| << "The file prepared by setup_filesystem.sh does not exist; the dependency was not " | ||
| "started/finished before filesystem_reader"; | ||
|
|
||
| std::ifstream output{std::filesystem::path{kSetupOutputFile}}; | ||
|
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. I think initializing here a |
||
| ASSERT_TRUE(output.is_open()) << "Could not open " << kSetupOutputFile; | ||
| std::string content; | ||
| std::getline(output, content); | ||
| EXPECT_EQ(content, "filesystem is ready") << "Unexpected content in " << kSetupOutputFile; | ||
| } | ||
|
|
||
| TEST_STEP("Verify setup_filesystem.sh process has already terminated") | ||
| { | ||
| EXPECT_FALSE(test_helper::process_is_running(kSetupScriptName)) | ||
| << "The setup_filesystem.sh process is still running; filesystem_reader was started " | ||
| "before its dependency terminated"; | ||
| } | ||
| } | ||
|
|
||
| int main() | ||
| { | ||
| // test_end is signalled by control_client_test_driver, which orchestrates the run target switches. | ||
| TestRunner runner{__FILE__, TerminationBehavior::kContinue, TerminationNotification::kNone}; | ||
| return runner.RunTests(); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A component can still want to go to a different state if it doesn't have a process state ready condition. This would mean that if a process_state ready condition is not set nothing will be started ever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also comparing enums with
>=shouldn't be done as it's very unclear.