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
372 lines
13 KiB
Python
372 lines
13 KiB
Python
"""Tests for generate_manifest_diff_report.py."""
|
|
|
|
import os
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
from urllib.error import HTTPError
|
|
|
|
sys.path.insert(0, os.fspath(Path(__file__).parent.parent))
|
|
|
|
from generate_manifest_diff_report import (
|
|
create_table,
|
|
determine_status,
|
|
fetch_commits_in_range,
|
|
format_commit_date,
|
|
generate_non_superrepo_html,
|
|
get_api_base_from_url,
|
|
is_revert,
|
|
ManifestDiff,
|
|
parse_args,
|
|
resolve_commits,
|
|
Submodule,
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# Pure Function Unit Tests
|
|
# =============================================================================
|
|
|
|
|
|
class GetApiBaseFromUrlTest(unittest.TestCase):
|
|
"""Tests for get_api_base_from_url function."""
|
|
|
|
def test_https_url(self):
|
|
"""Convert HTTPS GitHub URL to API base."""
|
|
url = "https://github.com/ROCm/rocBLAS.git"
|
|
result = get_api_base_from_url(url, "rocBLAS")
|
|
|
|
self.assertEqual(result, "https://api.github.com/repos/ROCm/rocBLAS")
|
|
|
|
def test_ssh_url(self):
|
|
"""Convert SSH GitHub URL to API base."""
|
|
url = "git@github.com:ROCm/MIOpen.git"
|
|
result = get_api_base_from_url(url, "MIOpen")
|
|
|
|
self.assertEqual(result, "https://api.github.com/repos/ROCm/MIOpen")
|
|
|
|
|
|
class FormatCommitDateTest(unittest.TestCase):
|
|
"""Tests for format_commit_date function."""
|
|
|
|
def test_valid_iso_date(self):
|
|
"""Format valid ISO date string."""
|
|
date_str = "2025-01-15T10:30:00Z"
|
|
result = format_commit_date(date_str)
|
|
|
|
self.assertEqual(result, "Jan 15, 2025")
|
|
|
|
def test_invalid_date(self):
|
|
"""Handle invalid/empty date strings."""
|
|
self.assertEqual(format_commit_date("Unknown"), "Unknown")
|
|
self.assertEqual(format_commit_date(""), "Unknown")
|
|
self.assertEqual(format_commit_date("not-a-date"), "not-a-date")
|
|
|
|
|
|
class DetermineStatusTest(unittest.TestCase):
|
|
"""Tests for determine_status function."""
|
|
|
|
def test_removed_status(self):
|
|
"""Old SHA exists, new SHA doesn't -> removed."""
|
|
status, fetch_start, fetch_end = determine_status(
|
|
"abc123", None, "https://api.github.com/repos/ROCm/test"
|
|
)
|
|
|
|
self.assertEqual(status, "removed")
|
|
self.assertEqual(fetch_start, "")
|
|
self.assertEqual(fetch_end, "")
|
|
|
|
def test_added_status(self):
|
|
"""New SHA exists, old SHA doesn't -> added."""
|
|
status, fetch_start, fetch_end = determine_status(
|
|
None, "def456", "https://api.github.com/repos/ROCm/test"
|
|
)
|
|
|
|
self.assertEqual(status, "added")
|
|
self.assertEqual(fetch_start, "")
|
|
self.assertEqual(fetch_end, "def456")
|
|
|
|
def test_unchanged_status(self):
|
|
"""Same SHA returns unchanged status without API calls."""
|
|
# This should not make any API calls since SHAs are equal
|
|
status, fetch_start, fetch_end = determine_status(
|
|
"abc123", "abc123", "https://api.github.com/repos/ROCm/test"
|
|
)
|
|
|
|
self.assertEqual(status, "unchanged")
|
|
self.assertEqual(fetch_start, "")
|
|
self.assertEqual(fetch_end, "")
|
|
|
|
|
|
# =============================================================================
|
|
# Mocked API Tests
|
|
# =============================================================================
|
|
|
|
|
|
class IsRevertTest(unittest.TestCase):
|
|
"""Tests for is_revert function with mocked API calls."""
|
|
|
|
def test_is_revert_ahead_status(self):
|
|
"""Returns True when old_sha is ahead of new_sha (revert)."""
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_send_request"
|
|
) as mock_request:
|
|
mock_request.return_value = {"status": "ahead"}
|
|
result = is_revert(
|
|
"old_sha", "new_sha", "https://api.github.com/repos/ROCm/test"
|
|
)
|
|
|
|
self.assertTrue(result)
|
|
|
|
def test_is_revert_behind_status(self):
|
|
"""Returns False when old_sha is behind new_sha (forward progress)."""
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_send_request"
|
|
) as mock_request:
|
|
mock_request.return_value = {"status": "behind"}
|
|
result = is_revert(
|
|
"old_sha", "new_sha", "https://api.github.com/repos/ROCm/test"
|
|
)
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_is_revert_http_404(self):
|
|
"""Returns False on 404 (orphaned commits - can't determine)."""
|
|
mock_error = HTTPError(
|
|
url="https://api.github.com/repos/ROCm/test/compare/new...old",
|
|
code=404,
|
|
msg="Not Found",
|
|
hdrs={},
|
|
fp=None,
|
|
)
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_send_request", side_effect=mock_error
|
|
):
|
|
result = is_revert(
|
|
"old_sha", "new_sha", "https://api.github.com/repos/ROCm/test"
|
|
)
|
|
|
|
self.assertFalse(result)
|
|
|
|
|
|
class FetchCommitsInRangeTest(unittest.TestCase):
|
|
"""Tests for fetch_commits_in_range function with mocked API calls."""
|
|
|
|
def test_fetch_commits_success(self):
|
|
"""Successfully fetch commits between two SHAs."""
|
|
mock_commits = [
|
|
{"sha": "commit3", "commit": {"message": "Third"}},
|
|
{"sha": "commit2", "commit": {"message": "Second"}},
|
|
{"sha": "start_sha", "commit": {"message": "Start"}},
|
|
]
|
|
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_send_request"
|
|
) as mock_request:
|
|
mock_request.return_value = mock_commits
|
|
result = fetch_commits_in_range(
|
|
repo_name="test-repo",
|
|
start_sha="start_sha",
|
|
end_sha="commit3",
|
|
api_base="https://api.github.com/repos/ROCm/test",
|
|
)
|
|
|
|
# Should return commits up to but not including start_sha
|
|
self.assertEqual(len(result), 2)
|
|
self.assertEqual(result[0]["sha"], "commit3")
|
|
self.assertEqual(result[1]["sha"], "commit2")
|
|
|
|
def test_fetch_commits_diverged_fallback(self):
|
|
"""Falls back to compare API when commits diverged."""
|
|
diverged_commits = [
|
|
{"sha": "diverged1"},
|
|
{"sha": "diverged2"},
|
|
]
|
|
|
|
def mock_request_side_effect(url):
|
|
if "compare" in url:
|
|
return {"status": "diverged", "commits": diverged_commits}
|
|
# Return empty list to trigger fallback
|
|
return []
|
|
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_send_request",
|
|
side_effect=mock_request_side_effect,
|
|
):
|
|
result = fetch_commits_in_range(
|
|
repo_name="test-repo",
|
|
start_sha="start_sha",
|
|
end_sha="end_sha",
|
|
api_base="https://api.github.com/repos/ROCm/test",
|
|
)
|
|
|
|
self.assertEqual(len(result), 2)
|
|
self.assertEqual(result[0]["sha"], "diverged1")
|
|
|
|
|
|
# =============================================================================
|
|
# CLI Options Tests (Mocked)
|
|
# =============================================================================
|
|
|
|
|
|
class ResolveCommitsTest(unittest.TestCase):
|
|
"""Tests for resolve_commits() with mocked API calls."""
|
|
|
|
def test_workflow_mode_resolves_both_commits(self):
|
|
"""--workflow-mode resolves both start and end from workflow run IDs."""
|
|
args = parse_args(["--start", "123", "--end", "456", "--workflow-mode"])
|
|
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_query_workflow_run_by_id"
|
|
) as mock_query:
|
|
mock_query.side_effect = [
|
|
{"head_sha": "789xyz000111"}, # end workflow (resolved first)
|
|
{"head_sha": "abc123def456"}, # start workflow
|
|
]
|
|
start_sha, end_sha = resolve_commits(args)
|
|
|
|
self.assertEqual(start_sha, "abc123def456")
|
|
self.assertEqual(end_sha, "789xyz000111")
|
|
self.assertEqual(mock_query.call_count, 2)
|
|
|
|
def test_find_last_run_resolves_start(self):
|
|
"""--find-last-run finds the most recent matching run for start commit."""
|
|
args = parse_args(["--end", "def456", "--find-last-run", "multi_arch_ci.yml"])
|
|
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_query_last_workflow_run"
|
|
) as mock_query:
|
|
mock_query.return_value = {"head_sha": "last_matching_sha"}
|
|
start_sha, end_sha = resolve_commits(args)
|
|
|
|
self.assertEqual(start_sha, "last_matching_sha")
|
|
self.assertEqual(end_sha, "def456")
|
|
mock_query.assert_called_once()
|
|
|
|
def test_find_last_run_uses_terminal_statuses(self):
|
|
"""--find-last-run hardcodes accepted statuses to {success, failure}."""
|
|
args = parse_args(["--end", "def456", "--find-last-run", "ci.yml"])
|
|
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_query_last_workflow_run"
|
|
) as mock_query:
|
|
mock_query.return_value = {"head_sha": "abc"}
|
|
resolve_commits(args)
|
|
|
|
_, kwargs = mock_query.call_args
|
|
self.assertEqual(kwargs["accepted_statuses"], {"success", "failure"})
|
|
|
|
def test_pr_base_ref_resolves_start_via_compare(self):
|
|
"""--pr-base-ref resolves start as the merge-base via the Compare API."""
|
|
args = parse_args(["--end", "deadbeef", "--pr-base-ref", "main"])
|
|
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_send_request"
|
|
) as mock_request:
|
|
mock_request.return_value = {"merge_base_commit": {"sha": "base_sha_xyz"}}
|
|
start_sha, end_sha = resolve_commits(args)
|
|
|
|
self.assertEqual(start_sha, "base_sha_xyz")
|
|
self.assertEqual(end_sha, "deadbeef")
|
|
# Verify the URL hit Compare API with base...end ordering.
|
|
called_url = mock_request.call_args.args[0]
|
|
self.assertIn("/compare/main...deadbeef", called_url)
|
|
|
|
def test_find_last_run_no_match_returns_none(self):
|
|
"""--find-last-run with no matching prior run → (None, None)."""
|
|
args = parse_args(["--end", "def456", "--find-last-run", "ci.yml"])
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_query_last_workflow_run",
|
|
return_value=None,
|
|
):
|
|
self.assertEqual(resolve_commits(args), (None, None))
|
|
|
|
def test_pr_base_ref_takes_precedence_over_find_last_run(self):
|
|
"""When both --pr-base-ref and --find-last-run are set, Compare wins.
|
|
|
|
This pins the precedence ladder documented in resolve_commits():
|
|
pr_base_ref > find_last_run > workflow_mode/start_ref. If a future
|
|
refactor reorders the branches, this test catches it.
|
|
"""
|
|
args = parse_args(
|
|
[
|
|
"--end",
|
|
"deadbeef",
|
|
"--pr-base-ref",
|
|
"main",
|
|
"--find-last-run",
|
|
"ci.yml",
|
|
]
|
|
)
|
|
|
|
with mock.patch(
|
|
"generate_manifest_diff_report.gha_send_request"
|
|
) as mock_compare, mock.patch(
|
|
"generate_manifest_diff_report.gha_query_last_workflow_run"
|
|
) as mock_last_run:
|
|
mock_compare.return_value = {"merge_base_commit": {"sha": "merge_base"}}
|
|
start_sha, end_sha = resolve_commits(args)
|
|
|
|
self.assertEqual(start_sha, "merge_base")
|
|
self.assertEqual(end_sha, "deadbeef")
|
|
mock_compare.assert_called_once()
|
|
mock_last_run.assert_not_called()
|
|
|
|
def test_direct_commit_shas_no_api_calls(self):
|
|
"""Direct commit SHAs don't require API calls."""
|
|
args = parse_args(["--start", "abc123", "--end", "def456"])
|
|
|
|
# No mocking needed - should work without API calls
|
|
start_sha, end_sha = resolve_commits(args)
|
|
|
|
self.assertEqual(start_sha, "abc123")
|
|
self.assertEqual(end_sha, "def456")
|
|
|
|
def test_output_dir_argument_parsed(self):
|
|
"""--output-dir argument is parsed as Path."""
|
|
args = parse_args(["--start", "abc", "--end", "def", "--output-dir", "reports"])
|
|
self.assertEqual(args.output_dir, Path("reports"))
|
|
|
|
def test_output_dir_defaults_to_none(self):
|
|
"""--output-dir defaults to None when not specified."""
|
|
args = parse_args(["--start", "abc", "--end", "def"])
|
|
self.assertIsNone(args.output_dir)
|
|
|
|
|
|
# =============================================================================
|
|
# HTML Report Structure Tests
|
|
# =============================================================================
|
|
|
|
|
|
class HtmlReportStructureTest(unittest.TestCase):
|
|
"""Tests that generated HTML includes semantic row classes and data attributes."""
|
|
|
|
def test_create_table_includes_header_row_class(self):
|
|
"""Report tables have header row with class report-table-header-row."""
|
|
html = create_table(["Component", "Commits"], [])
|
|
self.assertIn("report-table-header-row", html)
|
|
|
|
def test_non_superrepo_html_includes_component_row_and_data_component(self):
|
|
"""Non-superrepo table rows have component-row class and data-component attribute."""
|
|
sub = Submodule(
|
|
name="test-submodule",
|
|
sha="abc123",
|
|
api_base="https://api.github.com/repos/ROCm/test",
|
|
branch="main",
|
|
status="unchanged",
|
|
)
|
|
diff = ManifestDiff(
|
|
start_commit="start",
|
|
end_commit="end",
|
|
submodules={"test-submodule": sub},
|
|
)
|
|
html = generate_non_superrepo_html(diff)
|
|
self.assertIn("component-row", html)
|
|
self.assertIn("data-component=", html)
|
|
self.assertIn("test-submodule", html)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|