Skip to content

Commit f1513d7

Browse files
System test execution setup
- Local execution - Launching LM daemon with a config - GTEST processes launched by LM with test results parsed - Test timeout - Scaffolding for platform independent python scripts - Running LM without root - setgroups() is not attempted if there are no supplementary group ids - Minor cleanup in ProcessGroupManager::initialize()
1 parent 0b4f3a5 commit f1513d7

20 files changed

Lines changed: 871 additions & 15 deletions

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
],
3030
// Add your personal customizations
3131
"onCreateCommand": {
32-
"update certificates & install dependencies": "sudo apt update && sudo apt install -y --no-install-recommends ca-certificates-java openjdk-17-jre-headless libacl1-dev tmux && sudo update-ca-certificates",
32+
"update certificates & install dependencies": "sudo apt update && sudo apt install -y --no-install-recommends ca-certificates-java openjdk-17-jre-headless libacl1-dev tmux fakechroot && sudo update-ca-certificates",
3333
"bazel use system trust store": "echo 'startup --host_jvm_args=-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts --host_jvm_args=-Djavax.net.ssl.trustStorePassword=changeit' | sudo tee --append /etc/bazel.bazelrc"
3434
},
3535

MODULE.bazel

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ python.toolchain(
113113
)
114114
use_repo(python)
115115

116+
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip", dev_dependency = True)
117+
pip.parse(
118+
hub_name = "pip_score_venv_test",
119+
python_version = PYTHON_VERSION,
120+
requirements_lock = "//tests/integration:requirements.lock",
121+
)
122+
use_repo(pip, "pip_score_venv_test")
123+
116124
bazel_dep(name = "score_baselibs_rust", version = "0.1.0")
117125
bazel_dep(name = "score_baselibs", version = "0.2.4")
118126

config/flatbuffers_rules.bzl

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
def _flatbuffer_json_to_bin_impl(ctx):
2+
flatc = ctx.executable.flatc
3+
json = ctx.file.json
4+
schema = ctx.file.schema
5+
6+
# flatc will name the file the same as the json (can't be changed)
7+
out_name = json.basename[:-len(".json")] + ".bin"
8+
out = ctx.actions.declare_file(out_name, sibling = json)
9+
10+
# flatc args ---------------------------------
11+
flatc_args = [
12+
"-b",
13+
"-o",
14+
out.dirname,
15+
]
16+
17+
for inc in ctx.attr.includes:
18+
flatc_args.extend(["-I", inc.path])
19+
20+
if ctx.attr.strict_json:
21+
flatc_args.append("--strict-json")
22+
23+
flatc_args.extend([schema.path, json.path])
24+
# --------------------------------------------
25+
26+
ctx.actions.run(
27+
inputs = [json, schema] + list(ctx.files.includes),
28+
outputs = [out],
29+
executable = flatc,
30+
arguments = flatc_args,
31+
progress_message = "flatc generation {}".format(json.short_path),
32+
mnemonic = "FlatcGeneration",
33+
)
34+
35+
rf = ctx.runfiles(
36+
files = [out],
37+
root_symlinks = {
38+
("_main/" + ctx.attr.out_dir + "/" + out_name): out,
39+
},
40+
)
41+
42+
return DefaultInfo(files = depset([out]), runfiles = rf)
43+
44+
flatbuffer_json_to_bin = rule(
45+
implementation = _flatbuffer_json_to_bin_impl,
46+
attrs = {
47+
"json": attr.label(
48+
allow_single_file = [".json"],
49+
mandatory = True,
50+
doc = "Json file to convert. Note that the binary file will have the same name as the json (minus the suffix)",
51+
),
52+
"schema": attr.label(
53+
allow_single_file = [".fbs"],
54+
mandatory = True,
55+
doc = "FBS file to use",
56+
),
57+
"out_dir": attr.string(
58+
default = "etc",
59+
doc = "Directory to copy the generated file to, sibling to 'src' and 'tests' dirs. Do not include a trailing '/'",
60+
),
61+
"flatc": attr.label(
62+
default = Label("@flatbuffers//:flatc"),
63+
executable = True,
64+
cfg = "exec",
65+
doc = "Reference to the flatc binary",
66+
),
67+
# flatc arguments
68+
"includes": attr.label_list(
69+
allow_files = True,
70+
doc = "Flatc include paths",
71+
),
72+
"strict_json": attr.bool(
73+
default = False,
74+
doc = "Require strict JSON (no trailing commas etc)",
75+
),
76+
},
77+
)

