Skip to content
138 changes: 138 additions & 0 deletions scripts/receive_therock/tests/therock_update_status_json_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,12 @@ def test_completed_fanout_build_refreshes_same_run_test_leaves() -> None:
_job("Build | py 3.12 | torch release/2.10 / Test | gfx110X-all"),
],
)
# Deliberately mismatched vs. the linux leaf above: this proves the match
# is keyed on (run_id, run_attempt) alone, not platform. That's safe in
# practice -- GitHub's run_id is unique per repository across every
# workflow/platform, so a real linux and windows run can never collide on
# one -- but it's an isolation technique, not a model of real data; don't
# read it as "a windows run can update a linux leaf" in production.
completed_build.classification.platform = "windows"
tusj._merge_run_into_document(
doc, completed_build, tusj._create_leaf(completed_build)
Expand All @@ -1784,6 +1790,138 @@ def test_completed_fanout_build_refreshes_same_run_test_leaves() -> None:
assert leaf.variants[0].status is Status.success


def test_fanout_projection_uses_variant_rollup_not_raw_run_conclusion() -> None:
# The build run's own top-level GitHub conclusion is not necessarily the
# worst-of its matrix cells (e.g. a cell whose nested test job failed does
# not always flip the run's own conclusion). A projected test leaf must
# take the worst-of its own variants, not the raw run conclusion.
doc = StatusDocument()
stale_test = _variant_run(
pipeline_type="pytorch",
pipeline_phase="test",
architectures=["gfx110X-all"],
run_id=902,
conclusion=None,
jobs=[
_job("Build | py 3.12 | torch release/2.10 / Build"),
_job(
"Build | py 3.12 | torch release/2.10 / Test | gfx110X-all",
conclusion=None,
completed=None,
),
],
)
doc.upsert_leaf(
"linux", "gfx110X-all", "pytorch", "test", tusj._create_leaf(stale_test)
)

# The run's own conclusion reports success even though its nested test
# job for this cell failed.
completed_build = _variant_run(
pipeline_type="pytorch",
pipeline_phase="build",
run_id=902,
conclusion="success",
jobs=[
_job("Build | py 3.12 | torch release/2.10 / Build"),
_job(
"Build | py 3.12 | torch release/2.10 / Test | gfx110X-all",
conclusion="failure",
),
],
)
completed_build.classification.platform = "windows"
tusj._merge_run_into_document(
doc, completed_build, tusj._create_leaf(completed_build)
)

leaf = doc.pipelines.pytorch.test["linux"]["gfx110X-all"]
assert leaf.status is Status.failure
assert leaf.variants is not None
assert leaf.variants[0].status is Status.failure


def test_fanout_projection_folds_raw_run_conclusion_into_rollup() -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i think here is an error

if i understand the test correctly:
pytorch test for gfx110X-all

have a in_progress run on linux
have a successful test run but cancel the entire pytorch testing run on windows

now at the end when checking the status of linux should copy from windows and show cancelled?

that does not make sense because windows and linux have separate pytorch workflows. so this kind of rollup should only happen within a platform.

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.

No, no. agree it reads as confusing, since it looks like it's testing platform mixing when it's really testing field-independence. the reason the test sets cancelled_build.classification.platform = "windows" while the stale leaf is "linux" (both keyed to the same run_id=903) is to deliberately prove the matching predicate really is just (run_id, run_attempt) and doesn't silently depend on platform equality. I'll drop the platform override in my new test (default to "linux", matching the stale leaf).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

and doesn't silently depend on platform equality

but it should depend on the platform. pytorch test on windows are totally independent of linux - even for the same arch

# The inverse of the case above: every reported cell looks clean, but the
# run itself was cancelled (e.g. a cell whose job never even started, so
# it never shows up in `variants` at all). The projected test leaf must
# still surface that cancellation rather than reporting the variants'
# all-success rollup verbatim.
doc = StatusDocument()
stale_test = _variant_run(
pipeline_type="pytorch",
pipeline_phase="test",
architectures=["gfx110X-all"],
run_id=903,
conclusion=None,
jobs=[
_job(
"Build | py 3.12 | torch release/2.10 / Test | gfx110X-all",
conclusion=None,
completed=None,
),
],
)
doc.upsert_leaf(
"linux", "gfx110X-all", "pytorch", "test", tusj._create_leaf(stale_test)
)

# Same platform as the stale leaf above: this test is about the
# cancellation-folding logic, not about the (run_id, run_attempt)-only
# matching (already covered by
# test_completed_fanout_build_refreshes_same_run_test_leaves), so it
# doesn't need a platform mismatch to make its point.
cancelled_build = _variant_run(
pipeline_type="pytorch",
pipeline_phase="build",
run_id=903,
conclusion="cancelled",
jobs=[
_job(
"Build | py 3.12 | torch release/2.10 / Test | gfx110X-all",
conclusion="success",
),
],
)
tusj._merge_run_into_document(
doc, cancelled_build, tusj._create_leaf(cancelled_build)
)

leaf = doc.pipelines.pytorch.test["linux"]["gfx110X-all"]
assert leaf.status is Status.cancelled


def test_variant_job_name_matches_uppercase_ancestor_segment() -> None:
# A calling orchestrator (e.g. rockrel) can wrap TheRock's own build job
# in a differently-cased ancestor segment, e.g.
# "Release | py 3.12 | JAX 0.11.0 / Build | py 3.12 | jax rocm-jaxlib-v0.11.0"
# The build job's own tail (lowercase, full ref) must still win over that
# ancestor. A nested test sub-job has no (py, ref) segment of its own and
# must fall back to the uppercase ancestor instead of being dropped.
run = _variant_run(
pipeline_type="jax",
pipeline_phase="build",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

as it is build phase shouldnt it kick out jobs that are "test"?

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.

No -- this is intentional and predates this PR (see test_reusable_matrix_nested_jobs_collapse_to_one_variant_per_cell, already on develop). test_pytorch_wheels.yml/test_linux_jax_wheels.yml run as a reusable workflow_call nested inside the delegated build orchestrator, not as their own workflow_dispatch -- so their jobs land in the same job list, same run id, as the build. _variants_from_jobs deliberately groups every job sharing a (py, ref) cell, build and nested test alike, into one Variant, because that's the only way _refresh_same_run_fanout_tests can see a cell's test outcome before the top-level run finishes. Filtering out "Test"-named jobs here would break that early-projection path. I expanded the docstring on _refresh_same_run_fanout_tests (6f2438a) to spell this out -- let me know if that answers it or if you want the filtering anyway for a different reason.

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.

PyTorch/JAX test coverage runs as a reusable workflow_call nested inside the delegated build orchestrator so build and test jobs share one job list/run id, and _variants_from_jobs intentionally groups them together per py, ref cell so test outcomes can be seen before the top-level run finishes.

jobs=[
_job(
"build_jax_wheels / Release | py 3.12 | JAX 0.11.0 / "
"Build | py 3.12 | jax rocm-jaxlib-v0.11.0"
),
_job(
"build_jax_wheels / Release | py 3.13 | JAX 0.11.0 / "
"Test | gfx94X-dcgpu | linux-gfx942-1gpu-ccs-csp-ossci-rocm / "
"Test JAX | gfx94X-dcgpu",
conclusion="cancelled",
),
],
)
variants = tusj._derive_variants(run)
by_py = {v.matrix["py"]: v for v in variants}
assert by_py["3.12"].matrix["jax_ref"] == "rocm-jaxlib-v0.11.0"
assert by_py["3.12"].status is Status.success
assert by_py["3.13"].matrix["jax_ref"] == "0.11.0"
assert by_py["3.13"].status is Status.cancelled


def test_skip_workflow_names_are_all_disregarded(tmp_path: Path) -> None:
# Guards the generic `_SKIP_WORKFLOW_NAMES` mechanism itself, not just the
# one workflow it was introduced for: whatever is in the set must be
Expand Down
56 changes: 46 additions & 10 deletions scripts/receive_therock/therock_update_status_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,14 @@ def _update_document_metadata(
# Matrix-cell job name for fan-out builds, e.g. TheRock's
# "Build | py 3.12 | torch release/2.10" (pytorch)
# "Build | py 3.12 | jax rocm-jaxlib-v0.9" (jax)
# `.search` (not fullmatch) so a reusable-workflow prefix/suffix still matches.
_MATRIX_JOB_RE = re.compile(r"py\s+(?P<py>\S+)\s*\|\s*(?:torch|jax)\s+(?P<ref>\S+)")
# Case-insensitive: a calling orchestrator's own composite job name can wrap
# this in a differently-cased ancestor segment, e.g. rockrel's
# "Release | py 3.12 | JAX 0.11.0 / Build | py 3.12 | jax rocm-jaxlib-v0.11.0"
# -- the nested Test sub-job under that same cell has no (py, ref) of its own
# and relies entirely on that ancestor segment to be recognized.
_MATRIX_JOB_RE = re.compile(
r"py\s+(?P<py>\S+)\s*\|\s*(?:torch|jax)\s+(?P<ref>\S+)", re.IGNORECASE
)

# pipeline_type -> the matrix axis key used in the variant (reference schema:
# pytorch cells key the ref as "torch", jax cells as "jax_ref").
Expand Down Expand Up @@ -382,9 +388,17 @@ def _variants_from_jobs(
cells: dict[tuple[str, str], list[WorkflowJobRecord]] = {}
order: list[tuple[str, str]] = []
for j in jobs:
match = _MATRIX_JOB_RE.search(j.name)
if not match:
# Take the *last* match, not the first: a nested job's composite name
# is "ancestor segment(s) / ... / own segment", and the own segment
# (closest to the actual job) is the authoritative (py, ref) -- e.g.
# a build job's own tail carries the full ref, while an orchestrator
# ancestor segment upstream of it may carry a shorter/looser one. A
# job with no segment of its own (a nested test sub-job) falls back
# to whichever ancestor segment matched.
matches = list(_MATRIX_JOB_RE.finditer(j.name))
if not matches:
continue
match = matches[-1]
key = (match.group("py"), match.group("ref"))
if key not in cells:
cells[key] = []
Expand Down Expand Up @@ -492,11 +506,30 @@ def _refresh_same_run_fanout_tests(
) -> bool:
"""Refresh same-run test leaves from a completed fan-out workflow snapshot.

Delegated PyTorch/JAX release workflows report the shared entry run id.
Early notifications can project the run's job list into per-arch test
leaves while some matrix cells are still in progress; the final top-level
completion is classified as the build phase, so it would otherwise leave
those same-run test leaves stale.
PyTorch/JAX test coverage (`test_pytorch_wheels.yml` / `test_linux_jax_wheels.yml`)
is invoked as a reusable `workflow_call` nested inside the delegated release
workflow -- not dispatched as its own top-level run -- so its jobs land in
the *same* run id, job list, and webhook notifications as the entry build.
There is no job-name-level split between "build" and "test" jobs: the
registry classifies the whole run as `pipeline_type`/`pipeline_phase="build"`
(see `WORKFLOW_SPECS`), and `_variants_from_jobs` already groups every job
sharing a (py, ref) cell -- build and nested test alike -- into one
`Variant` per cell (see `test_reusable_matrix_nested_jobs_collapse_to_one_variant_per_cell`).
Early notifications can project that job-list snapshot into per-arch test
leaves (keyed by the same run id) while some cells are still in progress;
the final notification is still classified as the build phase, so without
this function those same-run test leaves would go stale once the build
itself is done.

`leaf.status` is this run's own top-level GitHub conclusion. It is not
necessarily the worst-of its `variants` (e.g. a matrix cell whose nested
test job failed/cancelled does not always flip the run's own conclusion,
or a cell can be entirely missing from `variants` if its job never
started). Fold it into the rollup rather than only using it as an
empty-variants fallback, so a terminal failure/cancellation at the run
level cannot be masked by whatever the individual cells happened to report
-- mirroring what `_merge_matrix_build_leaf` does for the build leaf
itself.
"""
cls = workflow_run.classification
if (
Expand All @@ -508,6 +541,9 @@ def _refresh_same_run_fanout_tests(
):
return False

projected_status = rollup_statuses(
(*(v.status for v in leaf.variants), leaf.status), leaf.status
)
pipeline = getattr(doc.pipelines, cls.pipeline_type)
wrote = False
for phase_map in (pipeline.test, pipeline.test_full):
Expand All @@ -519,7 +555,7 @@ def _refresh_same_run_fanout_tests(
continue
if not existing.should_replace(leaf):
continue
existing.status = leaf.status
existing.status = projected_status
existing.completed_at = leaf.completed_at
existing.variants = leaf.variants
wrote = True
Expand Down
Loading