Skip to content

Commit c2e120c

Browse files
committed
Add tests
1 parent eb807a1 commit c2e120c

3 files changed

Lines changed: 591 additions & 1 deletion

File tree

score/launch_manager/daemon/src/configuration/BUILD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,13 @@ cc_library(
133133
"//score/launch_manager/daemon/src/process_state_client:posix_process",
134134
],
135135
)
136+
137+
cc_test(
138+
name = "configuration_adapter_UT",
139+
srcs = ["details/configuration_adapter_UT.cpp"],
140+
visibility = ["//tests:__subpackages__"],
141+
deps = [
142+
":configuration_adapter",
143+
"@googletest//:gtest_main",
144+
],
145+
)
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
/********************************************************************************
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* SPDX-License-Identifier: Apache-2.0
12+
********************************************************************************/
13+
#include "score/mw/launch_manager/configuration/configuration_adapter.hpp"
14+
#include "score/mw/launch_manager/configuration/config.hpp"
15+
16+
#include <gmock/gmock.h>
17+
#include <gtest/gtest.h>
18+
19+
#include <cstring>
20+
#include <string>
21+
22+
namespace score::mw::launch_manager::configuration {
23+
namespace {
24+
25+
using ::testing::Eq;
26+
using ::testing::IsNull;
27+
using ::testing::Ne;
28+
using ::testing::NotNull;
29+
30+
using IdentifierHash = score::lcm::IdentifierHash;
31+
32+
Config makeMinimalConfig()
33+
{
34+
ComponentConfig comp_a;
35+
comp_a.name = "comp_a";
36+
comp_a.description = "Component A";
37+
comp_a.component_properties.binary_name = "comp_a_bin";
38+
comp_a.component_properties.application_profile.application_type = ApplicationType::ReportingAndSupervised;
39+
comp_a.component_properties.application_profile.is_self_terminating = false;
40+
comp_a.component_properties.application_profile.alive_supervision = ComponentAliveSupervision{500, 2, 1, 3};
41+
comp_a.component_properties.ready_condition = ReadyCondition{ProcessState::Running};
42+
comp_a.deployment_config.ready_timeout_ms = 500;
43+
comp_a.deployment_config.shutdown_timeout_ms = 500;
44+
comp_a.deployment_config.bin_dir = "/opt/apps";
45+
comp_a.deployment_config.working_dir = "/tmp";
46+
comp_a.deployment_config.sandbox.uid = 1000;
47+
comp_a.deployment_config.sandbox.gid = 1000;
48+
comp_a.deployment_config.sandbox.scheduling_policy = SCHED_OTHER;
49+
comp_a.deployment_config.sandbox.scheduling_priority = 0;
50+
51+
ComponentConfig comp_b;
52+
comp_b.name = "comp_b";
53+
comp_b.description = "Component B";
54+
comp_b.component_properties.binary_name = "comp_b_bin";
55+
comp_b.component_properties.application_profile.application_type = ApplicationType::Native;
56+
comp_b.component_properties.application_profile.is_self_terminating = true;
57+
comp_b.component_properties.depends_on = {"comp_a"};
58+
comp_b.component_properties.ready_condition = ReadyCondition{ProcessState::Running};
59+
comp_b.deployment_config.ready_timeout_ms = 1000;
60+
comp_b.deployment_config.shutdown_timeout_ms = 1000;
61+
comp_b.deployment_config.bin_dir = "/opt/apps";
62+
comp_b.deployment_config.working_dir = "/tmp";
63+
comp_b.deployment_config.sandbox.uid = 1000;
64+
comp_b.deployment_config.sandbox.gid = 1000;
65+
comp_b.deployment_config.sandbox.scheduling_policy = SCHED_OTHER;
66+
comp_b.deployment_config.sandbox.scheduling_priority = 0;
67+
68+
std::vector<ComponentConfig> components;
69+
components.push_back(std::move(comp_a));
70+
components.push_back(std::move(comp_b));
71+
72+
RunTargetConfig startup;
73+
startup.name = "Startup";
74+
startup.description = "Initial run target";
75+
startup.depends_on = {"comp_a"};
76+
startup.transition_timeout_ms = 5000;
77+
startup.recovery_action.run_target = "fallback_run_target";
78+
79+
RunTargetConfig full;
80+
full.name = "Full";
81+
full.description = "Full run target";
82+
full.depends_on = {"comp_b", "Startup"};
83+
full.transition_timeout_ms = 5000;
84+
full.recovery_action.run_target = "fallback_run_target";
85+
86+
std::vector<RunTargetConfig> run_targets;
87+
run_targets.push_back(std::move(startup));
88+
run_targets.push_back(std::move(full));
89+
90+
FallbackRunTargetConfig fallback;
91+
fallback.description = "Safe state";
92+
fallback.transition_timeout_ms = 1500;
93+
94+
AliveSupervisionConfig alive;
95+
alive.evaluation_cycle_ms = 500;
96+
97+
return ConfigBuilder{}
98+
.setComponents(std::move(components))
99+
.setRunTargets(std::move(run_targets))
100+
.setInitialRunTarget("Startup")
101+
.setFallbackRunTarget(std::move(fallback))
102+
.setAliveSupervision(alive)
103+
.build();
104+
}
105+
106+
class ConfigurationAdapterTest : public ::testing::Test {
107+
protected:
108+
void SetUp() override
109+
{
110+
RecordProperty("TestType", "interface-test");
111+
RecordProperty("DerivationTechnique", "explorative-testing");
112+
113+
config_ = makeMinimalConfig();
114+
adapter_.initialize(config_);
115+
}
116+
117+
void TearDown() override
118+
{
119+
adapter_.deinitialize();
120+
}
121+
122+
Config config_{makeMinimalConfig()};
123+
ConfigurationAdapter adapter_;
124+
};
125+
126+
TEST_F(ConfigurationAdapterTest, GetListOfProcessGroupsReturnsSingleImplicitGroup)
127+
{
128+
RecordProperty("Description", "After initialize, getListOfProcessGroups returns a list with the single MainPG group.");
129+
130+
auto result = adapter_.getListOfProcessGroups();
131+
132+
ASSERT_TRUE(result.has_value());
133+
const auto& groups = **result;
134+
ASSERT_THAT(groups.size(), Eq(1U));
135+
EXPECT_THAT(groups[0], Eq(IdentifierHash{"MainPG"}));
136+
}
137+
138+
TEST_F(ConfigurationAdapterTest, GetMainPGStartupStateMapsToInitialRunTarget)
139+
{
140+
RecordProperty("Description", "getMainPGStartupState returns MainPG/Startup matching Config::initialRunTarget.");
141+
142+
auto result = adapter_.getMainPGStartupState();
143+
144+
ASSERT_TRUE(result.has_value());
145+
const auto* state_id = *result;
146+
EXPECT_THAT(state_id->pg_name_, Eq(IdentifierHash{"MainPG"}));
147+
EXPECT_THAT(state_id->pg_state_name_, Eq(IdentifierHash{"MainPG/Startup"}));
148+
}
149+
150+
TEST_F(ConfigurationAdapterTest, GetNameOfOffStateMapsToMainPGOff)
151+
{
152+
RecordProperty("Description", "getNameOfOffState returns MainPG/Off for the implicit process group.");
153+
154+
auto off_state = adapter_.getNameOfOffState(IdentifierHash{"MainPG"});
155+
156+
EXPECT_THAT(off_state, Eq(IdentifierHash{"MainPG/Off"}));
157+
}
158+
159+
TEST_F(ConfigurationAdapterTest, GetNameOfRecoveryStateMapsToFallbackRunTarget)
160+
{
161+
RecordProperty("Description", "getNameOfRecoveryState returns MainPG/fallback_run_target.");
162+
163+
auto recovery = adapter_.getNameOfRecoveryState(IdentifierHash{"MainPG"});
164+
165+
EXPECT_THAT(recovery, Eq(IdentifierHash{"MainPG/fallback_run_target"}));
166+
}
167+
168+
TEST_F(ConfigurationAdapterTest, GetProcessIndexesListResolvesRunTargetDependencies)
169+
{
170+
RecordProperty("Description", "getProcessIndexesList resolves RunTarget depends_on to component indexes.");
171+
172+
score::lcm::internal::ProcessGroupStateID startup_id{
173+
IdentifierHash{"MainPG"}, IdentifierHash{"MainPG/Startup"}};
174+
175+
auto result = adapter_.getProcessIndexesList(startup_id);
176+
177+
ASSERT_TRUE(result.has_value());
178+
const auto& indexes = **result;
179+
EXPECT_THAT(indexes.size(), Eq(1U));
180+
EXPECT_THAT(indexes[0], Eq(0U));
181+
}
182+
183+
TEST_F(ConfigurationAdapterTest, GetProcessIndexesListResolvesTransitiveDependencies)
184+
{
185+
RecordProperty("Description", "Full run target depends on comp_b and Startup; transitive resolution yields both component indexes.");
186+
187+
score::lcm::internal::ProcessGroupStateID full_id{
188+
IdentifierHash{"MainPG"}, IdentifierHash{"MainPG/Full"}};
189+
190+
auto result = adapter_.getProcessIndexesList(full_id);
191+
192+
ASSERT_TRUE(result.has_value());
193+
const auto& indexes = **result;
194+
EXPECT_THAT(indexes.size(), Eq(2U));
195+
196+
bool has_comp_a = std::find(indexes.begin(), indexes.end(), 0U) != indexes.end();
197+
bool has_comp_b = std::find(indexes.begin(), indexes.end(), 1U) != indexes.end();
198+
EXPECT_TRUE(has_comp_a);
199+
EXPECT_TRUE(has_comp_b);
200+
}
201+
202+
TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationMapsComponentFields)
203+
{
204+
RecordProperty("Description", "getOsProcessConfiguration maps ComponentConfig fields to legacy OsProcess struct.");
205+
206+
IdentifierHash pg_name{"MainPG"};
207+
208+
auto result = adapter_.getOsProcessConfiguration(pg_name, 0U);
209+
210+
ASSERT_TRUE(result.has_value());
211+
const auto* os_proc = *result;
212+
213+
EXPECT_THAT(os_proc->process_id_, Eq(IdentifierHash{"comp_a"}));
214+
EXPECT_THAT(os_proc->process_number_, Eq(0U));
215+
EXPECT_THAT(os_proc->startup_config_.executable_path_, Eq("/opt/apps/comp_a_bin"));
216+
EXPECT_THAT(os_proc->startup_config_.short_name_, Eq("comp_a"));
217+
EXPECT_THAT(os_proc->startup_config_.uid_, Eq(1000U));
218+
EXPECT_THAT(os_proc->startup_config_.gid_, Eq(1000U));
219+
EXPECT_THAT(os_proc->pgm_config_.is_self_terminating_, Eq(false));
220+
EXPECT_THAT(os_proc->pgm_config_.startup_timeout_ms_, Eq(std::chrono::milliseconds{500}));
221+
EXPECT_THAT(os_proc->pgm_config_.termination_timeout_ms_, Eq(std::chrono::milliseconds{500}));
222+
}
223+
224+
TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationInjectsAliveInterfaceForSupervised)
225+
{
226+
RecordProperty("Description", "Supervised components get LCM_ALIVE_INTERFACE_PATH injected into envp.");
227+
228+
IdentifierHash pg_name{"MainPG"};
229+
auto result = adapter_.getOsProcessConfiguration(pg_name, 0U);
230+
ASSERT_TRUE(result.has_value());
231+
const auto* os_proc = *result;
232+
233+
bool found = false;
234+
for (size_t i = 0; os_proc->startup_config_.envp_[i] != nullptr; ++i) {
235+
if (std::string(os_proc->startup_config_.envp_[i]) == "LCM_ALIVE_INTERFACE_PATH=/lifecycle_health_comp_a") {
236+
found = true;
237+
break;
238+
}
239+
}
240+
EXPECT_TRUE(found) << "LCM_ALIVE_INTERFACE_PATH not found in supervised component envp";
241+
}
242+
243+
TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationNoAliveInterfaceForNative)
244+
{
245+
RecordProperty("Description", "Native components do not get LCM_ALIVE_INTERFACE_PATH injected.");
246+
247+
IdentifierHash pg_name{"MainPG"};
248+
auto result = adapter_.getOsProcessConfiguration(pg_name, 1U);
249+
ASSERT_TRUE(result.has_value());
250+
const auto* os_proc = *result;
251+
252+
for (size_t i = 0; os_proc->startup_config_.envp_[i] != nullptr; ++i) {
253+
EXPECT_THAT(std::string(os_proc->startup_config_.envp_[i]).find("LCM_ALIVE_INTERFACE_PATH"),
254+
Eq(std::string::npos))
255+
<< "Native component should not have LCM_ALIVE_INTERFACE_PATH";
256+
}
257+
}
258+
259+
TEST_F(ConfigurationAdapterTest, GetOsProcessDependenciesMapsComponentDependsOn)
260+
{
261+
RecordProperty("Description", "getOsProcessDependencies maps component depends_on to legacy Dependency structs.");
262+
263+
IdentifierHash pg_name{"MainPG"};
264+
265+
auto result = adapter_.getOsProcessDependencies(pg_name, 1U);
266+
267+
ASSERT_TRUE(result.has_value());
268+
const auto* deps = *result;
269+
ASSERT_THAT(deps->size(), Eq(1U));
270+
EXPECT_THAT((*deps)[0].target_process_id_, Eq(IdentifierHash{"comp_a"}));
271+
EXPECT_THAT((*deps)[0].os_process_index_, Eq(0U));
272+
EXPECT_THAT((*deps)[0].process_state_, Eq(score::lcm::ProcessState::kRunning));
273+
}
274+
275+
TEST_F(ConfigurationAdapterTest, GetOsProcessDependenciesEmptyForComponentWithNoDeps)
276+
{
277+
RecordProperty("Description", "Component with no depends_on has an empty dependency list.");
278+
279+
IdentifierHash pg_name{"MainPG"};
280+
281+
auto result = adapter_.getOsProcessDependencies(pg_name, 0U);
282+
283+
ASSERT_TRUE(result.has_value());
284+
const auto* deps = *result;
285+
EXPECT_TRUE(deps->empty());
286+
}
287+
288+
TEST_F(ConfigurationAdapterTest, GetNumberOfOsProcessesReturnsComponentCount)
289+
{
290+
RecordProperty("Description", "getNumberOfOsProcesses returns the number of components.");
291+
292+
auto result = adapter_.getNumberOfOsProcesses(IdentifierHash{"MainPG"});
293+
294+
ASSERT_TRUE(result.has_value());
295+
EXPECT_THAT(*result, Eq(2U));
296+
}
297+
298+
TEST_F(ConfigurationAdapterTest, GetSoftwareClusterReturnsDefault)
299+
{
300+
RecordProperty("Description", "getSoftwareCluster returns the default software cluster.");
301+
302+
auto result = adapter_.getSoftwareCluster(IdentifierHash{"MainPG"});
303+
304+
ASSERT_TRUE(result.has_value());
305+
EXPECT_THAT(*result, Eq(IdentifierHash{"DefaultSoftwareCluster"}));
306+
}
307+
308+
TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationReturnsNulloptForInvalidIndex)
309+
{
310+
RecordProperty("Description", "Out-of-range index returns nullopt.");
311+
312+
auto result = adapter_.getOsProcessConfiguration(IdentifierHash{"MainPG"}, 99U);
313+
314+
EXPECT_FALSE(result.has_value());
315+
}
316+
317+
TEST_F(ConfigurationAdapterTest, GetOsProcessConfigurationReturnsNulloptForUnknownPG)
318+
{
319+
RecordProperty("Description", "Unknown process group name returns nullopt.");
320+
321+
auto result = adapter_.getOsProcessConfiguration(IdentifierHash{"NonExistent"}, 0U);
322+
323+
EXPECT_FALSE(result.has_value());
324+
}
325+
326+
TEST_F(ConfigurationAdapterTest, OffStateReturnsProcessIndexesListEmpty)
327+
{
328+
RecordProperty("Description", "The Off state has no process indexes.");
329+
330+
score::lcm::internal::ProcessGroupStateID off_id{
331+
IdentifierHash{"MainPG"}, IdentifierHash{"MainPG/Off"}};
332+
333+
auto result = adapter_.getProcessIndexesList(off_id);
334+
335+
ASSERT_TRUE(result.has_value());
336+
EXPECT_TRUE((**result).empty());
337+
}
338+
339+
} // namespace
340+
} // namespace score::mw::launch_manager::configuration

0 commit comments

Comments
 (0)