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
1023 lines
32 KiB
Python
1023 lines
32 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright Advanced Micro Devices, Inc.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""
|
|
Unit tests for build_topology module.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import textwrap
|
|
import unittest
|
|
from io import StringIO
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, os.fspath(Path(__file__).parent.parent))
|
|
|
|
from configure_stage import get_stage_features
|
|
from _therock_utils.build_topology import (
|
|
BuildStage,
|
|
ArtifactGroup,
|
|
Artifact,
|
|
BuildTopology,
|
|
get_topology,
|
|
)
|
|
from topology_to_cmake import generate_feature_declarations
|
|
|
|
|
|
class BuildTopologyTest(unittest.TestCase):
|
|
"""Test cases for BuildTopology class."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
# Create a temporary file and close it immediately to avoid file locking on Windows
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=".toml", delete=False
|
|
) as temp_file:
|
|
self.topology_path = temp_file.name
|
|
|
|
def tearDown(self):
|
|
"""Clean up test fixtures."""
|
|
if os.path.exists(self.topology_path):
|
|
os.unlink(self.topology_path)
|
|
|
|
def write_topology(self, content: str):
|
|
"""Write topology content to temp file."""
|
|
with open(self.topology_path, "w") as f:
|
|
f.write(textwrap.dedent(content))
|
|
|
|
def test_empty_topology(self):
|
|
"""Test parsing an empty topology file."""
|
|
self.write_topology(
|
|
"""
|
|
[metadata]
|
|
version = "1.0"
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
self.assertEqual(len(topology.get_build_stages()), 0)
|
|
self.assertEqual(len(topology.get_artifact_groups()), 0)
|
|
self.assertEqual(len(topology.get_artifacts()), 0)
|
|
|
|
def test_parse_build_stages(self):
|
|
"""Test parsing build stages."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.foundation]
|
|
description = "Foundation stage"
|
|
artifact_groups = ["base", "sysdeps"]
|
|
|
|
[build_stages.compiler]
|
|
description = "Compiler stage"
|
|
artifact_groups = ["llvm"]
|
|
type = "per-arch"
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
stages = topology.get_build_stages()
|
|
|
|
self.assertEqual(len(stages), 2)
|
|
|
|
foundation = topology.build_stages["foundation"]
|
|
self.assertEqual(foundation.name, "foundation")
|
|
self.assertEqual(foundation.description, "Foundation stage")
|
|
self.assertEqual(foundation.artifact_groups, ["base", "sysdeps"])
|
|
self.assertEqual(foundation.type, "generic")
|
|
|
|
compiler = topology.build_stages["compiler"]
|
|
self.assertEqual(compiler.type, "per-arch")
|
|
|
|
def test_parse_external_git_sources(self):
|
|
"""Test parsing external git sources in source sets."""
|
|
self.write_topology(
|
|
"""
|
|
[source_sets.optional-hrx]
|
|
description = "Optional HRX"
|
|
external_git_sources = [
|
|
{ name = "hrx", origin = "https://github.com/ROCm/hrx.git", commit = "e642a13425f46bcf909078459dd4e07df0723a0d", path = "optional-sources/hrx" },
|
|
]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
source_set = topology.source_sets["optional-hrx"]
|
|
|
|
self.assertEqual(source_set.description, "Optional HRX")
|
|
self.assertEqual(len(source_set.external_git_sources), 1)
|
|
hrx = source_set.external_git_sources[0]
|
|
self.assertEqual(hrx.name, "hrx")
|
|
self.assertEqual(hrx.origin, "https://github.com/ROCm/hrx.git")
|
|
self.assertEqual(hrx.commit, "e642a13425f46bcf909078459dd4e07df0723a0d")
|
|
self.assertEqual(hrx.path, "optional-sources/hrx")
|
|
|
|
def test_get_source_set_for_submodule(self):
|
|
"""Test looking up the owning source set for a submodule."""
|
|
self.write_topology(
|
|
"""
|
|
[source_sets.compilers]
|
|
description = "Compiler toolchain submodules"
|
|
submodules = ["llvm-project", "HIPIFY", "spirv-llvm-translator"]
|
|
|
|
[source_sets.rocm-libraries]
|
|
description = "ROCm libraries"
|
|
submodules = ["rocm-libraries"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
self.assertEqual(
|
|
topology.get_source_set_for_submodule("llvm-project").name,
|
|
"compilers",
|
|
)
|
|
self.assertEqual(
|
|
topology.get_source_set_for_submodule("rocm-libraries").name,
|
|
"rocm-libraries",
|
|
)
|
|
self.assertIsNone(topology.get_source_set_for_submodule("unknown-submodule"))
|
|
|
|
def test_get_source_sets_for_submodules(self):
|
|
"""Test batch lookup of source sets from submodule names."""
|
|
self.write_topology(
|
|
"""
|
|
[source_sets.compilers]
|
|
description = "Compiler toolchain submodules"
|
|
submodules = ["llvm-project", "HIPIFY"]
|
|
|
|
[source_sets.rocm-libraries]
|
|
description = "ROCm libraries"
|
|
submodules = ["rocm-libraries"]
|
|
|
|
[source_sets.tests]
|
|
description = "Tests"
|
|
submodules = ["tests"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
source_sets = topology.get_source_sets_for_submodules(
|
|
["rocm-libraries", "llvm-project", "llvm-project"]
|
|
)
|
|
|
|
self.assertEqual(
|
|
sorted(source_set.name for source_set in source_sets),
|
|
["compilers", "rocm-libraries"],
|
|
)
|
|
|
|
def test_validate_external_git_source_path(self):
|
|
"""Test validation rejects external sources outside optional-sources."""
|
|
self.write_topology(
|
|
"""
|
|
[source_sets.optional-hrx]
|
|
description = "Optional HRX"
|
|
external_git_sources = [
|
|
{ name = "hrx", origin = "https://github.com/ROCm/hrx.git", commit = "e642a13425f46bcf909078459dd4e07df0723a0d", path = "rocm-systems/hrx" },
|
|
]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
errors = topology.validate_topology()
|
|
|
|
self.assertTrue(any("must be under optional-sources" in e for e in errors))
|
|
|
|
def test_validate_conflicting_submodule_ownership(self):
|
|
"""Test validation rejects submodules owned by multiple source sets."""
|
|
self.write_topology(
|
|
"""
|
|
[source_sets.set1]
|
|
description = "Set 1"
|
|
submodules = ["shared-submodule"]
|
|
|
|
[source_sets.set2]
|
|
description = "Set 2"
|
|
submodules = ["shared-submodule"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
errors = topology.validate_topology()
|
|
|
|
self.assertTrue(
|
|
any("Submodule 'shared-submodule' is used by both" in e for e in errors)
|
|
)
|
|
|
|
def test_parse_artifact_groups(self):
|
|
"""Test parsing artifact groups."""
|
|
self.write_topology(
|
|
"""
|
|
[artifact_groups.base]
|
|
description = "Base infrastructure"
|
|
type = "generic"
|
|
|
|
[artifact_groups.runtime]
|
|
description = "Runtime components"
|
|
type = "generic"
|
|
artifact_group_deps = ["base"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
groups = topology.get_artifact_groups()
|
|
|
|
self.assertEqual(len(groups), 2)
|
|
|
|
base = topology.artifact_groups["base"]
|
|
self.assertEqual(base.name, "base")
|
|
self.assertEqual(base.description, "Base infrastructure")
|
|
self.assertEqual(base.type, "generic")
|
|
self.assertEqual(base.artifact_group_deps, [])
|
|
|
|
runtime = topology.artifact_groups["runtime"]
|
|
self.assertEqual(runtime.artifact_group_deps, ["base"])
|
|
|
|
def test_parse_artifacts(self):
|
|
"""Test parsing artifacts."""
|
|
self.write_topology(
|
|
"""
|
|
[artifacts.rocm-core]
|
|
artifact_group = "base"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.hip]
|
|
artifact_group = "runtime"
|
|
type = "target-specific"
|
|
artifact_deps = ["rocm-core"]
|
|
platform = "linux"
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
artifacts = topology.get_artifacts()
|
|
|
|
self.assertEqual(len(artifacts), 2)
|
|
|
|
rocm_core = topology.artifacts["rocm-core"]
|
|
self.assertEqual(rocm_core.name, "rocm-core")
|
|
self.assertEqual(rocm_core.artifact_group, "base")
|
|
self.assertEqual(rocm_core.type, "target-neutral")
|
|
self.assertEqual(rocm_core.artifact_deps, [])
|
|
self.assertIsNone(rocm_core.platform)
|
|
|
|
hip = topology.artifacts["hip"]
|
|
self.assertEqual(hip.artifact_group, "runtime")
|
|
self.assertEqual(hip.type, "target-specific")
|
|
self.assertEqual(hip.artifact_deps, ["rocm-core"])
|
|
self.assertEqual(hip.platform, "linux")
|
|
|
|
def test_parse_platform_disables_guarded_by_flags(self):
|
|
"""Test parsing platform disables guarded by build flags."""
|
|
self.write_topology(
|
|
"""
|
|
[artifacts.core-runtime]
|
|
artifact_group = "runtime"
|
|
type = "target-neutral"
|
|
disable_platforms_if_flags_not_set = { windows = "HSA_WINDOWS_SHARED_RUNTIME" }
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
artifact = topology.artifacts["core-runtime"]
|
|
|
|
self.assertEqual(
|
|
artifact.disable_platforms_if_flags_not_set,
|
|
{"windows": "HSA_WINDOWS_SHARED_RUNTIME"},
|
|
)
|
|
self.assertTrue(topology.is_artifact_disabled_on_platform(artifact, "windows"))
|
|
self.assertFalse(
|
|
topology.is_artifact_disabled_on_platform(
|
|
artifact,
|
|
"windows",
|
|
enabled_flags={"HSA_WINDOWS_SHARED_RUNTIME"},
|
|
)
|
|
)
|
|
self.assertFalse(topology.is_artifact_disabled_on_platform(artifact, "linux"))
|
|
|
|
def test_stage_features_skip_platform_disables_guarded_by_flags(self):
|
|
"""Test stage features skip artifacts disabled by unset flags."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.runtime]
|
|
description = "Runtime"
|
|
artifact_groups = ["runtime"]
|
|
|
|
[artifact_groups.runtime]
|
|
description = "Runtime"
|
|
type = "generic"
|
|
|
|
[artifacts.core-runtime]
|
|
artifact_group = "runtime"
|
|
type = "target-neutral"
|
|
feature_name = "CORE_RUNTIME"
|
|
feature_group = "CORE"
|
|
disable_platforms_if_flags_not_set = { windows = "HSA_WINDOWS_SHARED_RUNTIME" }
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
self.assertNotIn(
|
|
"CORE_RUNTIME",
|
|
get_stage_features(topology, "runtime", platform_name="windows"),
|
|
)
|
|
self.assertIn(
|
|
"CORE_RUNTIME",
|
|
get_stage_features(
|
|
topology,
|
|
"runtime",
|
|
platform_name="windows",
|
|
enabled_flags={"HSA_WINDOWS_SHARED_RUNTIME"},
|
|
),
|
|
)
|
|
|
|
def test_generates_conditional_disabled_platform_feature(self):
|
|
"""Test generated CMake for platform disables guarded by flags."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.runtime]
|
|
description = "Runtime"
|
|
artifact_groups = ["runtime"]
|
|
|
|
[artifact_groups.runtime]
|
|
description = "Runtime"
|
|
type = "generic"
|
|
|
|
[artifacts.core-runtime]
|
|
artifact_group = "runtime"
|
|
type = "target-neutral"
|
|
feature_name = "CORE_RUNTIME"
|
|
feature_group = "CORE"
|
|
disable_platforms_if_flags_not_set = { windows = "HSA_WINDOWS_SHARED_RUNTIME" }
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
output = StringIO()
|
|
generate_feature_declarations(topology, output)
|
|
cmake = output.getvalue()
|
|
|
|
self.assertIn("if(NOT THEROCK_FLAG_HSA_WINDOWS_SHARED_RUNTIME)", cmake)
|
|
self.assertIn(
|
|
"list(APPEND _THEROCK_CORE_RUNTIME_DISABLE_PLATFORMS windows)",
|
|
cmake,
|
|
)
|
|
self.assertIn("else()", cmake)
|
|
self.assertIn(
|
|
"DISABLE_PLATFORMS ${_THEROCK_CORE_RUNTIME_DISABLE_PLATFORMS}",
|
|
cmake,
|
|
)
|
|
self.assertIn(
|
|
"CORE_RUNTIME can be built on ${CMAKE_SYSTEM_NAME} only with "
|
|
"-DTHEROCK_FLAG_HSA_WINDOWS_SHARED_RUNTIME=ON",
|
|
cmake,
|
|
)
|
|
|
|
def test_get_artifacts_in_group(self):
|
|
"""Test getting artifacts belonging to a group."""
|
|
self.write_topology(
|
|
"""
|
|
[artifacts.artifact1]
|
|
artifact_group = "group1"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.artifact2]
|
|
artifact_group = "group1"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.artifact3]
|
|
artifact_group = "group2"
|
|
type = "target-neutral"
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
group1_artifacts = topology.get_artifacts_in_group("group1")
|
|
self.assertEqual(len(group1_artifacts), 2)
|
|
self.assertIn("artifact1", [a.name for a in group1_artifacts])
|
|
self.assertIn("artifact2", [a.name for a in group1_artifacts])
|
|
|
|
group2_artifacts = topology.get_artifacts_in_group("group2")
|
|
self.assertEqual(len(group2_artifacts), 1)
|
|
self.assertEqual(group2_artifacts[0].name, "artifact3")
|
|
|
|
def test_get_produced_artifacts(self):
|
|
"""Test getting artifacts produced by a build stage."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.stage1]
|
|
description = "Stage 1"
|
|
artifact_groups = ["group1", "group2"]
|
|
|
|
[artifact_groups.group1]
|
|
description = "Group 1"
|
|
type = "generic"
|
|
|
|
[artifact_groups.group2]
|
|
description = "Group 2"
|
|
type = "generic"
|
|
|
|
[artifacts.artifact1]
|
|
artifact_group = "group1"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.artifact2]
|
|
artifact_group = "group1"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.artifact3]
|
|
artifact_group = "group2"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.artifact4]
|
|
artifact_group = "group3"
|
|
type = "target-neutral"
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
produced = topology.get_produced_artifacts("stage1")
|
|
|
|
self.assertEqual(len(produced), 3)
|
|
self.assertIn("artifact1", produced)
|
|
self.assertIn("artifact2", produced)
|
|
self.assertIn("artifact3", produced)
|
|
self.assertNotIn("artifact4", produced)
|
|
|
|
def test_get_inbound_artifacts(self):
|
|
"""Test getting inbound artifacts for a build stage."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.stage1]
|
|
description = "Stage 1"
|
|
artifact_groups = ["group1"]
|
|
|
|
[build_stages.stage2]
|
|
description = "Stage 2"
|
|
artifact_groups = ["group2"]
|
|
|
|
[artifact_groups.group1]
|
|
description = "Group 1"
|
|
type = "generic"
|
|
|
|
[artifact_groups.group2]
|
|
description = "Group 2"
|
|
type = "generic"
|
|
artifact_group_deps = ["group1"]
|
|
|
|
[artifacts.artifact1]
|
|
artifact_group = "group1"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.artifact2]
|
|
artifact_group = "group2"
|
|
type = "target-neutral"
|
|
artifact_deps = ["artifact1"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
# Stage1 has no inbound artifacts
|
|
stage1_inbound = topology.get_inbound_artifacts("stage1")
|
|
self.assertEqual(len(stage1_inbound), 0)
|
|
|
|
# Stage2 depends on artifacts from stage1
|
|
stage2_inbound = topology.get_inbound_artifacts("stage2")
|
|
self.assertEqual(len(stage2_inbound), 1)
|
|
self.assertIn("artifact1", stage2_inbound)
|
|
|
|
def test_validate_missing_references(self):
|
|
"""Test validation catches missing references."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.stage1]
|
|
description = "Stage with missing group"
|
|
artifact_groups = ["missing_group"]
|
|
|
|
[artifact_groups.group1]
|
|
description = "Group with missing dependency"
|
|
type = "generic"
|
|
artifact_group_deps = ["missing_dep"]
|
|
|
|
[artifacts.artifact1]
|
|
artifact_group = "missing_artifact_group"
|
|
type = "target-neutral"
|
|
artifact_deps = ["missing_artifact"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
errors = topology.validate_topology()
|
|
|
|
self.assertGreater(len(errors), 0)
|
|
self.assertTrue(any("missing_group" in e for e in errors))
|
|
self.assertTrue(any("missing_dep" in e for e in errors))
|
|
self.assertTrue(any("missing_artifact_group" in e for e in errors))
|
|
self.assertTrue(any("missing_artifact" in e for e in errors))
|
|
|
|
def test_validate_circular_dependencies(self):
|
|
"""Test validation catches circular dependencies."""
|
|
self.write_topology(
|
|
"""
|
|
[artifact_groups.group1]
|
|
description = "Group 1"
|
|
type = "generic"
|
|
artifact_group_deps = ["group2"]
|
|
|
|
[artifact_groups.group2]
|
|
description = "Group 2"
|
|
type = "generic"
|
|
artifact_group_deps = ["group3"]
|
|
|
|
[artifact_groups.group3]
|
|
description = "Group 3"
|
|
type = "generic"
|
|
artifact_group_deps = ["group1"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
errors = topology.validate_topology()
|
|
|
|
self.assertGreater(len(errors), 0)
|
|
self.assertTrue(any("Circular dependency" in e for e in errors))
|
|
|
|
def test_get_build_order(self):
|
|
"""Test getting the build order based on dependencies."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.foundation]
|
|
description = "Foundation"
|
|
artifact_groups = ["base"]
|
|
|
|
[build_stages.compiler]
|
|
description = "Compiler"
|
|
artifact_groups = ["llvm"]
|
|
|
|
[build_stages.runtime]
|
|
description = "Runtime"
|
|
artifact_groups = ["hip"]
|
|
|
|
[artifact_groups.base]
|
|
description = "Base"
|
|
type = "generic"
|
|
|
|
[artifact_groups.llvm]
|
|
description = "LLVM"
|
|
type = "generic"
|
|
artifact_group_deps = ["base"]
|
|
|
|
[artifact_groups.hip]
|
|
description = "HIP"
|
|
type = "generic"
|
|
artifact_group_deps = ["llvm"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
build_order = topology.get_build_order()
|
|
|
|
# Foundation should come before compiler
|
|
foundation_idx = build_order.index("foundation")
|
|
compiler_idx = build_order.index("compiler")
|
|
self.assertLess(foundation_idx, compiler_idx)
|
|
|
|
# Compiler should come before runtime
|
|
runtime_idx = build_order.index("runtime")
|
|
self.assertLess(compiler_idx, runtime_idx)
|
|
|
|
def test_topology_reverse_indexes(self):
|
|
"""Test reverse indexes across source sets, groups, artifacts, and stages."""
|
|
self.write_topology(
|
|
"""
|
|
[source_sets.base]
|
|
description = "Base"
|
|
submodules = ["base"]
|
|
|
|
[source_sets.runtime]
|
|
description = "Runtime"
|
|
submodules = ["runtime"]
|
|
|
|
[source_sets.tests]
|
|
description = "Tests"
|
|
submodules = ["tests"]
|
|
|
|
[source_sets.unused]
|
|
description = "Unused"
|
|
submodules = ["unused"]
|
|
|
|
[build_stages.foundation]
|
|
description = "Foundation"
|
|
artifact_groups = ["base"]
|
|
|
|
[build_stages.runtime]
|
|
description = "Runtime"
|
|
artifact_groups = ["hip", "rocrtst"]
|
|
|
|
[artifact_groups.base]
|
|
description = "Base"
|
|
type = "generic"
|
|
source_sets = ["base"]
|
|
|
|
[artifact_groups.hip]
|
|
description = "HIP"
|
|
type = "generic"
|
|
source_sets = ["runtime"]
|
|
|
|
[artifact_groups.rocrtst]
|
|
description = "Runtime tests"
|
|
type = "generic"
|
|
source_sets = ["runtime", "tests"]
|
|
|
|
[artifact_groups.future]
|
|
description = "Future group"
|
|
type = "generic"
|
|
source_sets = ["base"]
|
|
|
|
[artifacts.base-artifact]
|
|
artifact_group = "base"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.hip-artifact]
|
|
artifact_group = "hip"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.rocrtst-artifact]
|
|
artifact_group = "rocrtst"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.future-artifact]
|
|
artifact_group = "future"
|
|
type = "target-neutral"
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
self.assertEqual(
|
|
topology.get_source_set_to_artifact_groups(),
|
|
{
|
|
"base": ["base", "future"],
|
|
"runtime": ["hip", "rocrtst"],
|
|
"tests": ["rocrtst"],
|
|
"unused": [],
|
|
},
|
|
)
|
|
self.assertEqual(
|
|
topology.get_artifact_group_to_artifacts(),
|
|
{
|
|
"base": ["base-artifact"],
|
|
"hip": ["hip-artifact"],
|
|
"rocrtst": ["rocrtst-artifact"],
|
|
"future": ["future-artifact"],
|
|
},
|
|
)
|
|
self.assertEqual(
|
|
topology.get_artifact_group_to_build_stages(),
|
|
{
|
|
"base": ["foundation"],
|
|
"hip": ["runtime"],
|
|
"rocrtst": ["runtime"],
|
|
"future": [],
|
|
},
|
|
)
|
|
self.assertEqual(
|
|
topology.get_artifact_to_producer_stages(),
|
|
{
|
|
"base-artifact": ["foundation"],
|
|
"hip-artifact": ["runtime"],
|
|
"rocrtst-artifact": ["runtime"],
|
|
"future-artifact": [],
|
|
},
|
|
)
|
|
self.assertEqual(
|
|
topology.get_stage_to_source_sets(),
|
|
{
|
|
"foundation": ["base"],
|
|
"runtime": ["runtime", "tests"],
|
|
},
|
|
)
|
|
self.assertEqual(
|
|
topology.get_source_set_to_stages(),
|
|
{
|
|
"base": ["foundation"],
|
|
"runtime": ["runtime"],
|
|
"tests": ["runtime"],
|
|
"unused": [],
|
|
},
|
|
)
|
|
self.assertEqual(
|
|
topology.get_submodule_to_source_set(),
|
|
{
|
|
"base": "base",
|
|
"runtime": "runtime",
|
|
"tests": "tests",
|
|
"unused": "unused",
|
|
},
|
|
)
|
|
|
|
def test_stage_source_set_indexes_filter_disabled_platforms(self):
|
|
"""Test stage/source set indexes respect platform disabled source sets."""
|
|
self.write_topology(
|
|
"""
|
|
[source_sets.common]
|
|
description = "Common"
|
|
submodules = ["common"]
|
|
|
|
[source_sets.windows-only]
|
|
description = "Windows-only"
|
|
submodules = ["windows-only"]
|
|
disable_platforms = ["linux"]
|
|
|
|
[build_stages.runtime]
|
|
description = "Runtime"
|
|
artifact_groups = ["runtime"]
|
|
|
|
[artifact_groups.runtime]
|
|
description = "Runtime"
|
|
type = "generic"
|
|
source_sets = ["common", "windows-only"]
|
|
|
|
[artifacts.runtime-artifact]
|
|
artifact_group = "runtime"
|
|
type = "target-neutral"
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
self.assertEqual(
|
|
topology.get_stage_to_source_sets(),
|
|
{"runtime": ["common", "windows-only"]},
|
|
)
|
|
self.assertEqual(
|
|
topology.get_stage_to_source_sets(platform="linux"),
|
|
{"runtime": ["common"]},
|
|
)
|
|
self.assertEqual(
|
|
topology.get_source_set_to_stages(platform="linux"),
|
|
{
|
|
"common": ["runtime"],
|
|
"windows-only": [],
|
|
},
|
|
)
|
|
|
|
def test_get_dependency_graph(self):
|
|
"""Test generating dependency graph."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.stage1]
|
|
description = "Stage 1"
|
|
artifact_groups = ["group1"]
|
|
|
|
[artifact_groups.group1]
|
|
description = "Group 1"
|
|
type = "generic"
|
|
|
|
[artifacts.artifact1]
|
|
artifact_group = "group1"
|
|
type = "target-neutral"
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
graph = topology.get_dependency_graph()
|
|
|
|
self.assertIn("build_stages", graph)
|
|
self.assertIn("artifact_groups", graph)
|
|
self.assertIn("artifacts", graph)
|
|
|
|
self.assertIn("stage1", graph["build_stages"])
|
|
self.assertIn("group1", graph["artifact_groups"])
|
|
self.assertIn("artifact1", graph["artifacts"])
|
|
|
|
# Check stage details
|
|
stage1_data = graph["build_stages"]["stage1"]
|
|
self.assertEqual(stage1_data["type"], "generic")
|
|
self.assertEqual(stage1_data["artifact_groups"], ["group1"])
|
|
self.assertIn("produced_artifacts", stage1_data)
|
|
self.assertIn("artifact1", stage1_data["produced_artifacts"])
|
|
|
|
def test_invalid_stage_name(self):
|
|
"""Test handling of invalid stage name."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.stage1]
|
|
description = "Stage 1"
|
|
artifact_groups = []
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
with self.assertRaises(ValueError) as context:
|
|
topology.get_inbound_artifacts("nonexistent_stage")
|
|
|
|
self.assertIn("not found", str(context.exception))
|
|
|
|
def test_diamond_dependency_pattern(self):
|
|
"""Test diamond dependency pattern doesn't cause redundant processing."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.stage1]
|
|
description = "Stage 1"
|
|
artifact_groups = ["group1"]
|
|
|
|
[build_stages.stage2]
|
|
description = "Stage 2"
|
|
artifact_groups = ["group2"]
|
|
|
|
[artifact_groups.group1]
|
|
description = "Group 1"
|
|
type = "generic"
|
|
|
|
[artifact_groups.group2]
|
|
description = "Group 2"
|
|
type = "generic"
|
|
artifact_group_deps = ["group1"]
|
|
|
|
# Diamond pattern:
|
|
# A
|
|
# / \\
|
|
# B C
|
|
# \\ /
|
|
# D
|
|
[artifacts.D]
|
|
artifact_group = "group1"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.B]
|
|
artifact_group = "group1"
|
|
type = "target-neutral"
|
|
artifact_deps = ["D"]
|
|
|
|
[artifacts.C]
|
|
artifact_group = "group1"
|
|
type = "target-neutral"
|
|
artifact_deps = ["D"]
|
|
|
|
[artifacts.A]
|
|
artifact_group = "group2"
|
|
type = "target-neutral"
|
|
artifact_deps = ["B", "C"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
# Stage2 should get D only once, not twice
|
|
stage2_inbound = topology.get_inbound_artifacts("stage2")
|
|
|
|
# Count how many times D appears (should be exactly once)
|
|
d_count = list(stage2_inbound).count("D")
|
|
self.assertEqual(d_count, 1, "D should appear exactly once in dependencies")
|
|
|
|
# Should have all three dependencies B, C, D
|
|
self.assertEqual(len(stage2_inbound), 3)
|
|
self.assertIn("B", stage2_inbound)
|
|
self.assertIn("C", stage2_inbound)
|
|
self.assertIn("D", stage2_inbound)
|
|
|
|
def test_group_dependencies_include_transitive_artifact_dependencies(self):
|
|
"""Test group deps include the artifact deps of artifacts they pull in."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.producer]
|
|
description = "Producer"
|
|
artifact_groups = ["base"]
|
|
|
|
[build_stages.consumer]
|
|
description = "Consumer"
|
|
artifact_groups = ["leaf"]
|
|
|
|
[artifact_groups.base]
|
|
description = "Base"
|
|
type = "generic"
|
|
|
|
[artifact_groups.leaf]
|
|
description = "Leaf"
|
|
type = "generic"
|
|
artifact_group_deps = ["base"]
|
|
|
|
[artifacts.toolchain-runtime]
|
|
artifact_group = "base"
|
|
type = "target-neutral"
|
|
artifact_deps = ["toolchain-frontend"]
|
|
|
|
[artifacts.toolchain-frontend]
|
|
artifact_group = "base"
|
|
type = "target-neutral"
|
|
artifact_deps = ["toolchain-base"]
|
|
|
|
[artifacts.toolchain-base]
|
|
artifact_group = "base"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.leaf-artifact]
|
|
artifact_group = "leaf"
|
|
type = "target-neutral"
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
consumer_inbound = topology.get_inbound_artifacts("consumer")
|
|
self.assertIn("toolchain-runtime", consumer_inbound)
|
|
self.assertIn("toolchain-frontend", consumer_inbound)
|
|
self.assertIn("toolchain-base", consumer_inbound)
|
|
|
|
def test_complex_dependency_chain(self):
|
|
"""Test complex dependency chain resolution."""
|
|
self.write_topology(
|
|
"""
|
|
[build_stages.foundation]
|
|
artifact_groups = ["base"]
|
|
description = "Foundation"
|
|
|
|
[build_stages.compiler]
|
|
artifact_groups = ["llvm"]
|
|
description = "Compiler"
|
|
|
|
[build_stages.runtime]
|
|
artifact_groups = ["hip"]
|
|
description = "Runtime"
|
|
|
|
[build_stages.libraries]
|
|
artifact_groups = ["math"]
|
|
description = "Libraries"
|
|
|
|
[artifact_groups.base]
|
|
type = "generic"
|
|
description = "Base"
|
|
|
|
[artifact_groups.llvm]
|
|
type = "generic"
|
|
artifact_group_deps = ["base"]
|
|
description = "LLVM"
|
|
|
|
[artifact_groups.hip]
|
|
type = "generic"
|
|
artifact_group_deps = ["base", "llvm"]
|
|
description = "HIP"
|
|
|
|
[artifact_groups.math]
|
|
type = "generic"
|
|
artifact_group_deps = ["hip"]
|
|
description = "Math"
|
|
|
|
[artifacts.base-artifact]
|
|
artifact_group = "base"
|
|
type = "target-neutral"
|
|
|
|
[artifacts.llvm-artifact]
|
|
artifact_group = "llvm"
|
|
type = "target-neutral"
|
|
artifact_deps = ["base-artifact"]
|
|
|
|
[artifacts.hip-artifact]
|
|
artifact_group = "hip"
|
|
type = "target-neutral"
|
|
artifact_deps = ["base-artifact", "llvm-artifact"]
|
|
|
|
[artifacts.math-artifact]
|
|
artifact_group = "math"
|
|
type = "target-neutral"
|
|
artifact_deps = ["hip-artifact"]
|
|
"""
|
|
)
|
|
|
|
topology = BuildTopology(self.topology_path)
|
|
|
|
# Libraries stage should need all upstream artifacts
|
|
libs_inbound = topology.get_inbound_artifacts("libraries")
|
|
self.assertIn("hip-artifact", libs_inbound)
|
|
self.assertIn("llvm-artifact", libs_inbound)
|
|
self.assertIn("base-artifact", libs_inbound)
|
|
|
|
# Foundation stage should need nothing
|
|
foundation_inbound = topology.get_inbound_artifacts("foundation")
|
|
self.assertEqual(len(foundation_inbound), 0)
|
|
|
|
|
|
class RealTopologyTest(unittest.TestCase):
|
|
"""Assertions against the repo's actual BUILD_TOPOLOGY.toml."""
|
|
|
|
def test_hipkernelprovider_is_split_per_arch(self):
|
|
# rocKE ships per-arch AOT bundles under engines/arch_content/rocke/<arch>,
|
|
# so hipkernelprovider must stay target-specific and kpack-split; reverting
|
|
# either drops the per-arch bundles from the device artifacts.
|
|
topology = get_topology()
|
|
hkp = topology.artifacts["hipkernelprovider"]
|
|
self.assertEqual(hkp.type, "target-specific")
|
|
self.assertIn("hipkernelprovider", hkp.split_databases)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|