DR-008 Option 4: two-stage test-execution workflow (PR 2 of 2) - #280
DR-008 Option 4: two-stage test-execution workflow (PR 2 of 2)#280Subramanian-K812 wants to merge 5 commits into
Conversation
c9462ea to
fd3df76
Compare
d293277 to
b6df34d
Compare
e51b340 to
640dfee
Compare
|
The created documentation from the pull request is available at: docu-html |
04a7a73 to
e816620
Compare
PiotrKorkus
left a comment
There was a problem hiding this comment.
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
| @@ -0,0 +1,221 @@ | |||
| # Stage 2 — Constraint Sheet | |||
|
|
|||
| Living document. Amend it when a constraint turns out to be wrong, and record why in the | |||
There was a problem hiding this comment.
If thats AI generated and maintained, where is a skill / agent for that purpose?
There was a problem hiding this comment.
where is this file generated from? All modules should be from known_good
There was a problem hiding this comment.
not generated — known_good.json pins first-party repos by commit, these are registry deps pinned by version (confirmed via bazel mod graph --verbose: 2 of the 3 pins do move MVS's selection)
| # 0.9.1 declares the four *_rules_rust_miri toolchains score_lifecycle_health and | ||
| # score_persistency register in their own .bazelrc; 0.8.0 has none. ref_int cannot un-register | ||
| # them, so the version must match here. Locked in score_test_artifact_versions.MODULE.bazel. |
| - name: Set up Python 3 | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.12' |
|
|
||
| # One label for every Python unit test, so CI runs all of them by naming a single target. | ||
| test_suite( | ||
| name = "python_tests", |
There was a problem hiding this comment.
change name, its meaningless without looking into target
There was a problem hiding this comment.
Renamed to all_python_unit_tests.
| push: | ||
| branches: | ||
| - main | ||
| - Subramanian-K812_test_execution_upgrade # TEMP: remove after CI verified |
There was a problem hiding this comment.
it doesnt work because the same workflow name is with pull_request_target on main
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Clean disk space | ||
| uses: eclipse-score/more-disk-space@v1 |
There was a problem hiding this comment.
Bumped to v1.1
| if [ ! -s _module/module_graph.json ]; then | ||
| echo "::warning::no module graph captured for ${{ matrix.module.name }}"; exit 0 | ||
| fi | ||
| PYTHONPATH=scripts python3 scripts/known_good/verify_stage2_resolution.py \ |
There was a problem hiding this comment.
if script needs a PYTHONPATH to be set its very bad design. Fix with proper modules imports
| "bazel_config": [ | ||
| "stage2-gcc", | ||
| "stage2-rust" |
There was a problem hiding this comment.
it feels like it doesnt need to be placed in this json as its common for every module
There was a problem hiding this comment.
score_time and score_communication are exceptions for the above.
| "//score/mw/com/impl/bindings/lola/tracing:tracing_runtime_test" | ||
| ], | ||
| "bazel_config": [ | ||
| "stage2-rust" |
There was a problem hiding this comment.
why com has only rust?
There was a problem hiding this comment.
score_communication passes use_base_constraints_only=True to its own gcc.toolchain(), which changes its generated toolchain target name leading to referencing a target that doesn't exist.
|
Ran the reworked workflow end-to-end on a private fork: |
b22c037 to
a992474
Compare
587dcbb to
35d83cd
Compare
There was a problem hiding this comment.
this cannot be removed
| "//feature_integration_tests/configs:qemu_bridge_config.json", | ||
| "//images/qnx_x86_64:image", | ||
| ], | ||
| }) + ["conftest.py"], |
There was a problem hiding this comment.
Bazel version is not dependency to resolve. It is a given from ref_int
There was a problem hiding this comment.
None of the .bazelrc should be patched. We always take common config from ref_int
There was a problem hiding this comment.
None of the .bazelrc should be patched. We always take common config from ref_int
There was a problem hiding this comment.
None of the .bazelrc should be patched. We always take common config from ref_int
There was a problem hiding this comment.
every job has continue-on-error: true
this means that the workflow can never go red. kind of a no-op as PR check
| # Do not flood CI with unneeded previous runs in PR | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: ${{ github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') }} |
There was a problem hiding this comment.
github.ref_name is the base branch / main so this evaluates to false for PRs -> old runs never get cancelled.
can you check?
There was a problem hiding this comment.
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.
| resolved: dict[str, Module] = {} | ||
| provenance: dict[str, str] = {} | ||
| declared_overrides: dict[str, dict[str, str]] = {} | ||
| declared_deps: dict[str, dict[str, object]] = {} |
There was a problem hiding this comment.
It would be nice to have more descriptive data classes or enum classes to improve the readability.
May also be taken up as a follow up cleanup.
e.g.:
provenance:
class PinProvenance(StrEnum):
ASSERTED = "asserted"
INCIDENTAL = "incidental"
provenance: dict[str, PinProvenance] = {}
declared_overrides:
@dataclass(frozen=True)
class UncarriedOverride:
module_name: str
kind: str
source_file: str
reason: str
consequence: str
def to_dict(self) -> dict[str, str]:
return {
"module": self.module_name,
"kind": self.kind,
"file": self.source_file,
"reason": self.reason,
"consequence": self.consequence,
}
declared_overrides: dict[str, UncarriedOverride] = {}
declared_deps:
@dataclass(frozen=True)
class DependencySpec:
module_name: str
version: str | None
dev_dependency: bool
source_file: str
declared_deps: dict[str, DependencySpec] = {}
|
|
||
| # The completeness guard: every override ref_int declared that did not become a manifest entry. | ||
| uncarried = [entry for name, entry in sorted(declared_overrides.items()) if name not in resolved] | ||
| report = _build_report(resolved, provenance, uncarried, declared_by, _internal_drift(resolved, declared_deps)) |
There was a problem hiding this comment.
as mentioned above report would also benefit from a dataclass approach e.g.:
@dataclass(frozen=True)
class PinReport:
pin: dict[str, str]
provenance: PinProvenance
declared_versions: list[str]
verdict: Literal["unknown", "by_commit", "agree", "differs"] = "unknown"
direction: Literal["ref_int_lower", "ref_int_higher"] | None = None
def to_dict(self) -> dict[str, object]:
return {
"pin": self.pin,
"provenance": self.provenance.value,
"declared_versions": self.declared_versions,
"verdict": self.verdict,
"direction": self.direction,
}
| if entry["verdict"] == "differs": | ||
| conflicts.append(name) |
There was a problem hiding this comment.
conflicts appears to be a too strong term for the report entry.
I would expect ref_int_lower or ref_int_higher a typical case.
Can we stick to differs instead, this would also be more self-explanatory.
| return cls({name: Module.from_dict(name, md) for name, md in entries.items()}) | ||
|
|
||
| @staticmethod | ||
| def _parse_override_file(text: str) -> list[Module]: |
There was a problem hiding this comment.
The existing known_good.json makes use of patches (git_override or local_override).
Please also collect the patches so we are capable of injecting them in stage 2.
Related comment is in quality_runner.py
We apply the pachtes locally but the override mechanism is loosing them for dependencies since we use git_override.
| """ | ||
| module_bazel = Path(module_bazel) | ||
| original = self._strip_injection(module_bazel.read_text()) | ||
|
|
There was a problem hiding this comment.
Please add a warning generation in case the module is using:
- *_override
- highlight patches?
- non bazel version resolution dependency
- http_archive
- http_file
- git_repository
- new_git_repository
- local_repository
Overrides / Patches used by the module may conflict with the version imposed by reference integration.
Ideally we should do such a scan as a initial sanity check if the versions of known_good.json are updated.
If a selected module contain such elements it may cause build / test conflicts, being aware early could reduce time for issue identification.
| raise SystemExit(f"{args.modules_to_test[0]} is not a target_sw module in {args.known_good_path}") | ||
| print_centered(f"QR: Applying {module_under_test.name}'s own bazel_patches to {workspace}") | ||
| try: | ||
| apply_module_patches(module_under_test, workspace.resolve(), REF_INT_ROOT, log=print_centered) |
There was a problem hiding this comment.
patches are applied locally but the resolved.overwrite() patches are using single_version_override or git_override which leads to missing patches for dependencies in the stage 2 module run.
Ideally we can transport a patch of a dependency into the overwrite patching functionality.
| contents: write | ||
| pull-requests: write | ||
| on: | ||
| pull_request_target: # Allow forks to access secrets |
There was a problem hiding this comment.
This should only be required once we extend to QNX build and test in this workflow.
Since we use --config=linux-x86_64 shouldn't the following be sufficient?
Then we can also check the run in this PR.
| pull_request_target: # Allow forks to access secrets | |
| pull_request: |
| return tuple(int(part) for part in version.split(".")) if _RELEASE_RE.fullmatch(version) else None | ||
|
|
||
|
|
||
| def resolve_stage2_bazel_version(ref_int_version: str, module_version: str | None) -> str: |
There was a problem hiding this comment.
Why don't we demand a .bazelversion to be present under root of the module?
This way we could simplify the call and just give the root path of ref_int and module the module.
We could then also have the bazel version patching logic in this python file.
| - name: Export resolved dependency manifest | ||
| if: always() | ||
| run: | | ||
| mkdir -p artifacts/stage1-resolved-deps |
There was a problem hiding this comment.
Please provide all the steps also in a README for local replication of the workflow.
| actual = module_graph_versions(args.mod_graph) | ||
| resolved = json.loads(args.resolved.read_text(encoding="utf-8")) | ||
| resolved = resolved.get("modules", resolved) |
There was a problem hiding this comment.
since we base the check upon the bazel mod graph of the module and the resolved_version.json which both do not contain information about patches demanded by reference integration, the check is incomplete.
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 + overridemechanism") — 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:
exports the resolved dependency set as the
stage1-resolved-depsartifact(
resolved_versions.json).known_good.json(target_sw) — never hardcoded.known_goodcommit, overridesits
MODULE.bazelwith the Stage-1 resolved set (PR 1's mechanism), and runs themodule's own unit tests + coverage as the Bazel root (
//...).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); callsResolvedDependencies.overwritescripts/aggregate_quality_report.py— new, consolidated reportscripts/known_good/list_modules.py— new, dynamic Stage-2 matrix fromknown_good.jsonscripts/integration_test.py— derive build targets fromknown_good.json(dropbuild_config.json)Resolved pins + their config ripple (kept together so the tree is self-consistent)
known_good.json— pin bumps (baselibs, lifecycle) + per-modulebazel_config+ lifecyclecode_root_path/extra_test_configbazel_common/score_basic_bazel.MODULE.bazel— flatbuffers bumpbazel_common/score_modules_target_sw.MODULE.bazel— regenerated (baselibs, lifecycle)MODULE.bazel.lock— updated lockrust_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 fixKnown Stage 2 integration findings (surfaced, not fixed here)
The workflow surfaces two module-owned integration failures caused by the
baselibs bump (
score_loggingCheckSizeValidstaleuint8_tassumption;score_persistencyto_stringonstd::string_view).