Skip to content

Commit 97302d8

Browse files
committed
added test cases for conditional launching
Copyright fixes added test case to reject unknown conditions fixed the readme added launch manager processes adding additional test cases for lifecycle removing the changes in pyproject and requirements txt added review comment fixes: - cleared the non-required. - removed the dead LIFECYCLE_TESTS_SUMMARY.md doc reference. - dropped the class-level 13-req partially_verifies blanket claim; each requirement is now tagged on the specific test that actually exercises it (add_test_properties moved onto individual methods) - deleted test_config_defines_startup_retry_policy Adding review comments added the following fixes : - The scenario now actually polls and checks each condition instead of printing it - Replaced plain std::cout text with the same structured JSON log shape the Rust tracing subscriber emits - TestConditionalLaunchingScenario now creates a real flag file, sets a real env var, and spawns a real sleep process - Added TestConditionalLaunchingScenarioTimesOutOnUnmetConditions, a negative test where none of the conditions are ever satisfied - removed test_startup_launches_supervised_apps and test_dependency_gates_rust_startup from test_process_launching_with_daemon.py - TestConditionalLaunchingBlocksOnMissingDependency spins up its own launch_manager with cpp withheld to prove real gating added TestConditionalLaunchingScenarioRejectsUnsupportedPrefix Added timing assertion to TestConditionalLaunchingScenarioRejectsUnsupportedPrefix changed the decorators removed duplicate parametrization added patches/lifecycle/001-forward-visibility-to-config-combiner.patch updated known_goods.json for patch review comments addressed: - Split lifecycle tests into a standalone fit_lifecycle_daemon bazel target with no longer routed by language marker - strengthened test to assert the other app's pid is untouched, proving retry recovery - Added signal_process() helper - Retagged to launch_support updated read me added the issue for persistency build added real retry exhaustion test case added a fix for fit_cpp_orch , added test cases for parallel launching scorebug issue resolved removed the manual marker assuming CI always has resources removed the organizational marker daemon and marker manual as watchdog detection should be part of every CI added daemon invocation review comment address resolving the merge issue
1 parent b47842e commit 97302d8

29 files changed

Lines changed: 3403 additions & 16 deletions

‎bazel_common/score_modules_target_sw.MODULE.bazel‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ bazel_dep(name = "score_lifecycle_health")
6262
git_override(
6363
module_name = "score_lifecycle_health",
6464
commit = "23e9849437c578ade99b8ccbf5d09c1835737b3f",
65+
patch_strip = 1,
66+
patches = [
67+
"//patches/lifecycle:001-forward-visibility-to-config-combiner.patch",
68+
],
6569
remote = "https://github.com/eclipse-score/lifecycle.git",
6670
)
6771

