-
Notifications
You must be signed in to change notification settings - Fork 34
Add test for feat_req__lifecycle__shutdown_signal #414
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
Merged
MaciejKaszynski
merged 10 commits into
eclipse-score:main
from
etas-contrib:feature/shutdown-signal-fit
Aug 7, 2026
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
9c36139
Add test for comp_req__lifecycle__shutdown_signal
TimoSteuerwaldETAS bcee860
Rename comp to feat
TimoSteuerwaldETAS a2857ac
Rename process
TimoSteuerwaldETAS c054001
Strip down cfg and comments
TimoSteuerwaldETAS 7b250a1
Remove duplicated assertion from test
TimoSteuerwaldETAS 727e755
exit on file system failure and write message
TimoSteuerwaldETAS 1ae4448
Verify in python that no file system error appeared
TimoSteuerwaldETAS ca058e5
feature req is now a component req
TimoSteuerwaldETAS 251ac87
Rename graceful_exit to sigkill_not_received
TimoSteuerwaldETAS 25df534
No longer needed to check in python for log output
TimoSteuerwaldETAS File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # ******************************************************************************* | ||
| # 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:defs.bzl", "cc_binary", "cc_library") | ||
| load("//tests/utils/bazel:integration.bzl", "integration_test") | ||
|
|
||
| cc_library( | ||
| name = "shutdown_signal_common", | ||
| hdrs = ["common.hpp"], | ||
| ) | ||
|
|
||
| cc_binary( | ||
| name = "control_daemon_mock", | ||
| srcs = ["control_daemon_mock.cpp"], | ||
| deps = [ | ||
| ":shutdown_signal_common", | ||
| "//score/launch_manager:control_cc", | ||
| "//score/launch_manager:lifecycle_cc", | ||
| "//tests/utils/test_helper", | ||
| "@googletest//:gtest_main", | ||
| ], | ||
| ) | ||
|
|
||
| cc_binary( | ||
| name = "shutdown_signal_process", | ||
| srcs = ["shutdown_signal_process.cpp"], | ||
| deps = [ | ||
| ":shutdown_signal_common", | ||
| "//score/launch_manager:control_cc", | ||
| "//score/launch_manager:lifecycle_cc", | ||
| "//tests/utils/test_helper", | ||
| "@googletest//:gtest_main", | ||
| ], | ||
| ) | ||
|
|
||
| integration_test( | ||
| name = "shutdown_signal", | ||
| srcs = ["shutdown_signal.py"], | ||
| binaries = [ | ||
| ":control_daemon_mock", | ||
| ":shutdown_signal_process", | ||
| "//score/launch_manager", | ||
| ], | ||
| config = ":shutdown_signal.json", | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /******************************************************************************** | ||
| * 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 SCORE_TESTS_INTEGRATION_SHUTDOWN_SIGNAL_COMMON_HPP | ||
| #define SCORE_TESTS_INTEGRATION_SHUTDOWN_SIGNAL_COMMON_HPP | ||
|
|
||
| #include <string_view> | ||
|
|
||
| /// @brief Written by gtest_process from within its SIGTERM handler, containing | ||
| /// its PID as raw bytes. Its existence proves the Launch Manager delivered a | ||
| /// SIGTERM to request shutdown, and the PID lets control_daemon_mock confirm the | ||
| /// process was subsequently killed. It is written before the process blocks, so | ||
| /// it survives the subsequent SIGKILL (no code can run after SIGKILL). | ||
| constexpr std::string_view sigterm_received_file = "sigterm_received"; | ||
|
|
||
| #endif // SCORE_TESTS_INTEGRATION_SHUTDOWN_SIGNAL_COMMON_HPP |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| /******************************************************************************** | ||
| * 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 <cerrno> | ||
| #include <csignal> | ||
| #include <filesystem> | ||
| #include <fstream> | ||
|
|
||
| #include "common.hpp" | ||
| #include "tests/utils/test_helper/test_helper.hpp" | ||
| #include <score/mw/lifecycle/control_client.h> | ||
| #include <score/mw/lifecycle/report_running.h> | ||
|
|
||
| // The Launch Manager shall shut a process down by sending it a SIGTERM, and, if | ||
| // the process does not terminate itself in time, a SIGKILL. | ||
| // | ||
| // The managed shutdown_signal_process installs a SIGTERM handler that records the SIGTERM | ||
| // (writing its PID to `sigterm_received_file`) and then blocks instead of | ||
| // terminating, forcing the Launch Manager to send SIGKILL. | ||
| // | ||
| // We drive the shutdown by activating "Running" (which starts shutdown_signal_process) and | ||
| // then switching back to "Startup". "Startup" no longer depends on shutdown_signal_process, | ||
| // so it is terminated, while the control daemon itself stays alive (it is part of | ||
| // "Startup") and can therefore assert the outcome. Switching to "Off" instead | ||
| // would terminate the control daemon too, so it could not run the assertion. | ||
| TEST(ShutdownSignal, Daemon) | ||
| { | ||
| score::mw::lifecycle::ControlClient client{}; | ||
| ASSERT_TRUE(check_clean({test_end_location, sigterm_received_file})); | ||
|
|
||
| TEST_STEP("Control daemon report running") | ||
| { | ||
| score::mw::lifecycle::report_running(); | ||
| } | ||
|
|
||
| TEST_STEP("Activate RunTarget Running") | ||
| { | ||
| score::cpp::stop_token stop_token; | ||
| auto result = client.ActivateRunTarget("Running").Get(stop_token); | ||
| EXPECT_TRUE(result.has_value()) << "Activating target Running failed: " << result.error().Message(); | ||
| } | ||
|
|
||
| // Switching away from "Running" terminates shutdown_signal_process. Because it does not | ||
| // self-terminate on SIGTERM, the Launch Manager must escalate to SIGKILL for | ||
| // the transition to complete. | ||
| TEST_STEP("Activate RunTarget Startup") | ||
| { | ||
| score::cpp::stop_token stop_token; | ||
| auto result = client.ActivateRunTarget("Startup").Get(stop_token); | ||
| EXPECT_TRUE(result.has_value()) << "Activating target Startup failed: " << result.error().Message(); | ||
| } | ||
|
|
||
| TEST_STEP("Verify SIGTERM was received and SIGKILL forced termination") | ||
| { | ||
| // SIGTERM was delivered: the process recorded its PID before blocking. | ||
| ASSERT_TRUE(std::filesystem::exists(sigterm_received_file)) | ||
| << "shutdown_signal_process did not receive a SIGTERM during shutdown"; | ||
|
|
||
| // Read back the PID the process wrote from within its SIGTERM handler. | ||
| pid_t pid{}; | ||
| std::ifstream pid_file{std::string{sigterm_received_file}, std::ios::binary}; | ||
| ASSERT_TRUE(pid_file.read(reinterpret_cast<char*>(&pid), sizeof(pid))) | ||
| << "Failed to read the PID from " << sigterm_received_file; | ||
|
|
||
| // SIGKILL forced termination: the process never self-terminates, so its | ||
| // absence proves the Launch Manager escalated to SIGKILL. | ||
| EXPECT_EQ(kill(pid, 0), -1) << "shutdown_signal_process (pid " << pid | ||
| << ") is still alive; it was not SIGKILLed"; | ||
| EXPECT_EQ(errno, ESRCH) << "unexpected errno probing shutdown_signal_process (pid " << pid << ")"; | ||
| } | ||
|
|
||
| TEST_STEP("Activate RunTarget Off") | ||
| { | ||
| client.ActivateRunTarget("Off"); | ||
| } | ||
| } | ||
|
|
||
| int main() | ||
| { | ||
| return TestRunner(__FILE__, TerminationBehavior::kWait, TerminationNotification::kTestEnd).RunTests(); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| { | ||
| "schema_version": 1, | ||
| "defaults": { | ||
| "deployment_config": { | ||
|
MaciejKaszynski marked this conversation as resolved.
|
||
| "bin_dir": "/tmp/tests/shutdown_signal", | ||
| "ready_timeout": 1.0, | ||
| "shutdown_timeout": 1.0, | ||
| "ready_recovery_action": { | ||
| "restart": { | ||
| "number_of_attempts": 0 | ||
| } | ||
| }, | ||
| "sandbox": { | ||
| "uid": 0, | ||
| "gid": 0, | ||
| "scheduling_policy": "SCHED_OTHER", | ||
| "scheduling_priority": 0 | ||
| } | ||
| } | ||
| }, | ||
| "components": { | ||
| "control_daemon": { | ||
| "component_properties": { | ||
| "binary_name": "control_daemon_mock", | ||
| "application_profile": { | ||
| "application_type": "State_Manager", | ||
| "alive_supervision": { | ||
| "min_indications": 0 | ||
| } | ||
| } | ||
| }, | ||
| "deployment_config": { | ||
| "ready_timeout": 1.0, | ||
| "shutdown_timeout": 1.0, | ||
| "environmental_variables": { | ||
| "PROCESSIDENTIFIER": "control_daemon" | ||
| } | ||
| } | ||
| }, | ||
| "shutdown_signal_process": { | ||
| "component_properties": { | ||
| "binary_name": "shutdown_signal_process", | ||
| "application_profile": { | ||
| "application_type": "Reporting" | ||
| } | ||
| }, | ||
| "deployment_config": { | ||
| "shutdown_timeout": 0.5, | ||
| "environmental_variables": { | ||
| "PROCESSIDENTIFIER": "DefaultPG_app0" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "run_targets": { | ||
| "Startup": { | ||
| "depends_on": [ | ||
| "control_daemon" | ||
| ], | ||
| "recovery_action": { | ||
| "switch_run_target": { | ||
| "run_target": "fallback_run_target" | ||
| } | ||
| } | ||
| }, | ||
| "Running": { | ||
| "depends_on": [ | ||
| "control_daemon", | ||
| "shutdown_signal_process" | ||
| ], | ||
| "recovery_action": { | ||
| "switch_run_target": { | ||
| "run_target": "fallback_run_target" | ||
| } | ||
| } | ||
| }, | ||
| "Off": { | ||
| "depends_on": [], | ||
| "recovery_action": { | ||
| "switch_run_target": { | ||
| "run_target": "fallback_run_target" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "initial_run_target": "Startup", | ||
| "fallback_run_target": { | ||
| "depends_on": [] | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # ******************************************************************************* | ||
| # 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 | ||
| # ******************************************************************************* | ||
| from tests.utils.testing_utils.run_until_file_deployed import run_until_file_deployed | ||
| from tests.utils.testing_utils.setup_test import setup_test | ||
| from tests.utils.testing_utils.test_results import assert_test_results | ||
| from attribute_plugin import add_test_properties | ||
|
|
||
|
|
||
| @add_test_properties( | ||
| fully_verifies=[ | ||
| "comp_req__launch_man__shutdown_signal", | ||
| ], | ||
| partially_verifies=[], | ||
| test_type="requirements-based", | ||
| derivation_technique="requirements-analysis", | ||
| ) | ||
| def test_shutdown_signal(target, setup_test, assert_test_results, remote_test_dir): | ||
| """ | ||
| Objective: Verifies that the Launch Manager shuts a process down by sending a | ||
| SIGTERM and, if the process does not terminate itself in time, escalates to a | ||
| SIGKILL. | ||
|
|
||
| The control daemon activates the "Running" run target (starting the managed | ||
| shutdown_signal_process), then switches back to "Startup". The shutdown_signal_process installs a | ||
| SIGTERM handler that records its PID and then deliberately blocks instead of | ||
| terminating, forcing the Launch Manager to send SIGKILL. Finally the control | ||
| daemon activates "Off". | ||
|
|
||
| Expected Behaviour: shutdown_signal_process receives a SIGTERM (proven by the | ||
| `sigterm_received` file, which holds the PID it wrote before blocking and | ||
| therefore survives SIGKILL) and is then force-terminated by SIGKILL (proven by | ||
| that PID no longer existing, since the process never self-terminates). | ||
| """ | ||
|
|
||
| new_config_path = str(remote_test_dir / "etc/shutdown_signal.bin") | ||
|
|
||
| run_until_file_deployed( | ||
| target=target, | ||
| binary_path=str(remote_test_dir / "launch_manager"), | ||
| file_path=remote_test_dir.parent / "test_end", | ||
| cwd=str(remote_test_dir), | ||
| args=["-c", new_config_path], | ||
| timeout_s=10.0, | ||
| ) | ||
|
|
||
| assert_test_results({"control_daemon_mock.xml", "shutdown_signal_process.xml"}) |
71 changes: 71 additions & 0 deletions
71
tests/integration/shutdown_signal/shutdown_signal_process.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /******************************************************************************** | ||
|
MaciejKaszynski marked this conversation as resolved.
|
||
| * 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 "common.hpp" | ||
| #include "tests/utils/test_helper/test_helper.hpp" | ||
| #include <fcntl.h> | ||
| #include <gtest/gtest.h> | ||
| #include <score/mw/lifecycle/report_running.h> | ||
| #include <unistd.h> | ||
| #include <csignal> | ||
|
|
||
| namespace | ||
| { | ||
| /// @brief SIGTERM handler installed by the process under test. | ||
| /// | ||
| /// It records its PID (so the outcome can be verified even after the process is | ||
| /// gone, since no code runs after SIGKILL) and then blocks forever instead of | ||
| /// terminating. Because it never self-terminates, the Launch Manager must | ||
| /// escalate to SIGKILL to shut it down; the recorded PID then lets | ||
| /// control_daemon_mock confirm that the process is truly gone. | ||
| void shutdownSignalHandler(int /*signum*/) | ||
| { | ||
| // getpid()/open()/write()/pause() are all async-signal-safe, so this is safe | ||
| // to run from within a signal handler. The PID is written as raw bytes; no | ||
| // string encoding is needed. On a write failure nothing is recorded, which | ||
| // fails the SIGTERM assertion rather than masquerading as a graceful exit. | ||
| const pid_t pid = getpid(); | ||
| const int fd = open(sigterm_received_file.data(), O_WRONLY | O_CREAT | O_TRUNC, 0644); | ||
| if (fd >= 0) | ||
| { | ||
| static_cast<void>(write(fd, &pid, sizeof(pid))); | ||
| static_cast<void>(close(fd)); | ||
| } | ||
|
|
||
| // Do NOT terminate: block until SIGKILL arrives so shutdown requires it. | ||
| while (true) | ||
| { | ||
| static_cast<void>(pause()); | ||
| } | ||
| } | ||
| } // namespace | ||
|
|
||
| TEST(ShutdownSignal, Process) | ||
| { | ||
| // Remove any leftover file from a previous manual run. | ||
| ASSERT_TRUE(check_clean({sigterm_received_file}, false)); | ||
|
|
||
| // Install our own SIGTERM handler. This must happen after the TestRunner | ||
| // constructor (which registers its default handler), so that ours takes | ||
| // precedence for the shutdown signal sent by the Launch Manager. | ||
| signal(SIGTERM, shutdownSignalHandler); | ||
|
|
||
| // Report running so the Launch Manager considers this process ready and the | ||
| // "Running" run target can be activated. | ||
| score::mw::lifecycle::report_running(); | ||
| } | ||
|
|
||
| int main() | ||
| { | ||
| return TestRunner(__FILE__).RunTests(); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.