src/launch_manager_daemon/health_monitor_lib/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ filegroup(
2525
)
2626

2727
# flatcfg configuration
28+
exports_files(["config/hm_flatcfg.fbs"])
29+
2830
cc_library(
2931
name = "config",
3032
hdrs = [

src/launch_manager_daemon/src/process_group_manager/health_monitor_thread.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ bool HealthMonitorThread::start() {
3737

3838
waitForInitializationCompleted(init_status);
3939

40-
return (init_status == score::lcm::saf::daemon::EInitCode::kNoError);
40+
return init_status == saf::daemon::EInitCode::kNoError;
4141
}
4242

4343
void HealthMonitorThread::stop() {

src/launch_manager_daemon/src/process_group_manager/processgroupmanager.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ void ProcessGroupManager::setLaunchManagerConfiguration(const OsProcess* launch_
6565
}
6666

6767
bool ProcessGroupManager::initialize() {
68-
bool success = false;
69-
7068
// setup signal handler
7169
em_cancelled.store(false);
7270
// RULECHECKER_comment(1, 1, check_union_object, "Union type defined in external library is used.", true)
@@ -86,22 +84,24 @@ bool ProcessGroupManager::initialize() {
8684
sigaction(SIGUSR2, &action, NULL);
8785
sigaction(SIGVTALRM, &action, NULL);
8886

89-
success = initializeControlClientHandler() && initializeProcessGroups();
87+
if (!initializeControlClientHandler() || !initializeProcessGroups()) {
88+
return false;
89+
}
9090

91-
if (success) {
92-
LM_LOG_DEBUG() << "Process Group initialization done";
93-
createProcessComponentsObjects();
94-
initializeGraphNodes();
95-
//success = ucm_polling_thread_.startPolling();
96-
success = health_monitor_thread_->start();
91+
LM_LOG_DEBUG() << "Process Group initialization done";
92+
createProcessComponentsObjects();
93+
initializeGraphNodes();
94+
if (!health_monitor_thread_->start()) {
95+
LM_LOG_ERROR() << "Health monitor thread failed to start";
96+
return false;
9797
}
9898

99-
if (success && launch_manager_config_ &&
99+
if (launch_manager_config_ &&
100100
OsalReturnType::kFail == IProcess::setSchedulingAndSecurity(launch_manager_config_->startup_config_)) {
101-
success = false;
101+
return false;
102102
}
103103

104-
return success;
104+
return true;
105105
}
106106

107107
void ProcessGroupManager::deinitialize() {

src/launch_manager_daemon/src/process_group_manager/processlauncher.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ OsalReturnType IProcess::setSchedulingAndSecurity(const OsalConfig& config) {
334334
size_t supplementary_gids_number = config.supplementary_gids_.size();
335335

336336
// Note: the type of the first parameter of setgroups() differs in Linux and QNX, so we use osal
337-
if (-1 == osal::setgroups(supplementary_gids_number, config.supplementary_gids_.data())) {
337+
if (supplementary_gids_number > 0 && -1 == osal::setgroups(supplementary_gids_number, config.supplementary_gids_.data())) {
338338
LM_LOG_ERROR() << "setgroups() failed:" << std::strerror(errno);
339339
retval = OsalReturnType::kFail;
340340
}
@@ -354,6 +354,7 @@ inline void IProcess::handleChildProcess(ChildProcessConfig& param) {
354354
if (OsalReturnType::kSuccess != setSchedulingAndSecurity(*param.config)) {
355355
sysexit(EXIT_FAILURE);
356356
}
357+
357358
changeCurrentWorkingDirectory(*param.config);
358359
implementMemoryResourceLimits(*param.config);
359360
changeSecurityPolicy(*param.config);

tests/integration/BUILD

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
load("@pip_score_venv_test//:requirements.bzl", "all_requirements")
14+
load("@rules_python//python:pip.bzl", "compile_pip_requirements")
15+
load("@score_tooling//python_basics:defs.bzl", "score_py_pytest", "score_virtualenv")
16+
17+
# In order to update the requirements, change the `requirements.txt` file and run:
18+
# `bazel run //tests/integration:requirements.update`.
19+
# This will update the `requirements.lock` file.
20+
# To upgrade all dependencies to their latest versions, run:
21+
# `bazel run //tests/integration:requirements.update -- --upgrade`.
22+
compile_pip_requirements(
23+
name = "requirements",
24+
srcs = [
25+
"requirements.txt",
26+
"@score_tooling//python_basics:requirements.txt",
27+
],
28+
extra_args = [
29+
"--no-annotate",
30+
],
31+
requirements_txt = "requirements.lock",
32+
tags = [
33+
"manual",
34+
],
35+
)
36+
37+
score_virtualenv(
38+
name = "python_tc_venv",
39+
reqs = all_requirements,
40+
venv_name = ".python_tc_venv",
41+
)
42+
43+
cc_library(
44+
name = "test_helper",
45+
hdrs = ["test_helper.hpp"],
46+
visibility = ["//tests:__subpackages__"],
47+
deps = [
48+
"@googletest//:gtest_main",
49+
],
50+
)
51+
52+
py_library(
53+
name = "control_interface",
54+
srcs = ["control_interface.py"],
55+
visibility = ["//tests:__subpackages__"],
56+
)
57+
58+
py_library(
59+
name = "testing_utils",
60+
srcs = ["testing_utils.py"],
61+
visibility = ["//tests:__subpackages__"],
62+
deps = [":control_interface"],
63+
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
from typing import Tuple
14+
from pathlib import Path
15+
from abc import ABC, abstractmethod
16+
17+
18+
class ControlInterface(ABC):
19+
"""Platform independent interface to execute commands on the target"""
20+
21+
@abstractmethod
22+
def exec_command_blocking(
23+
*args: str, timeout=1, **env: str
24+
) -> Tuple[int, str, str]:
25+
"""Execute a command on the target
26+
27+
Args:
28+
*args (str): Command to run with arguments
29+
timeout (int): Time in seconds to exit after, returning status -1
30+
**env (str): Environment vars to set
31+
32+
Returns:
33+
(int, str, str): exit_status, stdout, stderr
34+
"""
35+
raise NotImplementedError()
36+
37+
@abstractmethod
38+
def run_until_file_deployed(
39+
*args,
40+
timeout=1,
41+
file_path=Path("tests/integration/test_end"),
42+
poll_interval=0.05,
43+
**env,
44+
) -> Tuple[int, str, str]:
45+
"""Launch a process and terminate it once a given file has been deployed
46+
47+
Args:
48+
49+
*args (str): Command to run with arguments
50+
timeout (int): Time in seconds to exit after, returning status -1
51+
file_path (Path): File to wait for
52+
poll_interval (float): How often, in seconds, to check if we should terminate the process
53+
**env (str): Environment vars to set
54+
55+
Returns:
56+
(int, str, str): exit_status, stdout, stderr
57+
"""
58+
raise NotImplementedError()

tests/integration/readme.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Local integration testing
2+
3+
## Prerequisites
4+
- fakechroot must be installed to run these tests
5+
- `sudo apt install fakechroot`
6+
7+
## Running the tests
8+
9+
To run all tests, simply run `bazel test //tests/integration/...`
10+
11+
## Running a single test
12+
You can run a single integration test locally using `bazel test //tests/integration/<test name>`

0 commit comments

Comments
 (0)