Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ void Graph::handleComponentEvent(const ComponentEvent& event)
// This is always an error after ready - an unexpected termination before ready is an activation failure
const auto error = IComponent::ComponentError::kErrorAfterReady;
abort(1, error);

// Need to clean up any leftover resources
IComponent& failingComponent = componentOf(nodes_[data.node_index]);
static_cast<void>(failingComponent.deactivate({}));

if (jobs_in_progress_ == 0)
{
handleNonTransitionExecution(getState());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,11 +440,19 @@ TEST_F(GraphHandleComponentEventTest, unexpectedTerminationDuringSuccess)
{
RecordProperty(
"Description",
"Test that an unexpected termination after a successful transition causes the graph to enter an undefined "
"state");
"Test that an unexpected termination after a successful transition causes the graph to deactivate the "
"component and enter an undefined state");

completeTransition(state_name(run_target_name(0)));

const auto component = graph_.getProcessInfoNode(0);
EXPECT_CALL(process_interface_, requestTermination)
.WillOnce(DoAll(
InvokeWithoutArgs([component] {
static_cast<void>(component->tryHandleTermination(134));
}),
Return(osal::OsalReturnType::kSuccess)));

graph_.handleComponentEvent(UnexpectedTermination{0});

EXPECT_EQ(graph_.getState(), GraphState::kUndefinedState);
Expand All @@ -464,6 +472,14 @@ TEST_F(GraphHandleComponentEventTest, unexpectedTerminationDuringTransition)
const auto component_index = first_job.value()->component.get().getIndex();
graph_.handleComponentEvent(ActivationSuccessful{component_index});

const auto component = graph_.getProcessInfoNode(component_index);
EXPECT_CALL(process_interface_, requestTermination)
.WillOnce(DoAll(
InvokeWithoutArgs([component] {
static_cast<void>(component->tryHandleTermination(134));
}),
Return(osal::OsalReturnType::kSuccess)));

// The active component then crashes
graph_.handleComponentEvent(UnexpectedTermination{component_index});

Expand Down
47 changes: 47 additions & 0 deletions tests/integration/crash_ignores_dependents/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# *******************************************************************************
# 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 = "test_process",
srcs = ["test_process.cpp"],
deps = [
"//score/launch_manager:control_cc",
"//score/launch_manager:lifecycle_cc",
"//tests/utils/test_helper",
"@googletest//:gtest_main",
],
)

cc_binary(
name = "process_crashing_once",
srcs = ["process_crashing_once.cpp"],
deps = [
"//score/launch_manager:lifecycle_cc",
"//tests/utils/test_helper",
"@googletest//:gtest_main",
],
)

integration_test(
name = "crash_ignores_dependents",
srcs = ["crash_ignores_dependents.py"],
binaries = [
":config",
":test_process",
":process_crashing_once",
"//score/launch_manager",
],
config = ":crash_ignores_dependents.json",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{
"schema_version": 1,
"defaults": {
"deployment_config": {
"bin_dir": "/tmp/tests/crash_ignores_dependents",
"ready_timeout": 1.0,
"shutdown_timeout": 1.0,
"ready_recovery_action": {
"restart": {
"number_of_attempts": 0
}
},
"recovery_action": {
"switch_run_target": {
"run_target": "fallback_run_target"
}
},
"environmental_variables": {
"LD_LIBRARY_PATH": "/opt/lib"
},
"sandbox": {
"uid": 0,
"gid": 0,
"scheduling_policy": "SCHED_OTHER",
"scheduling_priority": 0
}
},
"component_properties": {
"application_profile": {
"application_type": "Reporting",
"is_self_terminating": false,
"alive_supervision": {
"reporting_cycle": 0.1,
"min_indications": 1,
"max_indications": 3,
"failed_cycles_tolerance": 1
}
},
"ready_condition": {
"process_state": "Running"
}
}
},
"components": {
"test_process": {
"component_properties": {
"binary_name": "test_process",
"application_profile": {
"application_type": "Reporting",
"is_self_terminating": true,
"alive_supervision": {
"min_indications": 0
}
},
"depends_on": ["component_crashing_once"]
},
"deployment_config": {
"ready_timeout": 1.0,
"shutdown_timeout": 1.0,
"environmental_variables": {
"PROCESSIDENTIFIER": "test_process"
}
}
},
"component_crashing_once": {
"component_properties": {
"binary_name": "process_crashing_once",
"application_profile": {
"application_type": "Reporting",
"is_self_terminating": true
}
},
"deployment_config": {
"environmental_variables": {
"PROCESSIDENTIFIER": "component_crashing_once"
}
}
}
},
"run_targets": {
"Startup": {
"depends_on": [
"test_process"
]
}
},
"initial_run_target": "Startup",
"alive_supervision": {
"evaluation_cycle": 0.05
},
"fallback_run_target": {
"depends_on": [
"test_process"
]
}
}
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
# *******************************************************************************
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=[],
test_type="interface-test",
derivation_technique="design-analysis",
)
def test_crash_ignores_dependents(
target, setup_test, assert_test_results, remote_test_dir
):
"""
Objective: Verifies that the launch manager does not restart a process if a process it depends on crashes.

A process crashes after run target activation completes and proceeds normally the second time it is launched.
Expected Behaviour: The process that depends on it is not interrupted or restarted.
"""

