Skip to content
Draft
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
12 changes: 12 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ build:eb-aarch64 --config=_common
build:eb-aarch64 --extra_toolchains=@score_ebclfsa_toolchain//:aarch64-linux-sdk_0.1.0-ebclfsa
build:eb-aarch64 --extra_toolchains=@ferrocene_aarch64_ebclfsa//:rust_ferrocene_toolchain
build:eb-aarch64 --platforms=@score_bazel_platforms//:aarch64-linux-sdk_0.1.0-ebclfsa
# GCC-13 emits aarch64 outline-atomic calls (__aarch64_ldadd4_acq_rel, __aarch64_cas*, ...)
# whose helpers live in libgcc.a. Ferrocene rustc drives the final link with -nodefaultlibs,
# so libgcc.a is never pulled in -> undefined references when Rust binaries link C++ libs.
# Force inline atomics instead.
build:eb-aarch64 --copt=-mno-outline-atomics

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

# user specific overrides (like proxy settings)
try-import %workspace%/user.bazelrc
303 changes: 265 additions & 38 deletions .github/workflows/test_and_docs.yml

Large diffs are not rendered by default.

991 changes: 434 additions & 557 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions bazel_common/score_basic_bazel.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ bazel_dep(name = "rules_pkg", version = "1.2.0")
# strip_prefix = "toolchain/rpm",
# )

bazel_dep(name = "flatbuffers", version = "25.9.23")
bazel_dep(name = "flatbuffers", version = "25.12.19")
git_override(
module_name = "flatbuffers",
commit = "187240970746d00bbd26b0f5873ed54d2477f9f3",
commit = "7e163021e59cca4f8e1e35a7c828b5c6b7915953",
remote = "https://github.com/google/flatbuffers.git",
)

Expand Down
4 changes: 2 additions & 2 deletions bazel_common/score_modules_target_sw.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
bazel_dep(name = "score_baselibs")
git_override(
module_name = "score_baselibs",
commit = "cab36dd7de92c282f67e78e88032e08585803528",
commit = "0130881e1c2482850a7cc85f0b789b7adf7a8e7c",
remote = "https://github.com/eclipse-score/baselibs.git",
)

