Skip to content

Commit fd3df76

Browse files
feat: DR-008 Option 4 two-stage test-execution workflow using resolved-deps mechanism
1 parent a8c0f1b commit fd3df76

12 files changed

Lines changed: 1180 additions & 643 deletions

File tree

.bazelrc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ build:eb-aarch64 --config=_common
6464
build:eb-aarch64 --extra_toolchains=@score_ebclfsa_toolchain//:aarch64-linux-sdk_0.1.0-ebclfsa
6565
build:eb-aarch64 --extra_toolchains=@ferrocene_aarch64_ebclfsa//:rust_ferrocene_toolchain
6666
build:eb-aarch64 --platforms=@score_bazel_platforms//:aarch64-linux-sdk_0.1.0-ebclfsa
67+
# GCC-13 emits aarch64 outline-atomic calls (__aarch64_ldadd4_acq_rel, __aarch64_cas*, ...)
68+
# whose helpers live in libgcc.a. Ferrocene rustc drives the final link with -nodefaultlibs,
69+
# so libgcc.a is never pulled in -> undefined references when Rust binaries link C++ libs.
70+
# Force inline atomics instead.
71+
build:eb-aarch64 --copt=-mno-outline-atomics
6772

6873
build:autosd-x86_64 --config=_common
6974
build:autosd-x86_64 --force_pic
@@ -103,6 +108,13 @@ test:unit-tests --test_tag_filters=-manual
103108
# Coverage configuration for C++
104109
coverage --features=coverage
105110
coverage --combined_report=lcov
111+
# Exclude tests the modules tag out of coverage (e.g. *_tsan_test): gcov-instrumenting a
112+
# ThreadSanitizer binary reports false data races on the non-atomic __gcov* counters.
113+
# Mirrors the modules' own .bazelrc so central-mode coverage matches in-module coverage.
114+
coverage --test_tag_filters=-no-coverage
115+
# Make gcov counter updates atomic so any remaining multithreaded coverage test is race-free.
116+
coverage --copt=-fprofile-update=atomic
117+
coverage --linkopt=-fprofile-update=atomic
106118

107119
# user specific overrides (like proxy settings)
108120
try-import %workspace%/user.bazelrc

.github/workflows/test_and_docs.yml

Lines changed: 265 additions & 38 deletions
Large diffs are not rendered by default.

MODULE.bazel.lock

Lines changed: 434 additions & 557 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bazel_common/score_basic_bazel.MODULE.bazel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ bazel_dep(name = "rules_pkg", version = "1.2.0")
2323
# strip_prefix = "toolchain/rpm",
2424
# )
2525

26-
bazel_dep(name = "flatbuffers", version = "25.9.23")
26+
bazel_dep(name = "flatbuffers", version = "25.12.19")
2727
git_override(
2828
module_name = "flatbuffers",
29-
commit = "187240970746d00bbd26b0f5873ed54d2477f9f3",
29+
commit = "7e163021e59cca4f8e1e35a7c828b5c6b7915953",
3030
remote = "https://github.com/google/flatbuffers.git",
3131
)
3232

bazel_common/score_modules_target_sw.MODULE.bazel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
bazel_dep(name = "score_baselibs")
1818
git_override(
1919
module_name = "score_baselibs",
20-
commit = "cab36dd7de92c282f67e78e88032e08585803528",
20+
commit = "0130881e1c2482850a7cc85f0b789b7adf7a8e7c",
2121
remote = "https://github.com/eclipse-score/baselibs.git",
2222
)
2323