‎feature_integration_tests/README.md‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,98 @@ bazel run //feature_integration_tests/test_scenarios/rust:rust_test_scenarios --
4848
bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit --test_output=streamed
4949
```
5050

51+
To run the lifecycle tests directly with `pytest` and build the scenario binaries on demand:
52+
53+
```sh
54+
python3 -m pytest feature_integration_tests/test_cases/tests/lifecycle/ \
55+
--build-scenarios \
56+
-m rust \
57+
--rust-target-name=//feature_integration_tests/test_scenarios/rust:rust_test_scenarios \
58+
-q -v
59+
60+
python3 -m pytest feature_integration_tests/test_cases/tests/lifecycle/ \
61+
--build-scenarios \
62+
-m cpp \
63+
-q -v
64+
```
65+
66+
The Rust override is required because plain `--build-scenarios` defaults to
67+
`//feature_integration_tests/test_scenarios/rust:rust_test_scenarios`, while the
68+
lifecycle tests need the reduced lifecycle-only Rust target.
69+
70+
#### Sandbox uid/gid and scheduling-policy tests
71+
72+
Some lifecycle daemon tests (`test_launched_process_uid_gid_matches_config_when_applied`,
73+
`test_launched_process_scheduling_matches_config_when_applied`) verify that `launch_manager`
74+
applies the sandbox `uid`/`gid` and scheduling policy from
75+
`feature_integration_tests/configs/lifecycle_daemon_config.json`. This requires granting
76+
`launch_manager` the `cap_setuid,cap_setgid,cap_sys_nice` file capabilities via `setcap`, which
77+
in turn requires `CAP_SETFCAP` — not available to a non-root test runner by default, so these
78+
tests opt in via the `FIT_ENABLE_SETCAP` env var (backed by a passwordless sudoers rule scoped
79+
to the `setcap` binary, e.g. `<user> ALL=(root) NOPASSWD: /usr/sbin/setcap`, with no trailing
80+
arguments pinned — the target path is a fresh `tmp_path` on every run) and skip otherwise.
81+
82+
Under `bazel test`, undeclared env vars like `FIT_ENABLE_SETCAP` only reach the test process when
83+
passed via `--test_env` (not `--action_env`, which only affects build actions). Two variants of
84+
the full suite are relevant:
85+
86+
```sh
87+
# Default: matches CI/CD exactly (sandboxed, no FIT_ENABLE_SETCAP) — the two capability tests skip.
88+
bazel test --config=linux-x86_64 --nocache_test_results //feature_integration_tests/test_cases:fit \
89+
--test_output=all --test_arg=-rs --test_verbose_timeout_warnings
90+
91+
# Local verification: also exercises the uid/gid and scheduling-policy grants instead of skipping.
92+
bazel test --config=linux-x86_64 --nocache_test_results //feature_integration_tests/test_cases:fit \
93+
--spawn_strategy=local --test_env=FIT_ENABLE_SETCAP=1 \
94+
--test_output=all --test_arg=-rs --test_verbose_timeout_warnings
95+
```
96+
97+
Flag rationale (shared by both commands unless noted):
98+
99+
- `--nocache_test_results`: forces re-execution instead of replaying a cached PASS/SKIP, so a
100+
fresh `setcap` attempt is made every time.
101+
- `--test_output=all`: prints full stdout/stderr for every test, not just failures, so the
102+
`sandbox_privileged_reason` and pytest skip-reason diagnostics are visible.
103+
- `--test_arg=-rs`: forwards pytest's `-rs` flag, which prints the reason for every `SKIPPED`
104+
test instead of just `SKIPPED` with no context.
105+
- `--test_verbose_timeout_warnings`: warns when a test's actual runtime is far from its declared
106+
`timeout`/`size`, useful for right-sizing `fit_lifecycle_daemon`'s `timeout = "long"`.
107+
- `--spawn_strategy=local` (local-verification command only): runs the test action directly on
108+
the host instead of inside Bazel's `linux-sandbox`. The sandbox sets `PR_SET_NO_NEW_PRIVS`,
109+
which makes `setuid` (`sudo`) and file capabilities (`setcap`) inert at exec time even with a
110+
correctly configured host — the grant is applied but silently dropped when the supervised
111+
binary later executes. Only unsandboxed execution lets the grant persist.
112+
- `--test_env=FIT_ENABLE_SETCAP=1` (local-verification command only): opts the test process into
113+
the `sudo -n setcap` attempt; without it these tests always take the plain, non-sudo `setcap`
114+
path and skip on a non-root runner. `bazel run` inherits the shell environment directly, so
115+
`export FIT_ENABLE_SETCAP=1` beforehand is sufficient there instead of `--test_env`.
116+
117+
#### Tests skipped in CI/CD
118+
119+
The GitHub Actions runners (`ubuntu-latest`, see `.github/workflows/build_and_test_linux.yml`) run
120+
`bazel test` sandboxed (default `linux-sandbox` strategy) and do not set `FIT_ENABLE_SETCAP` or
121+
provision a passwordless `sudo setcap` rule. As a result, the following subtests in
122+
`fit_lifecycle_daemon` always skip in CI, for both the `rust` and `cpp` supervised-app variants:
123+
124+
- `test_process_launching_with_daemon.py::TestProcessLaunchingWithDaemon::test_launched_process_uid_gid_matches_config_when_applied[rust|cpp]`
125+
- `test_process_launching_with_daemon.py::TestProcessLaunchingWithDaemon::test_launched_process_scheduling_matches_config_when_applied[rust|cpp]`
126+
127+
Reason: both depend on `launch_manager` successfully gaining `cap_setuid,cap_setgid,cap_sys_nice`
128+
via `setcap` (see `daemon_helpers._grant_sandbox_capabilities`), which fails in CI for two
129+
independent reasons, either sufficient on its own:
130+
131+
1. **Sandboxed execution**: `linux-sandbox` sets `PR_SET_NO_NEW_PRIVS`, making any `setuid`/file-capability
132+
escalation inert at exec time, so even a successful `setcap` call has no effect on the process
133+
that actually runs.
134+
2. **No opt-in / no sudoers rule**: `FIT_ENABLE_SETCAP` is not set in the CI workflow, so the tests
135+
never attempt the `sudo -n setcap` path; and the CI runner has no passwordless sudoers entry for
136+
`setcap` regardless.
137+
138+
This is by design: `_grant_sandbox_capabilities` degrades gracefully (never raises) and the two
139+
capability-dependent subtests self-skip with a diagnostic reason instead of failing the build. All
140+
other subtests in `fit_lifecycle_daemon` only check same-uid process behavior and require no
141+
privilege escalation, so they run and pass normally in CI.
142+
51143
### ITF Tests (QEMU-based)
52144