new_config_path = str(remote_test_dir / "etc/crash_ignores_dependents.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=6.0,
)

assert_test_results({"test_process.xml", "process_crashing_once.xml"})
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
********************************************************************************/
#include <gtest/gtest.h>

#include "tests/utils/test_helper/test_helper.hpp"
#include <score/mw/lifecycle/report_running.h>

TEST(CrashIgnoresDependents, CrashingProcess)
{
TEST_STEP("Report running")
{
score::mw::lifecycle::report_running();
}

// Limitation: we can't wait for run target activation to complete
sleep(1);

TEST_STEP("Crash if we haven't crashed yet")
{
const std::string_view crash_file = "process_crashed";

if (!std::filesystem::exists(crash_file))
{
std::cout << "Process crashing..." << std::endl;
if (!touch_file(crash_file))
{
std::cout << "Failed to deploy marker file!" << std::endl;
}
exit(1);
}

ASSERT_TRUE(touch_file("process_started_normally"));
std::cout << "Process finishing normally" << std::endl;
}
}

int main()
{
TestRunner(__FILE__, TerminationBehavior::kContinue).RunTests();
}
57 changes: 57 additions & 0 deletions tests/integration/crash_ignores_dependents/test_process.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/********************************************************************************
* 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 "tests/utils/test_helper/test_helper.hpp"
#include <fcntl.h>
#include <score/mw/lifecycle/report_running.h>
#include <chrono>
#include <thread>

TEST(CrashIgnoresDependents, TestProcess)
{
const std::string_view started_file = "test_process_started";
const std::string_view crash_file = "process_crashed";

TEST_STEP("Check this is the first start")
{
ASSERT_TRUE(check_clean({test_end_location, started_file, crash_file}))
<< "Process was started more than once!";

ASSERT_TRUE(touch_file(started_file)) << "Failed to deploy file!";
}

TEST_STEP("Report running")
{
score::mw::lifecycle::report_running();
}

// Wait for the crashing process to deploy its first file
{
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
while (!std::filesystem::exists(crash_file) && std::chrono::steady_clock::now() < deadline)
{
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
// Then
TEST_STEP("Verify process crashed while this was running")
{
EXPECT_TRUE(std::filesystem::exists(crash_file)) << "Process did not actually crash";
}
}

int main()
{
return TestRunner(__FILE__, TerminationBehavior::kContinue, TerminationNotification::kTestEnd).RunTests();
}
47 changes: 47 additions & 0 deletions tests/integration/fallback_to_same_target_restarts/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# *******************************************************************************
# 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 = "control_client_mock",
srcs = ["control_client_mock.cpp"],
deps = [
"//score/launch_manager:control_cc",
"//score/launch_manager:lifecycle_cc",
"//tests/utils/test_helper",
"@googletest//:gtest_main",
],
)

cc_binary(
name = "process_crashing_once",
srcs = ["process_crashing_once.cpp"],
deps = [
"//score/launch_manager:lifecycle_cc",
"//tests/utils/test_helper",
"@googletest//:gtest_main",
],
)

integration_test(
name = "fallback_to_same_target_restarts",
srcs = ["fallback_to_same_target_restarts.py"],
binaries = [
":config",
":control_client_mock",
":process_crashing_once",
"//score/launch_manager",
],
config = ":fallback_to_same_target_restarts.json",
)
Loading
Loading