Skip to content

DR-008 Option 4: two-stage test-execution workflow (PR 2 of 2) - #280

Open
Subramanian-K812 wants to merge 15 commits into
eclipse-score:mainfrom
qorix-group:Subramanian-K812_test_execution_upgrade
Open

Subramanian-K812 wants to merge 15 commits into
eclipse-score:mainfrom
qorix-group:Subramanian-K812_test_execution_upgrade

Conversation

@Subramanian-K812

Copy link
Copy Markdown
Contributor

DR-008 Option 4: two-stage test-execution workflow (PR 2 of 2)

Closes #264. This is the second of two PRs. It is stacked on PR 1
(Subramanian-K812_resolve_override_mechanism, "resolved-dependency resolve + override
mechanism") — please review/merge that one first. Until PR 1 merges, this PR's base branch
is the PR-1 branch, so the diff shown here is only the test-execution delta.

What this PR does

Rewires the quality workflow to the DR-008 Option 4 flow, using the resolve + override
mechanism added in PR 1:

  1. Stage 1 — integration: builds the platform, runs Feature Integration Tests, and
    exports the resolved dependency set as the stage1-resolved-deps artifact
    (resolved_versions.json).
  2. prepare_matrix: derives the Stage-2 module list dynamically from
    known_good.json (target_sw) — never hardcoded.
  3. Stage 2 — per module: checks the module out at its known_good commit, overrides
    its MODULE.bazel with the Stage-1 resolved set (PR 1's mechanism), and runs the
    module's own unit tests + coverage as the Bazel root (//...).
  4. aggregate: consolidates Stage 1 + Stage 2 into one quality report (and the
    release-tag test-report ZIP).

Files

Workflow & runners

  • .github/workflows/test_and_docs.yml — two-stage restructure (+ parallel docs build)
  • scripts/quality_runners.py — module-context mode (--module-dir / --resolved-deps); calls ResolvedDependencies.overwrite
  • scripts/aggregate_quality_report.pynew, consolidated report
  • scripts/known_good/list_modules.pynew, dynamic Stage-2 matrix from known_good.json
  • scripts/integration_test.py — derive build targets from known_good.json (drop build_config.json)

Resolved pins + their config ripple (kept together so the tree is self-consistent)

  • known_good.json — pin bumps (baselibs, lifecycle) + per-module bazel_config + lifecycle code_root_path/extra_test_config
  • bazel_common/score_basic_bazel.MODULE.bazel — flatbuffers bump
  • bazel_common/score_modules_target_sw.MODULE.bazel — regenerated (baselibs, lifecycle)
  • MODULE.bazel.lock — updated lock
  • rust_coverage/BUILD — lifecycle query //src/...//score/... (layout moved by the bump)
  • showcases/simple_lifecycle/BUILD — lifecycle bin path //src/...//score/launch_manager
  • .bazelrc — coverage atomic-gcov / -no-coverage; eb-aarch64 outline-atomics link fix

Known Stage 2 integration findings (surfaced, not fixed here)

The workflow surfaces two module-owned integration failures caused by the
baselibs bump (score_logging CheckSizeValid stale uint8_t assumption;
score_persistency to_string on std::string_view).

@Subramanian-K812
Subramanian-K812 force-pushed the Subramanian-K812_test_execution_upgrade branch from c9462ea to fd3df76 Compare July 7, 2026 08:49
@Subramanian-K812
Subramanian-K812 force-pushed the Subramanian-K812_test_execution_upgrade branch 2 times, most recently from d293277 to b6df34d Compare July 24, 2026 06:56
@Subramanian-K812
Subramanian-K812 force-pushed the Subramanian-K812_test_execution_upgrade branch 2 times, most recently from e51b340 to 640dfee Compare August 3, 2026 03:26
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

The created documentation from the pull request is available at: docu-html

@Subramanian-K812
Subramanian-K812 force-pushed the Subramanian-K812_test_execution_upgrade branch 4 times, most recently from 04a7a73 to e816620 Compare August 11, 2026 10:22

@PiotrKorkus PiotrKorkus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as the workflow has been completely reworked please create a private fork and execute it there, so we can see the proof of working in CI

pull_request_target wont allow to execute it here

Comment thread ci/stage2/CONSTRAINTS.md Outdated
Comment thread bazel_common/score_test_artifact_versions.MODULE.bazel
Comment thread bazel_common/score_rust_toolchains.MODULE.bazel Outdated
Comment thread .github/workflows/test_and_docs.yml Outdated
Comment thread scripts/BUILD Outdated
Comment thread .github/workflows/test_and_docs.yml Outdated
Comment thread .github/workflows/test_and_docs.yml Outdated
Comment thread .github/workflows/test_and_docs.yml Outdated
Comment thread known_good.json Outdated
Comment thread known_good.json Outdated
@Subramanian-K812

Subramanian-K812 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Ran the reworked workflow end-to-end on a private fork:
https://github.com/Subramanian-K812/reference_integration-ci-verify/actions/runs/32220961400

@Subramanian-K812
Subramanian-K812 force-pushed the Subramanian-K812_test_execution_upgrade branch 3 times, most recently from b22c037 to a992474 Compare August 21, 2026 09:58
Comment thread feature_integration_tests/itf/conftest.py
Comment thread feature_integration_tests/itf/BUILD
Comment thread scripts/known_good/bazel_version.py Outdated
Comment thread patches/logging/005-memory-shared-moved-to-communication.patch Outdated
Comment thread patches/persistency/004-safety-level-moved-to-log-rust.patch Outdated
Comment thread patches/time/001-memory-shared-moved-to-communication.patch
Comment thread .github/workflows/dr8_test_execution.yml
Comment thread .github/workflows/dr8_test_execution.yml Outdated

@OliverHeilwagen OliverHeilwagen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review focus ./scripts/known_good, excluding tests.
Transitive dependency are now correctly pinned 👍 .

Patches from reference integration are locally applied but patching in Stage 2 uses single_version_override and git_override without patches, which leads to missing patches for module dependencies, which were present in Stage 1.

Comment thread scripts/known_good/resolved_dependencies.py
Comment thread scripts/known_good/resolved_dependencies.py
Comment thread scripts/known_good/resolved_dependencies.py Outdated
Comment thread scripts/known_good/resolved_dependencies.py
Comment thread scripts/known_good/resolved_dependencies.py
Comment thread scripts/quality_runners.py
Comment thread .github/workflows/dr8_test_execution.yml Outdated
Comment thread scripts/known_good/bazel_version.py Outdated
Comment thread .github/workflows/dr8_test_execution.yml
Comment thread scripts/known_good/verify_stage2_resolution.py
@OliverHeilwagen

Copy link
Copy Markdown

@Subramanian-K812 Thanks a lot for the quick rework 👍 . Unfortunately I am not at work till 09.09., once I am back I can continue the re-review.

@MaximilianSoerenPollak MaximilianSoerenPollak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some questions from my side.
But overall I think it does what was asked according to the DR.

Comment thread .github/workflows/dr8_test_execution.yml Outdated
--stage1-result "${{ needs.stage1_integration.result }}" \
--stage2-result "${{ needs.stage2_module_validation.result }}" \
--stage2-dir "_stage2_reports/" \
>> "$GITHUB_STEP_SUMMARY"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an output lenght limit for the step summary?
If there is, will this hit it or is there no risk of that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked against GitHub's actual limit (1 MiB per step, upload fails with an annotation if exceeded) — a full 8-module report from the real script is ~4.3 KB, so this should not reach the cap.

still points at the old @score_baselibs labels, so redirect them and declare the
score_communication dependency they now require.

Source labels only; the module's .bazelrc has the same stale labels but ref_int no longer patches it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if it's stale labels should they not be removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but that would require patching the module’s .bazelrc, which has already been ruled out for this PR.

Comment thread scripts/known_good/BUILD
Comment on lines +25 to +27
# scripts/ on sys.path, so "from known_good...." resolves under 'bazel run' as it does for
# "python3 scripts/...". Propagates to every consumer of this library.
imports = [".."],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this does not break anything else?

Importing .. can have unexpected consequences in consumers of this py_library.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes agreed — better to scope or drop imports than leave it shared.


def repo_slug(repo_url: str) -> str:
"""Derive the 'owner/name' slug actions/checkout expects from a git URL."""
match = re.search(r"[:/]([^/:]+/[^/:]+?)(?:\.git)?/?$", repo_url or "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very sure this regex does more and less than what it is suppose to do (as all regex).
But I guess it's okay for now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This to be improved — switching to urllib.parse for the path url in known_good.json.

Comment on lines +44 to +51
try:
from known_good.models.known_good import load_known_good
from known_good.resolved_dependencies import repo_root, workspace_path
except ImportError:
if str(_HERE) not in sys.path:
sys.path.insert(0, str(_HERE))
from models.known_good import load_known_good # noqa: E402
from resolved_dependencies import repo_root, workspace_path # noqa: E402

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed again?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — same as noted below, the current try/except isn’t ideal and will be improved in a follow‑up.

Comment on lines -53 to -107
if module.repo in repo_commit_dict:
commit = repo_commit_dict[module.repo]

# Generate patches lines if bazel_patches exist
patches_lines = ""
if module.bazel_patches:
patches_lines = " patches = [\n"
for patch in module.bazel_patches:
patches_lines += f' "{patch}",\n'
patches_lines += " ],\n"
patch_strip_line = " patch_strip = 1,\n" if patches_lines else ""

if module.version:
# If version is provided, use bazel_dep with single_version_override
block = (
f'bazel_dep(name = "{module.name}")\n'
"single_version_override(\n"
f' module_name = "{module.name}",\n'
f"{patch_strip_line}"
f"{patches_lines}"
f' version = "{module.version}",\n'
")\n"
)
else:
if not module.repo or not commit:
logging.warning(
"Skipping module %s with missing repo or commit: repo=%s, commit=%s",
module.name,
module.repo,
commit,
)
continue

# Validate commit hash format (7-40 hex characters)
if not re.match(r"^[a-fA-F0-9]{7,40}$", commit):
logging.warning(
"Skipping module %s with invalid commit hash: %s",
module.name,
commit,
)
continue

# If no version, use bazel_dep with git_override
# Only include patch_strip if there are patches to apply
block = (
f'bazel_dep(name = "{module.name}")\n'
"git_override(\n"
f' module_name = "{module.name}",\n'
f' commit = "{commit}",\n'
f"{patch_strip_line}"
f"{patches_lines}"
f' remote = "{module.repo}",\n'
")\n"
)
blocks.append(block)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole logic not needed anymore, or moved somewhere else?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved, not removed — now imported from resolved_dependencies.generate_override_directive so Stage 1 and Stage 2 can't emit different git_override syntax.

Comment on lines +41 to +59
_HERE = Path(__file__).resolve().parent
try:
from known_good.module_patches import patch_relpath
from known_good.resolved_dependencies import (
INJECTED_PATCHES_PKG,
_collect_resolved_versions,
injected_override_names,
workspace_path,
)
except ImportError:
if str(_HERE) not in sys.path:
sys.path.insert(0, str(_HERE))
from module_patches import patch_relpath # noqa: E402
from resolved_dependencies import ( # noqa: E402
INJECTED_PATCHES_PKG,
_collect_resolved_versions,
injected_override_names,
workspace_path,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure there is a better way than to this try / except.

What are you trying to achieve with this again?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — the current try/except isn’t ideal; it’s a leftover from the same pattern used in files that are still invoked directly, which don’t have py_binary targets, so those workflows call the .py files directly instead of through bazel run.

@@ -0,0 +1,187 @@
#!/usr/bin/env python3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this file runs in CI, be careful that we already had the issue (in docs-as-code) that we ran into the upper limit of a pipe buffer in the CI output (64kb) due to folding it with long prints.
Just an FYI

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the heads-up, this file's output isn't piped into anything in CI — it runs as a plain bazel run step straight to the job log, so the pipe-buffer deadlock docs-as-code hit shouldn't be an issue here.

@PiotrKorkus PiotrKorkus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • nested bazel invokes in ferrocene_report.sh coming from the toolchain do not carry --noworkspace_rc which makes rust coverage fail
  • huge differences in number of executed tests. e.g. communication has 6500 test cases under //score/... but here we run only 3800. Why do we have differences when we should execute from the score of tested repo just with common config and deps?
  • how about appending single line with import of common bazelrc to the one in tested module and using the config from it? Would that work or?

@Subramanian-K812
Subramanian-K812 force-pushed the Subramanian-K812_test_execution_upgrade branch from 8b5251f to 891e471 Compare September 4, 2026 03:46

@OliverHeilwagen OliverHeilwagen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with suggestions.

Tests for the scripts were not in scope of this review.

From my point this is a nice initial version, which we can further be refactored to simplify the test complexity for the scripts. (ISO26262 tool qualification depending on the classification level).

Toolchain configuration overwrites for stage 2 are not be expected from my side for this PR, should however be planned in the backlog.

Current failing stage 2 steps need to be resolved, but are related to integration details (e.g. upgrade of coverage tooling score_tooling -> score_coverage) and not caused by general points in modified | added scripts.

"verdict": "unknown",
"direction": None,
}
if not wanted:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also take over the bazel_patches for a version pin.

        if module.bazel_patches:
            entry["pin"]["bazel_patches"] = module.bazel_patches

Comment on lines 645 to 656
``dev_dependency`` is not the discriminator and is never read: it does not predict whether
ref_int resolved a dependency. ``score_baselibs`` at 0.2.9 declares 13 dev-only deps that
ref_int *has* resolved and therefore pins (``score_tooling``, ``score_docs_as_code``,
``toolchains_llvm``, ...). A dependency is absent from the resolved set for an unrelated
reason: nothing in ref_int's own graph reaches it, or ref_int pins it with an override the
manifest cannot express (``rules_boost``, an ``archive_override``).

Scope is the module's declared deps plus ``closure()`` of the module and of each declared
dep. The closure is what makes the rule above safe rather than merely permissive: pinning
``score_tooling`` without ``lobster``/``trlc`` aborts with ``module lobster@0.0.0 not found
in registries``, since those are non-registry and resolvable only via a root override.
``graph`` is therefore required -- a caller that cannot supply the closure must not pin.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep the description free of examples


# After the module's competing overrides are stripped and before ref_int's are appended,
# what is left is the module's own resolution that survives injection.
hazards = module_resolution_hazards(original)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this check.

Out of scope for this PR.
In general i would wish for such a sanity check in stage 0, especially if we want to increase the frequency updating reference_integration. We should be informed early about *_override, patches or other 'anti patterns' within modules, which may cause issues during integration.
Hopefully this can reduce the time to analyse issues when updating known_good.json.

graph: DependencyGraph,
*,
module_under_test: str | None = None,
patch_source: Path | None = None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stage 2 bazel_dep patching is now from my point of view complete and covers all cases.

What out fo scope for this PR from my point of view is the toolchain related patches.
In stage 2 the toolchain configuration of the module is kept (e.g. QNX sdp version), which is incorrect from the big picture.

Since these updates are less frequent i don't see it as a high priority for now but the topic should be planned in the backlog.

default=repo_root() / "known_good.json",
help="Path to known_good.json (default: repo-root known_good.json).",
)
parser.add_argument("--group", default="target_sw", help="Module group to list (default: target_sw).")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend to add choices=("target_sw", "tooling"),

Comment on lines +141 to +143
if want is None:
by_commit += 1 # git_override'd: pinned by commit, not comparable by version
continue

@OliverHeilwagen OliverHeilwagen Sep 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since MODULE.bazel is source of truth for git_overrides can we add this as a data input to complete the check?
It is a little paranoid but would be nice round trip verification.

Comment on lines +764 to +778
if not STAGE2_RC.is_file():
raise SystemExit(f"Stage-2 centralized config not found at {STAGE2_RC}")
startup = stage2_startup_flags(args.modules_to_test[0])
dedicated_rc = stage2_module_rc(args.modules_to_test[0])
if args.modules_to_test[0] in MODULES_WITH_OWN_RC:
print_centered(
f"QR: Layering ref_int config {STAGE2_RC} over {args.modules_to_test[0]}'s own "
".bazelrc (registers a libclang toolchain ref_int cannot supply)"
)
elif dedicated_rc is not None:
print_centered(
f"QR: Using ref_int config {STAGE2_RC} plus {dedicated_rc}; the module's .bazelrc is not read"
)
else:
print_centered(f"QR: Using ref_int config {STAGE2_RC} only; the module's .bazelrc is not read")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional for this PR:
It would be nice to refactor the existing functionality into more readable steps for the main().

  • select_bazelrc
  • apply_patches
  • inject_resolved_dependencies
  • update_module_lock (currently remove)
  • pin_bazel_version
  • ...

e.g. wrapper class that can also store data and does not need to transport it in main to the next method.

Comment on lines +239 to +240
call = ["bazel"] + (startup or []) + ["mod", "graph", "--output=json", "--lockfile_mode=update"]
result = run_command(call, cwd=str(workspace))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please dump directly into file or is there a reason to see the full module graph in the log?

return ("integration conflict", named) if named else ("ref_int harness defect", [])


def run_unit_test_with_coverage(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope for this PR.
Would be nice to refactor the individual quality checks into their own classes | files, so we can import them in quality_runners and invoke them from main().

Comment thread ci/stage2/module.bazelrc
build:ferrocene-coverage --@rules_rust//rust/settings:extra_exec_rustc_flag=-Clink-dead-code
build:ferrocene-coverage --@rules_rust//rust/settings:extra_exec_rustc_flag=-Ccodegen-units=1
build:ferrocene-coverage --@rules_rust//rust/settings:extra_exec_rustc_flag=-Cdebuginfo=2
test:ferrocene-coverage --run_under=@score_tooling//coverage:llvm_profile_wrapper

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should already change this to score_coverage.
eclipse-score/score#3258

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ref_int - update test workflow according to DR8

5 participants