53145
ITF tests run on a QEMU target and require the `itf-qnx-x86_64` config:
@@ -62,6 +154,7 @@ Test scenarios can be listed and run directly for debugging:
62154

63155
```sh
64156
bazel run //feature_integration_tests/test_scenarios/rust:rust_test_scenarios -- --list-scenarios
157+
bazel run //feature_integration_tests/test_scenarios/rust:rust_lifecycle_test_scenarios -- --list-scenarios
65158
bazel run --config=linux-x86_64 //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -- --list-scenarios
66159
```
67160

‎feature_integration_tests/configs/BUILD‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ exports_files(
1515
"dlt_config_qnx_x86_64.json",
1616
"dlt_config_x86_64.json",
1717
"qemu_bridge_config.json",
18+
"lifecycle_daemon_config.json",
19+
"lifecycle_daemon_parallel_launch_config.json",
20+
"lifecycle_daemon_retry_recovers_config.json",
21+
"lifecycle_daemon_retry_exhausts_config.json",
1822
],
1923
)
2024

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{
2+
"schema_version": 1,
3+
"defaults": {
4+
"deployment_config": {
5+
"bin_dir": "__FIT_RUNTIME_ROOT__/bin",
6+
"ready_timeout": 2.0,
7+
"shutdown_timeout": 2.0,
8+
"ready_recovery_action": {
9+
"restart": {
10+
"number_of_attempts": 2
11+
}
12+
},
13+
"recovery_action": {
14+
"switch_run_target": {
15+
"run_target": "fallback_run_target"
16+
}
17+
},
18+
"sandbox": {
19+
"uid": 1001,
20+
"gid": 1001,
21+
"scheduling_policy": "SCHED_OTHER",
22+
"scheduling_priority": 0
23+
}
24+
},
25+
"component_properties": {
26+
"application_profile": {
27+
"application_type": "Reporting",
28+
"is_self_terminating": false,
29+
"alive_supervision": {
30+
"reporting_cycle": 0.1,
31+
"min_indications": 1,
32+
"max_indications": 3,
33+
"failed_cycles_tolerance": 1
34+
}
35+
},
36+
"ready_condition": {
37+
"process_state": "Running"
38+
}
39+
}
40+
},
41+
"components": {
42+
"cpp_supervised_app": {
43+
"component_properties": {
44+
"binary_name": "cpp_supervised_app",
45+
"application_profile": {
46+
"application_type": "Reporting_And_Supervised"
47+
},
48+
"process_arguments": [
49+
"-d50"
50+
]
51+
},
52+
"deployment_config": {
53+
"environmental_variables": {
54+
"PROCESSIDENTIFIER": "cpp_supervised_app",
55+
"IDENTIFIER": "cpp_supervised_app"
56+
}
57+
}
58+
},
59+
"rust_supervised_app": {
60+
"component_properties": {
61+
"binary_name": "rust_supervised_app",
62+
"depends_on": [
63+
"cpp_supervised_app"
64+
],
65+
"application_profile": {
66+
"application_type": "Reporting_And_Supervised"
67+
},
68+
"process_arguments": [
69+
"-d50"
70+
]
71+
},
72+
"deployment_config": {
73+
"environmental_variables": {
74+
"PROCESSIDENTIFIER": "rust_supervised_app",
75+
"IDENTIFIER": "rust_supervised_app"
76+
}
77+
}
78+
}
79+
},
80+
"run_targets": {
81+
"Startup": {
82+
"depends_on": [
83+
"cpp_supervised_app",
84+
"rust_supervised_app"
85+
],
86+
"recovery_action": {
87+
"switch_run_target": {
88+
"run_target": "fallback_run_target"
89+
}
90+
}
91+
}
92+
},
93+
"initial_run_target": "Startup",
94+
"alive_supervision": {
95+
"evaluation_cycle": 0.05
96+
},
97+
"fallback_run_target": {
98+
"depends_on": [
99+
"cpp_supervised_app",
100+
"rust_supervised_app"
101+
]
102+
}
103+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{
2+
"schema_version": 1,
3+
"defaults": {
4+
"deployment_config": {
5+
"bin_dir": "__FIT_RUNTIME_ROOT__/bin",
6+
"ready_timeout": 2.0,
7+
"shutdown_timeout": 2.0,
8+
"ready_recovery_action": {
9+
"restart": {
10+
"number_of_attempts": 2
11+
}
12+
},
13+
"recovery_action": {
14+
"switch_run_target": {
15+
"run_target": "fallback_run_target"
16+
}
17+
},
18+
"sandbox": {
19+
"uid": 1001,
20+
"gid": 1001,
21+
"scheduling_policy": "SCHED_OTHER",
22+
"scheduling_priority": 0
23+
}
24+
},
25+
"component_properties": {
26+
"application_profile": {
27+
"application_type": "Reporting",
28+
"is_self_terminating": false,
29+
"alive_supervision": {
30+
"reporting_cycle": 0.1,
31+
"min_indications": 1,
32+
"max_indications": 3,
33+
"failed_cycles_tolerance": 1
34+
}
35+
},
36+
"ready_condition": {
37+
"process_state": "Running"
38+
}
39+
}
40+
},
41+
"components": {
42+
"cpp_supervised_app": {
43+
"component_properties": {
44+
"binary_name": "cpp_supervised_app",
45+
"application_profile": {
46+
"application_type": "Reporting_And_Supervised"
47+
},
48+
"process_arguments": [
49+
"-d50"
50+
]
51+
},
52+
"deployment_config": {
53+
"environmental_variables": {
54+
"PROCESSIDENTIFIER": "cpp_supervised_app",
55+
"IDENTIFIER": "cpp_supervised_app"
56+
}
57+
}
58+
},
59+
"rust_supervised_app": {
60+
"component_properties": {
61+
"binary_name": "rust_supervised_app",
62+
"application_profile": {
63+
"application_type": "Reporting_And_Supervised"
64+
},
65+
"process_arguments": [
66+
"-d50"
67+
]
68+
},
69+
"deployment_config": {
70+
"environmental_variables": {
71+
"PROCESSIDENTIFIER": "rust_supervised_app",
72+
"IDENTIFIER": "rust_supervised_app"
73+
}
74+
}
75+
}
76+
},
77+
"run_targets": {
78+
"Startup": {
79+
"depends_on": [
80+
"cpp_supervised_app",
81+
"rust_supervised_app"
82+
],
83+
"recovery_action": {
84+
"switch_run_target": {
85+
"run_target": "fallback_run_target"
86+
}
87+
}
88+
}
89+
},
90+
"initial_run_target": "Startup",
91+
"alive_supervision": {
92+
"evaluation_cycle": 0.05
93+
},
94+
"fallback_run_target": {
95+
"depends_on": [
96+
"cpp_supervised_app",
97+
"rust_supervised_app"
98+
]
99+
}
100+
}

0 commit comments

Comments
 (0)