@@ -65,7 +65,7 @@ git_override(
6565
bazel_dep(name = "score_lifecycle_health")
6666
git_override(
6767
module_name = "score_lifecycle_health",
68-
commit = "2f2e9c4c3e6c6b38abd0b72f78c8ffb96dc7f9e8",
68+
commit = "34afee91b745e080dc2c409d9653df8c8632fb64",
6969
remote = "https://github.com/eclipse-score/lifecycle.git",
7070
)
7171

known_good.json

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
"target_sw": {
44
"score_baselibs": {
55
"repo": "https://github.com/eclipse-score/baselibs.git",
6-
"hash": "cab36dd7de92c282f67e78e88032e08585803528",
6+
"hash": "0130881e1c2482850a7cc85f0b789b7adf7a8e7c",
77
"metadata": {
8+
"bazel_config": ["bl-x86_64-linux"],
89
"extra_test_config": [
910
"@score_baselibs//score/json:base_library=nlohmann",
1011
"@score_baselibs//score/memory/shared/flags:use_typedshmd=False"
@@ -24,6 +25,7 @@
2425
"repo": "https://github.com/eclipse-score/baselibs_rust.git",
2526
"hash": "4050b62f298b639d27c092f63858642c3d08f9a0",
2627
"metadata": {
28+
"bazel_config": ["x86_64-linux", "ferrocene-coverage"],
2729
"code_root_path": "//src/...",
2830
"exclude_test_targets": [
2931
"//src/log/score_log_fmt_macro:tests"
@@ -42,6 +44,7 @@
4244
"//patches/communication:003-module-deps-visibility.patch"
4345
],
4446
"metadata": {
47+
"bazel_config": ["linux_x86_64_score_gcc_12_2_0_posix"],
4548
"code_root_path": "//score/mw/com/impl/...",
4649
"exclude_test_targets": [
4750
"//score/mw/com/impl:unit_test_runtime_single_exec",
@@ -56,6 +59,7 @@
5659
"repo": "https://github.com/eclipse-score/persistency.git",
5760
"hash": "4d1fa1ae3c55d27d0f1e863b8cdf59a148651c5d",
5861
"metadata": {
62+
"bazel_config": ["per-x86_64-linux"],
5963
"extra_test_config": [
6064
"@score_baselibs_rust//src/log:safety_level=qm",
6165
"@score_baselibs//score/json:base_library=nlohmann",
@@ -72,6 +76,7 @@
7276
"repo": "https://github.com/eclipse-score/orchestrator.git",
7377
"hash": "c5bcbd39fc6e124a26b1d66abbfc696a3319c2a4",
7478
"metadata": {
79+
"bazel_config": ["x86_64-linux"],
7580
"code_root_path": "//src/...",
7681
"langs": [
7782
"rust"
@@ -82,6 +87,7 @@
8287
"repo": "https://github.com/eclipse-score/kyron.git",
8388
"hash": "fc9218cc23d5fc62dd45b2e9dfcc9403e71ab27a",
8489
"metadata": {
90+
"bazel_config": ["x86_64-linux", "ferrocene-coverage"],
8591
"code_root_path": "//src/...",
8692
"langs": [
8793
"rust"
@@ -90,15 +96,20 @@
9096
},
9197
"score_lifecycle_health": {
9298
"repo": "https://github.com/eclipse-score/lifecycle.git",
93-
"hash": "2f2e9c4c3e6c6b38abd0b72f78c8ffb96dc7f9e8",
99+
"hash": "34afee91b745e080dc2c409d9653df8c8632fb64",
94100
"metadata": {
95-
"code_root_path": "//src/..."
101+
"bazel_config": ["x86_64-linux"],
102+
"code_root_path": "//score/...",
103+
"extra_test_config": [
104+
"test_lang_filters=-rust,-miri"
105+
]
96106
}
97107
},
98108
"score_logging": {
99109
"repo": "https://github.com/eclipse-score/logging.git",
100110
"hash": "0e9187f79a9935ca192779234b82f9d00dc4e335",
101111
"metadata": {
112+
"bazel_config": ["x86_64-linux"],
102113
"extra_test_config": [
103114
"@score_logging//score/datarouter/build_configuration_flags:persistent_logging=False",
104115
"@score_logging//score/datarouter/build_configuration_flags:persistent_config_feature_enabled=False",

rust_coverage/BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ rust_coverage_report(
7272
"linux-x86_64",
7373
"ferrocene-coverage",
7474
],
75-
query = 'kind("rust_test", @score_lifecycle_health//src/...)',
75+
query = 'kind("rust_test", @score_lifecycle_health//score/...)',
7676
visibility = ["//visibility:public"],
7777
)
7878

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#!/usr/bin/env python3
2+
# *******************************************************************************
3+
# Copyright (c) 2026 Contributors to the Eclipse Foundation
4+
#
5+
# See the NOTICE file(s) distributed with this work for additional
6+
# information regarding copyright ownership.
7+
#
8+
# This program and the accompanying materials are made available under the
9+
# terms of the Apache License Version 2.0 which is available at
10+
# https://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# SPDX-License-Identifier: Apache-2.0
13+
# *******************************************************************************
14+
"""
15+
Aggregate Stage 1 and Stage 2 quality reports into a single consolidated report.
16+
17+
Implements the upstream aggregation step of DR-008 Option 4: reads per-module
18+
unit_test_summary.md and coverage_summary.md from downloaded stage2-report-*
19+
artifacts and combines them with the Stage 1 integration result.
20+
21+
Usage:
22+
python3 scripts/aggregate_quality_report.py \\
23+
--stage1-result success \\
24+
--stage2-result success \\
25+
--stage2-dir _stage2_reports/ \\
26+
>> "$GITHUB_STEP_SUMMARY"
27+
28+
The --stage2-dir is expected to contain subdirectories named
29+
stage2-report-<module>, each holding unit_test_summary.md and
30+
coverage_summary.md produced by quality_runners.py.
31+
"""
32+
33+
import argparse
34+
import json
35+
import sys
36+
from pathlib import Path
37+
38+
_STATUS_MAP = {
39+
"success": "✅ Success",
40+
"failure": "❌ Failure",
41+
"cancelled": "⚪ Cancelled",
42+
"skipped": "⚪ Skipped",
43+
"": "⚪ Unknown",
44+
}
45+
46+
47+
def _format_status(result: str) -> str:
48+
return _STATUS_MAP.get(result.lower().strip(), "⚪ Unknown")
49+
50+
51+
def _extract_table_data_rows(md_path: Path) -> list[str]:
52+
"""Return the data rows of the first markdown table found in md_path.
53+
54+
Skips the title line (starts with #), the header row, and the separator
55+
row (contains ---), then collects all remaining pipe-delimited lines.
56+
"""
57+
if not md_path.exists():
58+
return []
59+
60+
lines = md_path.read_text(encoding="utf-8").splitlines()
61+
data_rows: list[str] = []
62+
header_seen = False
63+
separator_seen = False
64+
65+
for line in lines:
66+
stripped = line.strip()
67+
if not stripped.startswith("|"):
68+
continue
69+
if not header_seen:
70+
header_seen = True
71+
continue
72+
if not separator_seen:
73+
separator_seen = True
74+
continue
75+
if stripped:
76+
data_rows.append(stripped)
77+
78+
return data_rows
79+
80+
81+
def _excluded_test_targets(known_good_path: Path) -> list[tuple[str, list[str]]]:
82+
"""Return [(module, [excluded targets])] for target_sw modules in known_good.json.
83+
84+
These targets are excluded from Stage 2 (e.g. they depend on dev_dependency-only
85+
deps) and so are absent from the consolidated report — they are still covered by
86+
each module's own CI. Surfacing them keeps the report honest about completeness.
87+
"""
88+
if not known_good_path.exists():
89+
return []
90+
91+
data = json.loads(known_good_path.read_text(encoding="utf-8"))
92+
target_sw = data.get("modules", {}).get("target_sw", {})
93+
94+
excluded: list[tuple[str, list[str]]] = []
95+
for name in sorted(target_sw):
96+
targets = target_sw[name].get("metadata", {}).get("exclude_test_targets", [])
97+
if targets:
98+
excluded.append((name, targets))
99+
return excluded
100+
101+
102+
def main() -> int:
103+
parser = argparse.ArgumentParser(
104+
description="Aggregate Stage 1 + Stage 2 quality reports (DR-008 Option 4).",
105+
formatter_class=argparse.RawDescriptionHelpFormatter,
106+
epilog=(
107+
"Examples:\n"
108+
" python3 scripts/aggregate_quality_report.py \\\n"
109+
" --stage1-result success \\\n"
110+
" --stage2-result failure \\\n"
111+
" --stage2-dir _stage2_reports/ \\\n"
112+
" >> $GITHUB_STEP_SUMMARY\n"
113+
),
114+
)
115+
parser.add_argument(
116+
"--stage1-result",
117+
default="",
118+
help="GitHub Actions result of the stage1_integration job (success/failure/cancelled/skipped).",
119+
)
120+
parser.add_argument(
121+
"--stage2-result",
122+
default="",
123+
help="GitHub Actions result of the stage2_module_validation job.",
124+
)
125+
parser.add_argument(
126+
"--stage2-dir",
127+
type=Path,
128+
default=Path("_stage2_reports"),
129+
help="Directory containing downloaded stage2-report-* artifact subdirectories.",
130+
)
131+
parser.add_argument(
132+
"--known-good-path",
133+
type=Path,
134+
default=Path("known_good.json"),
135+
help="Path to known_good.json (used to list test targets excluded from Stage 2).",
136+
)
137+
args = parser.parse_args()
138+
139+
out = sys.stdout
140+
141+
out.write("# S-CORE Quality Report — DR-008 Option 4\n\n")
142+
143+
# ------------------------------------------------------------------
144+
# Stage 1 summary
145+
# ------------------------------------------------------------------
146+
out.write("## Stage 1 — Integration Results\n\n")
147+
out.write("| Check | Status |\n")
148+
out.write("|-------|--------|\n")
149+
out.write(f"| Platform Build + Feature Integration Tests (linux-x86_64) | {_format_status(args.stage1_result)} |\n")
150+
out.write("\n")
151+
152+
# ------------------------------------------------------------------
153+
# Stage 2 summary — read per-module reports
154+
# ------------------------------------------------------------------
155+
out.write("## Stage 2 — Module Validation Results\n\n")
156+
157+
stage2_dir: Path = args.stage2_dir
158+
ut_rows: list[str] = []
159+
cov_rows: list[str] = []
160+
161+
if stage2_dir.exists():
162+
for artifact_dir in sorted(stage2_dir.iterdir()):
163+
if not artifact_dir.is_dir():
164+
continue
165+
if not artifact_dir.name.startswith("stage2-report-"):
166+
continue
167+
ut_rows.extend(_extract_table_data_rows(artifact_dir / "unit_test_summary.md"))
168+
cov_rows.extend(_extract_table_data_rows(artifact_dir / "coverage_summary.md"))
169+
else:
170+
out.write(f"*Stage 2 reports directory not found: `{stage2_dir}`*\n\n")
171+
172+
if ut_rows:
173+
out.write("### Unit Test Summary\n\n")
174+
out.write("| module | passed | failed | skipped | total |\n")
175+
out.write("|--------|--------|--------|---------|-------|\n")
176+
for row in ut_rows:
177+
out.write(f"{row}\n")
178+
out.write("\n")
179+
else:
180+
out.write("*No Stage 2 unit test reports found.*\n\n")
181+
182+
if cov_rows:
183+
out.write("### Coverage Summary\n\n")
184+
out.write("| module | lines | functions | branches |\n")
185+
out.write("|--------|-------|-----------|----------|\n")
186+
for row in cov_rows:
187+
out.write(f"{row}\n")
188+
out.write("\n")
189+
190+
# ------------------------------------------------------------------
191+
# Excluded test targets — completeness disclosure (DR-008 Q4)
192+
# ------------------------------------------------------------------
193+
excluded = _excluded_test_targets(args.known_good_path)
194+
if excluded:
195+
out.write("### Test Targets Excluded from Stage 2\n\n")
196+
out.write(
197+
"These targets are excluded from central Stage 2 execution (typically because "
198+
"they depend on `dev_dependency`-only deps that are not visible from the resolved "
199+
"graph). They are still validated by each module's own CI.\n\n"
200+
)
201+
out.write("| module | excluded test target |\n")
202+
out.write("|--------|----------------------|\n")
203+
for module_name, targets in excluded:
204+
for target in targets:
205+
out.write(f"| {module_name} | `{target}` |\n")
206+
out.write("\n")
207+
208+
# ------------------------------------------------------------------
209+
# Overall status
210+
# ------------------------------------------------------------------
211+
out.write("## Overall Status\n\n")
212+
stage1_ok = args.stage1_result == "success"
213+
stage2_ok = args.stage2_result in ("success", "skipped")
214+
215+
if stage1_ok and stage2_ok:
216+
out.write("✅ All quality checks passed.\n")
217+
else:
218+
out.write("❌ One or more quality checks failed — see details above.\n\n")
219+
out.write("| Stage | Result |\n")
220+
out.write("|-------|--------|\n")
221+
out.write(f"| Stage 1 (integration) | {_format_status(args.stage1_result)} |\n")
222+
out.write(f"| Stage 2 (module validation) | {_format_status(args.stage2_result)} |\n")
223+
224+
return 0 if (stage1_ok and stage2_ok) else 1
225+
226+
227+
if __name__ == "__main__":
228+
sys.exit(main())

0 commit comments

Comments
 (0)