Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

309 lines
10 KiB
Python

# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
import json
import os
import sys
from pathlib import Path
from unittest.mock import patch
import pytest
# Add github_actions to path so workflow_summary is importable.
sys.path.insert(0, os.fspath(Path(__file__).parent.parent))
from workflow_summary import (
FailedJobInfo,
evaluate_results,
fetch_failed_jobs,
main,
parse_needs_json,
)
# ---------------------------------------------------------------------------
# Fixtures: realistic needs JSON blobs
# ---------------------------------------------------------------------------
ALL_SUCCESS = {
"setup": {"result": "success", "outputs": {}},
"linux_build_and_test": {"result": "success", "outputs": {}},
"windows_build_and_test": {"result": "success", "outputs": {}},
}
ONE_FAILURE = {
"setup": {"result": "success", "outputs": {}},
"linux_build_and_test": {"result": "failure", "outputs": {}},
"windows_build_and_test": {"result": "success", "outputs": {}},
}
FAILURE_WITH_CONTINUE_ON_ERROR = {
"setup": {"result": "success", "outputs": {}},
"linux_build_and_test": {
"result": "failure",
"outputs": {"continue_on_error": "true"},
},
"windows_build_and_test": {"result": "success", "outputs": {}},
}
ONE_CANCELLED = {
"setup": {"result": "success", "outputs": {}},
"linux_build_and_test": {"result": "cancelled", "outputs": {}},
}
ONE_SKIPPED = {
"setup": {"result": "success", "outputs": {}},
"windows_build_and_test": {"result": "skipped", "outputs": {}},
}
# ---------------------------------------------------------------------------
# parse_needs_json
# ---------------------------------------------------------------------------
class TestParseNeedsJson:
def test_all_success(self):
jobs = parse_needs_json(json.dumps(ALL_SUCCESS))
assert len(jobs) == 3
assert all(j.result == "success" for j in jobs)
assert all(not j.continue_on_error for j in jobs)
def test_continue_on_error_parsed(self):
jobs = parse_needs_json(json.dumps(FAILURE_WITH_CONTINUE_ON_ERROR))
by_name = {j.name: j for j in jobs}
assert by_name["linux_build_and_test"].continue_on_error is True
assert by_name["setup"].continue_on_error is False
def test_missing_outputs_key(self):
"""Jobs with no outputs key should still parse (continue_on_error=False)."""
needs = {"build": {"result": "success"}}
jobs = parse_needs_json(json.dumps(needs))
assert len(jobs) == 1
assert jobs[0].continue_on_error is False
def test_null_outputs(self):
"""Jobs with null outputs should still parse."""
needs = {"build": {"result": "success", "outputs": None}}
jobs = parse_needs_json(json.dumps(needs))
assert len(jobs) == 1
assert jobs[0].continue_on_error is False
def test_invalid_json_raises(self):
with pytest.raises(json.JSONDecodeError):
parse_needs_json("not json")
def test_non_dict_top_level_raises(self):
with pytest.raises(AssertionError, match="Expected a JSON object"):
parse_needs_json("[]")
def test_non_dict_job_raises(self):
with pytest.raises(AssertionError, match="JSON object for job"):
parse_needs_json(json.dumps({"build": "not a dict"}))
# ---------------------------------------------------------------------------
# evaluate_results
# ---------------------------------------------------------------------------
class TestEvaluateResults:
def test_all_success(self):
jobs = parse_needs_json(json.dumps(ALL_SUCCESS))
failed, ok = evaluate_results(jobs)
assert len(failed) == 0
assert len(ok) == 3
def test_one_failure(self):
jobs = parse_needs_json(json.dumps(ONE_FAILURE))
failed, ok = evaluate_results(jobs)
assert len(failed) == 1
assert failed[0].name == "linux_build_and_test"
assert len(ok) == 2
def test_continue_on_error_not_failed(self):
jobs = parse_needs_json(json.dumps(FAILURE_WITH_CONTINUE_ON_ERROR))
failed, ok = evaluate_results(jobs)
assert len(failed) == 0
assert len(ok) == 3
def test_cancelled_is_failure(self):
jobs = parse_needs_json(json.dumps(ONE_CANCELLED))
failed, ok = evaluate_results(jobs)
assert len(failed) == 1
assert failed[0].name == "linux_build_and_test"
assert failed[0].result == "cancelled"
def test_skipped_is_ok(self):
jobs = parse_needs_json(json.dumps(ONE_SKIPPED))
failed, ok = evaluate_results(jobs)
assert len(failed) == 0
assert len(ok) == 2
def test_empty_needs(self):
failed, ok = evaluate_results([])
assert len(failed) == 0
assert len(ok) == 0
# ---------------------------------------------------------------------------
# fetch_failed_jobs
# ---------------------------------------------------------------------------
class TestFetchFailedJobs:
def test_parses_failed_jobs(self):
api_response = {
"jobs": [
{
"name": "Build",
"conclusion": "success",
"html_url": "https://github.com/test/run/1",
},
{
"name": "Test hip-tests (shard 1/1)",
"conclusion": "failure",
"html_url": "https://github.com/test/run/2",
},
{
"name": "Test rocthrust (shard 1/1)",
"conclusion": "failure",
"html_url": "https://github.com/test/run/3",
},
]
}
with patch("workflow_summary.gha_send_request", return_value=api_response):
failed = fetch_failed_jobs("owner/repo", "12345")
assert len(failed) == 2
assert failed[0] == FailedJobInfo(
name="Test hip-tests (shard 1/1)", html_url="https://github.com/test/run/2"
)
assert failed[1] == FailedJobInfo(
name="Test rocthrust (shard 1/1)", html_url="https://github.com/test/run/3"
)
def test_no_failures(self):
api_response = {
"jobs": [
{"name": "Build", "conclusion": "success", "html_url": ""},
]
}
with patch("workflow_summary.gha_send_request", return_value=api_response):
failed = fetch_failed_jobs("owner/repo", "12345")
assert len(failed) == 0
def test_empty_jobs_list(self):
with patch("workflow_summary.gha_send_request", return_value={"jobs": []}):
failed = fetch_failed_jobs("owner/repo", "12345")
assert len(failed) == 0
def test_paginates_when_more_than_100_jobs(self):
"""Runs with >100 jobs require multiple API pages."""
# Page 1: 100 successful jobs.
page1 = {
"jobs": [
{"name": f"Job {i}", "conclusion": "success", "html_url": ""}
for i in range(100)
]
}
# Page 2: 2 jobs, one failed.
page2 = {
"jobs": [
{"name": "Job 100", "conclusion": "success", "html_url": ""},
{
"name": "Job 101",
"conclusion": "failure",
"html_url": "https://github.com/test/run/101",
},
]
}
with patch("workflow_summary.gha_send_request", side_effect=[page1, page2]):
failed = fetch_failed_jobs("owner/repo", "12345")
assert len(failed) == 1
assert failed[0] == FailedJobInfo(
name="Job 101", html_url="https://github.com/test/run/101"
)
# ---------------------------------------------------------------------------
# main (integration)
# ---------------------------------------------------------------------------
class TestMain:
def test_all_success_returns_zero(self, capsys):
rc = main(["--needs-json", json.dumps(ALL_SUCCESS)])
assert rc == 0
assert "succeeded" in capsys.readouterr().out
def test_failure_returns_one(self, capsys):
rc = main(["--needs-json", json.dumps(ONE_FAILURE)])
assert rc == 1
assert "failed" in capsys.readouterr().out
def test_continue_on_error_returns_zero(self, capsys):
rc = main(["--needs-json", json.dumps(FAILURE_WITH_CONTINUE_ON_ERROR)])
assert rc == 0
assert "succeeded" in capsys.readouterr().out
def test_failure_with_api_prints_urls(self, capsys):
api_response = {
"jobs": [
{
"name": "Test hip-tests",
"conclusion": "failure",
"html_url": "https://github.com/test/run/2",
},
]
}
with patch("workflow_summary.gha_send_request", return_value=api_response):
rc = main(
[
"--needs-json",
json.dumps(ONE_FAILURE),
"--github-repository",
"owner/repo",
"--github-run-id",
"12345",
]
)
assert rc == 1
out = capsys.readouterr().out
assert "Test hip-tests" in out
assert "https://github.com/test/run/2" in out
def test_failure_without_api_args_still_works(self, capsys, monkeypatch):
"""Without repo/run-id, the script should still report failures."""
monkeypatch.delenv("GITHUB_REPOSITORY", raising=False)
monkeypatch.delenv("GITHUB_RUN_ID", raising=False)
rc = main(["--needs-json", json.dumps(ONE_FAILURE)])
assert rc == 1
assert "failed" in capsys.readouterr().out
def test_failure_with_api_error_still_fails(self, capsys):
"""API errors should not prevent the script from reporting failures."""
from github_actions_api import GitHubAPIError
with patch(
"workflow_summary.gha_send_request",
side_effect=GitHubAPIError("test error"),
):
rc = main(
[
"--needs-json",
json.dumps(ONE_FAILURE),
"--github-repository",
"owner/repo",
"--github-run-id",
"12345",
]
)
assert rc == 1
out = capsys.readouterr().out
assert "Could not fetch job details" in out