Expand Down Expand Up @@ -65,7 +65,7 @@ git_override(
bazel_dep(name = "score_lifecycle_health")
git_override(
module_name = "score_lifecycle_health",
commit = "2f2e9c4c3e6c6b38abd0b72f78c8ffb96dc7f9e8",
commit = "34afee91b745e080dc2c409d9653df8c8632fb64",
remote = "https://github.com/eclipse-score/lifecycle.git",
)

Expand Down
17 changes: 14 additions & 3 deletions known_good.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
"target_sw": {
"score_baselibs": {
"repo": "https://github.com/eclipse-score/baselibs.git",
"hash": "cab36dd7de92c282f67e78e88032e08585803528",
"hash": "0130881e1c2482850a7cc85f0b789b7adf7a8e7c",
"metadata": {
"bazel_config": ["bl-x86_64-linux"],
"extra_test_config": [
"@score_baselibs//score/json:base_library=nlohmann",
"@score_baselibs//score/memory/shared/flags:use_typedshmd=False"
Expand All @@ -24,6 +25,7 @@
"repo": "https://github.com/eclipse-score/baselibs_rust.git",
"hash": "4050b62f298b639d27c092f63858642c3d08f9a0",
"metadata": {
"bazel_config": ["x86_64-linux", "ferrocene-coverage"],
"code_root_path": "//src/...",
"exclude_test_targets": [
"//src/log/score_log_fmt_macro:tests"
Expand All @@ -42,6 +44,7 @@
"//patches/communication:003-module-deps-visibility.patch"
],
"metadata": {
"bazel_config": ["linux_x86_64_score_gcc_12_2_0_posix"],
"code_root_path": "//score/mw/com/impl/...",
"exclude_test_targets": [
"//score/mw/com/impl:unit_test_runtime_single_exec",
Expand All @@ -56,6 +59,7 @@
"repo": "https://github.com/eclipse-score/persistency.git",
"hash": "4d1fa1ae3c55d27d0f1e863b8cdf59a148651c5d",
"metadata": {
"bazel_config": ["per-x86_64-linux"],
"extra_test_config": [
"@score_baselibs_rust//src/log:safety_level=qm",
"@score_baselibs//score/json:base_library=nlohmann",
Expand All @@ -72,6 +76,7 @@
"repo": "https://github.com/eclipse-score/orchestrator.git",
"hash": "c5bcbd39fc6e124a26b1d66abbfc696a3319c2a4",
"metadata": {
"bazel_config": ["x86_64-linux"],
"code_root_path": "//src/...",
"langs": [
"rust"
Expand All @@ -82,6 +87,7 @@
"repo": "https://github.com/eclipse-score/kyron.git",
"hash": "fc9218cc23d5fc62dd45b2e9dfcc9403e71ab27a",
"metadata": {
"bazel_config": ["x86_64-linux", "ferrocene-coverage"],
"code_root_path": "//src/...",
"langs": [
"rust"
Expand All @@ -90,15 +96,20 @@
},
"score_lifecycle_health": {
"repo": "https://github.com/eclipse-score/lifecycle.git",
"hash": "2f2e9c4c3e6c6b38abd0b72f78c8ffb96dc7f9e8",
"hash": "34afee91b745e080dc2c409d9653df8c8632fb64",
"metadata": {
"code_root_path": "//src/..."
"bazel_config": ["x86_64-linux"],
"code_root_path": "//score/...",
"extra_test_config": [
"test_lang_filters=-rust,-miri"
]
}
},
"score_logging": {
"repo": "https://github.com/eclipse-score/logging.git",
"hash": "0e9187f79a9935ca192779234b82f9d00dc4e335",
"metadata": {
"bazel_config": ["x86_64-linux"],
"extra_test_config": [
"@score_logging//score/datarouter/build_configuration_flags:persistent_logging=False",
"@score_logging//score/datarouter/build_configuration_flags:persistent_config_feature_enabled=False",
Expand Down
2 changes: 1 addition & 1 deletion rust_coverage/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ rust_coverage_report(
"linux-x86_64",
"ferrocene-coverage",
],
query = 'kind("rust_test", @score_lifecycle_health//src/...)',
query = 'kind("rust_test", @score_lifecycle_health//score/...)',
visibility = ["//visibility:public"],
)

Expand Down
228 changes: 228 additions & 0 deletions scripts/aggregate_quality_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#!/usr/bin/env python3
# *******************************************************************************
# 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
# *******************************************************************************
"""
Aggregate Stage 1 and Stage 2 quality reports into a single consolidated report.

Implements the upstream aggregation step of DR-008 Option 4: reads per-module
unit_test_summary.md and coverage_summary.md from downloaded stage2-report-*
artifacts and combines them with the Stage 1 integration result.

Usage:
python3 scripts/aggregate_quality_report.py \\
--stage1-result success \\
--stage2-result success \\
--stage2-dir _stage2_reports/ \\
>> "$GITHUB_STEP_SUMMARY"

The --stage2-dir is expected to contain subdirectories named
stage2-report-<module>, each holding unit_test_summary.md and
coverage_summary.md produced by quality_runners.py.
"""

import argparse
import json
import sys
from pathlib import Path

_STATUS_MAP = {
"success": "✅ Success",
"failure": "❌ Failure",
"cancelled": "⚪ Cancelled",
"skipped": "⚪ Skipped",
"": "⚪ Unknown",
}


def _format_status(result: str) -> str:
return _STATUS_MAP.get(result.lower().strip(), "⚪ Unknown")


def _extract_table_data_rows(md_path: Path) -> list[str]:
"""Return the data rows of the first markdown table found in md_path.

Skips the title line (starts with #), the header row, and the separator
row (contains ---), then collects all remaining pipe-delimited lines.
"""
if not md_path.exists():
return []

lines = md_path.read_text(encoding="utf-8").splitlines()
data_rows: list[str] = []
header_seen = False
separator_seen = False

for line in lines:
stripped = line.strip()
if not stripped.startswith("|"):
continue
if not header_seen:
header_seen = True
continue
if not separator_seen:
separator_seen = True
continue
if stripped:
data_rows.append(stripped)

return data_rows


def _excluded_test_targets(known_good_path: Path) -> list[tuple[str, list[str]]]:
"""Return [(module, [excluded targets])] for target_sw modules in known_good.json.

These targets are excluded from Stage 2 (e.g. they depend on dev_dependency-only
deps) and so are absent from the consolidated report — they are still covered by
each module's own CI. Surfacing them keeps the report honest about completeness.
"""
if not known_good_path.exists():
return []

data = json.loads(known_good_path.read_text(encoding="utf-8"))
target_sw = data.get("modules", {}).get("target_sw", {})

excluded: list[tuple[str, list[str]]] = []
for name in sorted(target_sw):
targets = target_sw[name].get("metadata", {}).get("exclude_test_targets", [])
if targets:
excluded.append((name, targets))
return excluded


def main() -> int:
parser = argparse.ArgumentParser(
description="Aggregate Stage 1 + Stage 2 quality reports (DR-008 Option 4).",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" python3 scripts/aggregate_quality_report.py \\\n"
" --stage1-result success \\\n"
" --stage2-result failure \\\n"
" --stage2-dir _stage2_reports/ \\\n"
" >> $GITHUB_STEP_SUMMARY\n"
),
)
parser.add_argument(
"--stage1-result",
default="",
help="GitHub Actions result of the stage1_integration job (success/failure/cancelled/skipped).",
)
parser.add_argument(
"--stage2-result",
default="",
help="GitHub Actions result of the stage2_module_validation job.",
)
parser.add_argument(
"--stage2-dir",
type=Path,
default=Path("_stage2_reports"),
help="Directory containing downloaded stage2-report-* artifact subdirectories.",
)
parser.add_argument(
"--known-good-path",
type=Path,
default=Path("known_good.json"),
help="Path to known_good.json (used to list test targets excluded from Stage 2).",
)
args = parser.parse_args()

out = sys.stdout

out.write("# S-CORE Quality Report — DR-008 Option 4\n\n")

# ------------------------------------------------------------------
# Stage 1 summary
# ------------------------------------------------------------------
out.write("## Stage 1 — Integration Results\n\n")
out.write("| Check | Status |\n")
out.write("|-------|--------|\n")
out.write(f"| Platform Build + Feature Integration Tests (linux-x86_64) | {_format_status(args.stage1_result)} |\n")
out.write("\n")

# ------------------------------------------------------------------
# Stage 2 summary — read per-module reports
# ------------------------------------------------------------------
out.write("## Stage 2 — Module Validation Results\n\n")

stage2_dir: Path = args.stage2_dir
ut_rows: list[str] = []
cov_rows: list[str] = []

if stage2_dir.exists():
for artifact_dir in sorted(stage2_dir.iterdir()):
if not artifact_dir.is_dir():
continue
if not artifact_dir.name.startswith("stage2-report-"):
continue
ut_rows.extend(_extract_table_data_rows(artifact_dir / "unit_test_summary.md"))
cov_rows.extend(_extract_table_data_rows(artifact_dir / "coverage_summary.md"))
else:
out.write(f"*Stage 2 reports directory not found: `{stage2_dir}`*\n\n")

if ut_rows:
out.write("### Unit Test Summary\n\n")
out.write("| module | passed | failed | skipped | total |\n")
out.write("|--------|--------|--------|---------|-------|\n")
for row in ut_rows:
out.write(f"{row}\n")
out.write("\n")
else:
out.write("*No Stage 2 unit test reports found.*\n\n")

if cov_rows:
out.write("### Coverage Summary\n\n")
out.write("| module | lines | functions | branches |\n")
out.write("|--------|-------|-----------|----------|\n")
for row in cov_rows:
out.write(f"{row}\n")
out.write("\n")

# ------------------------------------------------------------------
# Excluded test targets — completeness disclosure (DR-008 Q4)
# ------------------------------------------------------------------
excluded = _excluded_test_targets(args.known_good_path)
if excluded:
out.write("### Test Targets Excluded from Stage 2\n\n")
out.write(
"These targets are excluded from central Stage 2 execution (typically because "
"they depend on `dev_dependency`-only deps that are not visible from the resolved "
"graph). They are still validated by each module's own CI.\n\n"
)
out.write("| module | excluded test target |\n")
out.write("|--------|----------------------|\n")
for module_name, targets in excluded:
for target in targets:
out.write(f"| {module_name} | `{target}` |\n")
out.write("\n")

# ------------------------------------------------------------------
# Overall status
# ------------------------------------------------------------------
out.write("## Overall Status\n\n")
stage1_ok = args.stage1_result == "success"
stage2_ok = args.stage2_result in ("success", "skipped")

if stage1_ok and stage2_ok:
out.write("✅ All quality checks passed.\n")
else:
out.write("❌ One or more quality checks failed — see details above.\n\n")
out.write("| Stage | Result |\n")
out.write("|-------|--------|\n")
out.write(f"| Stage 1 (integration) | {_format_status(args.stage1_result)} |\n")
out.write(f"| Stage 2 (module validation) | {_format_status(args.stage2_result)} |\n")

return 0 if (stage1_ok and stage2_ok) else 1


if __name__ == "__main__":
sys.exit(main())
Loading
Loading