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
426 lines
17 KiB
Python
426 lines
17 KiB
Python
################################################################################
|
|
#
|
|
# Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
################################################################################
|
|
|
|
import os
|
|
import pytest
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, patch, call, MagicMock
|
|
|
|
from Tensile.ClientExecutable import (
|
|
CMakeEnvironment,
|
|
clientExecutableEnvironment,
|
|
getClientExecutable,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestCMakeEnvironment:
|
|
"""Test suite for CMakeEnvironment class."""
|
|
|
|
def test_init_stores_source_dir(self):
|
|
"""Constructor stores sourceDir."""
|
|
env = CMakeEnvironment("/src", "/build")
|
|
assert env.sourceDir == "/src"
|
|
|
|
def test_init_stores_build_dir(self):
|
|
"""Constructor stores buildDir."""
|
|
env = CMakeEnvironment("/src", "/build")
|
|
assert env.buildDir == "/build"
|
|
|
|
def test_init_stores_empty_options(self):
|
|
"""Constructor stores empty options dict when no options provided."""
|
|
env = CMakeEnvironment("/src", "/build")
|
|
assert env.options == {}
|
|
|
|
def test_init_stores_options(self):
|
|
"""Constructor stores provided options."""
|
|
options = {"CMAKE_BUILD_TYPE": "Release", "OPTION2": "value2"}
|
|
env = CMakeEnvironment("/src", "/build", **options)
|
|
assert env.options == options
|
|
|
|
@patch("Tensile.ClientExecutable.print2")
|
|
@patch("Tensile.ClientExecutable.ClientExecutionLock")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("subprocess.check_call")
|
|
def test_generate_calls_cmake_with_no_options(
|
|
self, mock_subprocess, mock_ensure, mock_lock, mock_print
|
|
):
|
|
"""generate() calls cmake with sourceDir when no options."""
|
|
mock_ensure.return_value = "/build"
|
|
mock_lock.return_value.__enter__ = Mock()
|
|
mock_lock.return_value.__exit__ = Mock()
|
|
|
|
env = CMakeEnvironment("/src", "/build")
|
|
env.generate()
|
|
|
|
expected_args = ["cmake", "/src"]
|
|
mock_subprocess.assert_called_once_with(expected_args, cwd="/build")
|
|
|
|
@patch("Tensile.ClientExecutable.print2")
|
|
@patch("Tensile.ClientExecutable.ClientExecutionLock")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("subprocess.check_call")
|
|
def test_generate_calls_cmake_with_options(
|
|
self, mock_subprocess, mock_ensure, mock_lock, mock_print
|
|
):
|
|
"""generate() calls cmake with -D flags for each option."""
|
|
mock_ensure.return_value = "/build"
|
|
mock_lock.return_value.__enter__ = Mock()
|
|
mock_lock.return_value.__exit__ = Mock()
|
|
|
|
env = CMakeEnvironment(
|
|
"/src", "/build", CMAKE_BUILD_TYPE="Release", TENSILE_USE_MSGPACK="ON"
|
|
)
|
|
env.generate()
|
|
|
|
call_args = mock_subprocess.call_args[0][0]
|
|
assert call_args[0] == "cmake"
|
|
assert "-D" in call_args
|
|
assert "CMAKE_BUILD_TYPE=Release" in call_args
|
|
assert "-D" in call_args
|
|
assert "TENSILE_USE_MSGPACK=ON" in call_args
|
|
assert call_args[-1] == "/src"
|
|
|
|
@patch("Tensile.ClientExecutable.print2")
|
|
@patch("Tensile.ClientExecutable.ClientExecutionLock")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("subprocess.check_call")
|
|
def test_generate_ensures_build_path(
|
|
self, mock_subprocess, mock_ensure, mock_lock, mock_print
|
|
):
|
|
"""generate() ensures build directory exists."""
|
|
mock_ensure.return_value = "/build/created"
|
|
mock_lock.return_value.__enter__ = Mock()
|
|
mock_lock.return_value.__exit__ = Mock()
|
|
|
|
env = CMakeEnvironment("/src", "/build")
|
|
env.generate()
|
|
|
|
mock_ensure.assert_called_once_with("/build")
|
|
|
|
@patch("Tensile.ClientExecutable.print2")
|
|
@patch("Tensile.ClientExecutable.ClientExecutionLock")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("subprocess.check_call")
|
|
def test_generate_uses_lock(
|
|
self, mock_subprocess, mock_ensure, mock_lock, mock_print
|
|
):
|
|
"""generate() uses ClientExecutionLock."""
|
|
mock_ensure.return_value = "/build"
|
|
mock_lock_instance = MagicMock()
|
|
mock_lock.return_value = mock_lock_instance
|
|
|
|
env = CMakeEnvironment("/src", "/build")
|
|
env.generate()
|
|
|
|
mock_lock_instance.__enter__.assert_called_once()
|
|
mock_lock_instance.__exit__.assert_called_once()
|
|
|
|
@patch("Tensile.ClientExecutable.print2")
|
|
@patch("Tensile.ClientExecutable.ClientExecutionLock")
|
|
@patch("subprocess.check_call")
|
|
def test_build_calls_make(self, mock_subprocess, mock_lock, mock_print):
|
|
"""build() calls make -j in buildDir."""
|
|
mock_lock.return_value.__enter__ = Mock()
|
|
mock_lock.return_value.__exit__ = Mock()
|
|
|
|
env = CMakeEnvironment("/src", "/build")
|
|
env.build()
|
|
|
|
expected_args = ["make", "-j"]
|
|
mock_subprocess.assert_called_once_with(expected_args, cwd="/build")
|
|
|
|
@patch("Tensile.ClientExecutable.print2")
|
|
@patch("Tensile.ClientExecutable.ClientExecutionLock")
|
|
@patch("subprocess.check_call")
|
|
def test_build_uses_lock(self, mock_subprocess, mock_lock, mock_print):
|
|
"""build() uses ClientExecutionLock."""
|
|
mock_lock_instance = MagicMock()
|
|
mock_lock.return_value = mock_lock_instance
|
|
|
|
env = CMakeEnvironment("/src", "/build")
|
|
env.build()
|
|
|
|
mock_lock_instance.__enter__.assert_called_once()
|
|
mock_lock_instance.__exit__.assert_called_once()
|
|
|
|
def test_builtPath_single_path(self):
|
|
"""builtPath() joins single path to buildDir."""
|
|
env = CMakeEnvironment("/src", "/build")
|
|
result = env.builtPath("output")
|
|
assert result == os.path.join("/build", "output")
|
|
|
|
def test_builtPath_multiple_paths(self):
|
|
"""builtPath() joins multiple paths to buildDir."""
|
|
env = CMakeEnvironment("/src", "/build")
|
|
result = env.builtPath("client", "tensile_client")
|
|
assert result == os.path.join("/build", "client", "tensile_client")
|
|
|
|
def test_builtPath_nested_paths(self):
|
|
"""builtPath() handles nested directory paths."""
|
|
env = CMakeEnvironment("/src", "/build")
|
|
result = env.builtPath("a", "b", "c", "file.txt")
|
|
assert result == os.path.join("/build", "a", "b", "c", "file.txt")
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestClientExecutableEnvironment:
|
|
"""Test suite for clientExecutableEnvironment function."""
|
|
|
|
@patch("Tensile.ClientExecutable.SOURCE_PATH", "/tensile/source")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("Tensile.ClientExecutable.globalParameters", {
|
|
"CMakeBuildType": "Release",
|
|
"LibraryFormat": "msgpack",
|
|
"EnableMarker": "ON",
|
|
"ROCmBinPath": "/opt/rocm/bin",
|
|
})
|
|
def test_creates_cmake_environment(self, mock_ensure):
|
|
"""clientExecutableEnvironment() creates CMakeEnvironment with correct paths."""
|
|
mock_ensure.return_value = "/build/ensured"
|
|
|
|
result = clientExecutableEnvironment("/build", "hipcc", "clang")
|
|
|
|
assert isinstance(result, CMakeEnvironment)
|
|
assert result.sourceDir == "/tensile/source"
|
|
assert result.buildDir == "/build/ensured"
|
|
|
|
@patch("Tensile.ClientExecutable.SOURCE_PATH", "/tensile/source")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("Tensile.ClientExecutable.globalParameters", {
|
|
"CMakeBuildType": "Debug",
|
|
"LibraryFormat": "yaml",
|
|
"EnableMarker": "OFF",
|
|
"ROCmBinPath": "/opt/rocm/bin",
|
|
})
|
|
def test_sets_cmake_build_type(self, mock_ensure):
|
|
"""clientExecutableEnvironment() sets CMAKE_BUILD_TYPE from globalParameters."""
|
|
mock_ensure.return_value = "/build"
|
|
|
|
result = clientExecutableEnvironment("/build", "hipcc", "clang")
|
|
|
|
assert result.options["CMAKE_BUILD_TYPE"] == "Debug"
|
|
|
|
@patch("Tensile.ClientExecutable.SOURCE_PATH", "/tensile/source")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("Tensile.ClientExecutable.globalParameters", {
|
|
"CMakeBuildType": "Release",
|
|
"LibraryFormat": "msgpack",
|
|
"EnableMarker": "ON",
|
|
"ROCmBinPath": "/opt/rocm/bin",
|
|
})
|
|
def test_sets_tensile_options(self, mock_ensure):
|
|
"""clientExecutableEnvironment() sets Tensile-specific options."""
|
|
mock_ensure.return_value = "/build"
|
|
|
|
result = clientExecutableEnvironment("/build", "hipcc", "clang")
|
|
|
|
assert result.options["TENSILE_USE_MSGPACK"] == "ON"
|
|
assert result.options["Tensile_LIBRARY_FORMAT"] == "msgpack"
|
|
assert result.options["Tensile_ENABLE_MARKER"] == "ON"
|
|
|
|
@patch("Tensile.ClientExecutable.SOURCE_PATH", "/tensile/source")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("Tensile.ClientExecutable.globalParameters", {
|
|
"CMakeBuildType": "Release",
|
|
"LibraryFormat": "msgpack",
|
|
"EnableMarker": "ON",
|
|
"ROCmBinPath": "/custom/rocm",
|
|
})
|
|
def test_sets_compilers_from_rocm_path(self, mock_ensure):
|
|
"""clientExecutableEnvironment() constructs compiler paths from ROCmBinPath."""
|
|
mock_ensure.return_value = "/build"
|
|
|
|
result = clientExecutableEnvironment("/build", "hipcc", "clang")
|
|
|
|
assert result.options["CMAKE_CXX_COMPILER"] == "/custom/rocm/hipcc"
|
|
assert result.options["CMAKE_C_COMPILER"] == "/custom/rocm/clang"
|
|
|
|
@patch("Tensile.ClientExecutable.SOURCE_PATH", "/tensile/source")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("Tensile.ClientExecutable.globalParameters", {
|
|
"CMakeBuildType": "Release",
|
|
"LibraryFormat": "msgpack",
|
|
"EnableMarker": "ON",
|
|
"ROCmBinPath": "/opt/rocm/bin",
|
|
})
|
|
@patch.dict(os.environ, {}, clear=True)
|
|
def test_no_ccache_when_env_not_set(self, mock_ensure):
|
|
"""clientExecutableEnvironment() doesn't add ccache options when CCACHE_BASEDIR not set."""
|
|
mock_ensure.return_value = "/build"
|
|
|
|
result = clientExecutableEnvironment("/build", "hipcc", "clang")
|
|
|
|
assert "CMAKE_C_COMPILER_LAUNCHER" not in result.options
|
|
assert "CMAKE_CXX_COMPILER_LAUNCHER" not in result.options
|
|
|
|
@patch("Tensile.ClientExecutable.SOURCE_PATH", "/tensile/source")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("Tensile.ClientExecutable.globalParameters", {
|
|
"CMakeBuildType": "Release",
|
|
"LibraryFormat": "msgpack",
|
|
"EnableMarker": "ON",
|
|
"ROCmBinPath": "/opt/rocm/bin",
|
|
})
|
|
@patch.dict(os.environ, {"CCACHE_BASEDIR": "/tmp/ccache"})
|
|
@patch("builtins.print")
|
|
def test_enables_ccache_when_env_set(self, mock_print, mock_ensure):
|
|
"""clientExecutableEnvironment() adds ccache launcher when CCACHE_BASEDIR is set."""
|
|
mock_ensure.return_value = "/build"
|
|
|
|
result = clientExecutableEnvironment("/build", "hipcc", "clang")
|
|
|
|
assert result.options["CMAKE_C_COMPILER_LAUNCHER"] == "ccache"
|
|
assert result.options["CMAKE_CXX_COMPILER_LAUNCHER"] == "ccache"
|
|
mock_print.assert_called_once_with("Is Using CCACHE")
|
|
|
|
@patch("Tensile.ClientExecutable.SOURCE_PATH", "/tensile/source")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("Tensile.ClientExecutable.globalParameters", {
|
|
"CMakeBuildType": "Release",
|
|
"LibraryFormat": "msgpack",
|
|
"EnableMarker": "ON",
|
|
"ROCmBinPath": "/opt/rocm/bin",
|
|
})
|
|
def test_sets_tensile_use_llvm_on_unix(self, mock_ensure):
|
|
"""clientExecutableEnvironment() sets TENSILE_USE_LLVM=ON on non-Windows."""
|
|
mock_ensure.return_value = "/build"
|
|
|
|
with patch("os.name", "posix"):
|
|
result = clientExecutableEnvironment("/build", "hipcc", "clang")
|
|
|
|
assert result.options["TENSILE_USE_LLVM"] == "ON"
|
|
|
|
@patch("Tensile.ClientExecutable.SOURCE_PATH", "/tensile/source")
|
|
@patch("Tensile.ClientExecutable.ensurePath")
|
|
@patch("Tensile.ClientExecutable.globalParameters", {
|
|
"CMakeBuildType": "Release",
|
|
"LibraryFormat": "msgpack",
|
|
"EnableMarker": "ON",
|
|
"ROCmBinPath": "/opt/rocm/bin",
|
|
})
|
|
def test_sets_tensile_use_llvm_off_on_windows(self, mock_ensure):
|
|
"""clientExecutableEnvironment() sets TENSILE_USE_LLVM=OFF on Windows."""
|
|
mock_ensure.return_value = "/build"
|
|
|
|
with patch("os.name", "nt"):
|
|
result = clientExecutableEnvironment("/build", "hipcc", "clang")
|
|
|
|
assert result.options["TENSILE_USE_LLVM"] == "OFF"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestGetClientExecutable:
|
|
"""Test suite for getClientExecutable function."""
|
|
|
|
def setup_method(self):
|
|
"""Reset buildEnv singleton before each test."""
|
|
import Tensile.ClientExecutable
|
|
Tensile.ClientExecutable.buildEnv = None
|
|
|
|
@patch("Tensile.ClientExecutable.globalParameters", {"PrebuiltClient": "/prebuilt/client"})
|
|
def test_returns_prebuilt_client_when_set(self):
|
|
"""getClientExecutable() returns PrebuiltClient path when set in globalParameters."""
|
|
result = getClientExecutable("hipcc", "clang", Path("/build"))
|
|
|
|
assert result == "/prebuilt/client"
|
|
|
|
@patch("Tensile.ClientExecutable.globalParameters", {})
|
|
@patch("Tensile.ClientExecutable.clientExecutableEnvironment")
|
|
def test_builds_client_on_first_call(self, mock_env_factory):
|
|
"""getClientExecutable() builds client on first call when PrebuiltClient not set."""
|
|
mock_env = Mock()
|
|
mock_env.builtPath.return_value = "/build/client/tensile_client"
|
|
mock_env_factory.return_value = mock_env
|
|
|
|
result = getClientExecutable("hipcc", "clang", Path("/build"))
|
|
|
|
mock_env.generate.assert_called_once()
|
|
mock_env.build.assert_called_once()
|
|
assert result == "/build/client/tensile_client"
|
|
|
|
@patch("Tensile.ClientExecutable.globalParameters", {})
|
|
@patch("Tensile.ClientExecutable.CLIENT_BUILD_DIR", "client_build")
|
|
@patch("Tensile.ClientExecutable.clientExecutableEnvironment")
|
|
def test_uses_correct_build_dir(self, mock_env_factory):
|
|
"""getClientExecutable() constructs correct build directory path."""
|
|
mock_env = Mock()
|
|
mock_env.builtPath.return_value = "/build/client/tensile_client"
|
|
mock_env_factory.return_value = mock_env
|
|
|
|
getClientExecutable("hipcc", "clang", Path("/my/build"))
|
|
|
|
expected_builddir = Path("/my/build") / "client_build"
|
|
mock_env_factory.assert_called_once_with(expected_builddir, "hipcc", "clang")
|
|
|
|
@patch("Tensile.ClientExecutable.globalParameters", {})
|
|
@patch("Tensile.ClientExecutable.clientExecutableEnvironment")
|
|
def test_calls_builtPath_with_client_path(self, mock_env_factory):
|
|
"""getClientExecutable() calls builtPath with correct client path."""
|
|
mock_env = Mock()
|
|
mock_env.builtPath.return_value = "/build/output/client/tensile_client"
|
|
mock_env_factory.return_value = mock_env
|
|
|
|
result = getClientExecutable("hipcc", "clang", Path("/build"))
|
|
|
|
mock_env.builtPath.assert_called_once_with("client/tensile_client")
|
|
assert result == "/build/output/client/tensile_client"
|
|
|
|
@patch("Tensile.ClientExecutable.globalParameters", {})
|
|
@patch("Tensile.ClientExecutable.clientExecutableEnvironment")
|
|
def test_singleton_pattern_reuses_buildenv(self, mock_env_factory):
|
|
"""getClientExecutable() reuses buildEnv on subsequent calls."""
|
|
mock_env = Mock()
|
|
mock_env.builtPath.return_value = "/build/client/tensile_client"
|
|
mock_env_factory.return_value = mock_env
|
|
|
|
# First call
|
|
result1 = getClientExecutable("hipcc", "clang", Path("/build"))
|
|
# Second call
|
|
result2 = getClientExecutable("hipcc", "clang", Path("/build"))
|
|
|
|
# Should only create environment once
|
|
assert mock_env_factory.call_count == 1
|
|
# But generate and build only called once
|
|
assert mock_env.generate.call_count == 1
|
|
assert mock_env.build.call_count == 1
|
|
# Both calls should return the same result
|
|
assert result1 == result2
|
|
|
|
@patch("Tensile.ClientExecutable.globalParameters", {})
|
|
@patch("Tensile.ClientExecutable.clientExecutableEnvironment")
|
|
def test_passes_correct_compilers(self, mock_env_factory):
|
|
"""getClientExecutable() passes compiler arguments correctly."""
|
|
mock_env = Mock()
|
|
mock_env.builtPath.return_value = "/build/client/tensile_client"
|
|
mock_env_factory.return_value = mock_env
|
|
|
|
getClientExecutable("custom-cxx", "custom-cc", Path("/build"))
|
|
|
|
# Verify compilers passed to environment factory
|
|
call_args = mock_env_factory.call_args
|
|
assert call_args[0][1] == "custom-cxx"
|
|
assert call_args[0][2] == "custom-cc"
|