ci: cram: resolve comma-separated component unions
Multi-component changes currently require separate CRAM jobs. They repeat the board boot. The automatic selector needs one resolver invocation that can cover all affected component buckets. Accept comma-separated components and build their ordered union from the legacy full-suite order. Prefix sanity tests once, remove duplicate tests and retain the existing behavior for every single component and full. Compare all five legacy selections on all six boards with the CI-7 base. All 30 outputs are byte-identical. Verify forward and reverse two-component unions on all six boards, including stable ordering and uniqueness. Assisted-by: codex:gpt-5.6-sol References: CI-9 Signed-off-by: Petr Štetiar <petr.stetiar@prplfoundation.org>
This commit is contained in:
parent
abebe0921f
commit
d001612fee
@ -120,9 +120,15 @@ dependency match the job:
|
||||
```
|
||||
|
||||
The command prints the ordered test paths for cram. Valid component values are
|
||||
`full`, `sanity`, `prplmesh`, `lcm`, and `prplos`. Use `sanity` to inspect the
|
||||
always-run block. The `sanity` value is for inspection, not pipeline job
|
||||
selection.
|
||||
`full`, `sanity`, `prplmesh`, `lcm`, and `prplos`. A comma-separated value such
|
||||
as `prplmesh,lcm` resolves the ordered union: the sanity block is emitted once,
|
||||
then each selected bucket in manifest order, so the job still uses one boot.
|
||||
Repeated components and tests are de-duplicated. Selecting `full`, or selecting
|
||||
all three split buckets, collapses to the legacy full-suite ordering. Do not put
|
||||
spaces around the commas.
|
||||
|
||||
Use `sanity` to inspect the always-run block. The `sanity` value is for
|
||||
inspection, not pipeline job selection.
|
||||
|
||||
Run the manifest lint from that environment as well:
|
||||
|
||||
|
||||
@ -361,6 +361,39 @@ def partition_tests(
|
||||
return buckets
|
||||
|
||||
|
||||
def select_component_tests(
|
||||
full: Sequence[Path],
|
||||
buckets: Mapping[str, List[Path]],
|
||||
components: Sequence[str],
|
||||
) -> List[Path]:
|
||||
"""Select an ordered component union with the sanity tests first."""
|
||||
|
||||
allowed = {"full", "sanity", "prplmesh", "lcm", "prplos"}
|
||||
unknown = sorted(set(components) - allowed)
|
||||
if unknown:
|
||||
raise BucketingError(f"unknown components: {', '.join(unknown)}")
|
||||
if not components:
|
||||
raise BucketingError("at least one component is required")
|
||||
|
||||
unique = tuple(dict.fromkeys(components))
|
||||
if "full" in unique:
|
||||
return list(full)
|
||||
if len(unique) == 1:
|
||||
component = unique[0]
|
||||
if component == "sanity":
|
||||
return list(buckets["sanity"])
|
||||
return list(buckets["sanity"] + buckets[component])
|
||||
|
||||
selected = set(buckets["sanity"])
|
||||
for component in unique:
|
||||
if component != "sanity":
|
||||
selected.update(buckets[component])
|
||||
|
||||
sanity = list(buckets["sanity"])
|
||||
sanity_set = set(sanity)
|
||||
return sanity + [path for path in full if path in selected and path not in sanity_set]
|
||||
|
||||
|
||||
def render_test_paths(paths: Sequence[Path]) -> str:
|
||||
"""Render ordered test paths for cram's command-line arguments."""
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ from cram_component_bucketing import (
|
||||
partition_tests,
|
||||
render_test_paths,
|
||||
resolve_manifest_path,
|
||||
select_component_tests,
|
||||
)
|
||||
|
||||
|
||||
@ -37,7 +38,7 @@ def parse_args(argv: Sequence[str]) -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--component",
|
||||
required=True,
|
||||
choices=("full", "sanity", "prplmesh", "lcm", "prplos"),
|
||||
help="component name or comma-separated component union",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
@ -50,12 +51,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
expanded = expand_manifest(args.test_root, manifest)
|
||||
full = collect_board_tests(args.test_root, args.board)
|
||||
buckets = partition_tests(full, args.test_root, expanded)
|
||||
if args.component == "full":
|
||||
selected = full
|
||||
elif args.component == "sanity":
|
||||
selected = buckets["sanity"]
|
||||
else:
|
||||
selected = buckets["sanity"] + buckets[args.component]
|
||||
components = args.component.split(",")
|
||||
if any(not component for component in components):
|
||||
raise BucketingError("component list contains an empty value")
|
||||
selected = select_component_tests(full, buckets, components)
|
||||
except BucketingError as error:
|
||||
print(f"error: {error}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
Loading…
Reference in New Issue
Block a user