Files
TheRock/build_tools/tests/artifact_descriptor_overlap_test.py
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

99 lines
3.7 KiB
Python

# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""Tests validating that artifact descriptors don't have overlapping basedirs.
When two artifact descriptors claim the same stage directory for any component
type, extracting both artifacts into the same output directory causes file
collisions.
This was the root cause of https://github.com/ROCm/TheRock/issues/3758, where
both `artifact-rocprofiler-sdk.toml` and `artifact-aqlprofile-tests.toml`
included `profiler/aqlprofile/stage`, causing concurrent extraction to race
(and fail with "file exists" errors) on overlapping files.
This test loads the real artifact-*.toml descriptors from the source tree and
checks that each stage directory (basedir) belongs to exactly one descriptor.
Limitations (cases this test does NOT catch):
- Two artifacts with *different* basedirs whose installed files collide
after flattening (basedir prefix stripped). If two subprojects both
install a file to the same relative path (e.g., "lib/libfoo.so" or
"bin/sequence.yaml") in their respective stage dirs, flattening produces
duplicate paths even though the basedirs are distinct. Current projects
avoid this by using unique file names, but it's convention rather than
enforcement. Catching this requires inspecting actual build output.
"""
import tomllib
import unittest
from pathlib import Path
THEROCK_DIR = Path(__file__).resolve().parent.parent.parent
def get_basedirs(descriptor_path: Path) -> set[str]:
"""Extract all basedir paths from an artifact descriptor.
The TOML structure for basedirs is:
[components.{component_name}."{basedir_path}"]
Within a component's dict, string-keyed dict values are basedir entries.
Non-dict values (like "extends") are component-level fields.
"""
with open(descriptor_path, "rb") as f:
data = tomllib.load(f)
basedirs: set[str] = set()
for _comp_name, comp_data in data.get("components", {}).items():
if not isinstance(comp_data, dict):
continue
for key, value in comp_data.items():
if isinstance(value, dict):
basedirs.add(key)
return basedirs
class ArtifactDescriptorOverlapTest(unittest.TestCase):
"""Verifies no two artifact descriptors claim the same stage directory."""
def test_no_duplicate_basedirs_across_descriptors(self):
"""Each stage directory must belong to exactly one artifact descriptor.
If two descriptors reference the same basedir, their tarballs will
contain overlapping files, causing extraction failures.
"""
# basedir -> first descriptor that claims it
seen: dict[str, Path] = {}
errors: list[str] = []
descriptors = sorted(THEROCK_DIR.rglob("artifact-*.toml"))
self.assertGreater(
len(descriptors),
0,
f"No artifact descriptors found, check THEROCK_DIR ('{THEROCK_DIR}')",
)
for descriptor_path in descriptors:
relpath = descriptor_path.relative_to(THEROCK_DIR)
for basedir in get_basedirs(descriptor_path):
if basedir in seen:
errors.append(
f"basedir '{basedir}' claimed by both "
f"{seen[basedir]} and {relpath}"
)
else:
seen[basedir] = relpath
if errors:
self.fail(
"Duplicate basedirs across artifact descriptors will cause "
"extraction collisions (see "
"https://github.com/ROCm/TheRock/issues/3758):\n"
+ "\n".join(f" - {e}" for e in errors)
)
if __name__ == "__main__":
unittest.main()