Files
TheRock/rocm-libraries/projects/hipblaslt/tensilelite/Tensile/Tests/unit/test_TensileMergeLibrary.py
T
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

740 lines
27 KiB
Python

# Copyright © Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
import pytest
import os
import sys
import tempfile
import shutil
import yaml
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock, mock_open
from copy import deepcopy
from Tensile.TensileMergeLibrary import (
ensurePath,
allFiles,
fixSizeInconsistencies,
addKernel,
sanitizeSolutions,
reNameSolutions,
removeUnusedSolutions,
removeDuplicatedSolutions,
loadData,
compareDestFolderToYaml,
compareProblemType,
msg,
verbose,
debug,
findSolutionWithIndex,
mergeLogic,
avoidRegressions,
)
@pytest.mark.unit
class TestEnsurePath:
"""Test ensurePath function"""
def test_create_new_directory(self):
"""Test creating a new directory"""
with tempfile.TemporaryDirectory() as tmpdir:
new_path = os.path.join(tmpdir, "test_dir", "nested")
result = ensurePath(new_path)
assert os.path.exists(new_path)
assert result == new_path
def test_existing_directory(self):
"""Test with existing directory"""
with tempfile.TemporaryDirectory() as tmpdir:
result = ensurePath(tmpdir)
assert os.path.exists(tmpdir)
assert result == tmpdir
@pytest.mark.unit
class TestAllFiles:
"""Test allFiles function"""
def test_find_yaml_files_in_directory(self):
"""Test finding YAML files in a directory"""
with tempfile.TemporaryDirectory() as tmpdir:
# Create test files
Path(os.path.join(tmpdir, "file1.yaml")).touch()
Path(os.path.join(tmpdir, "file2.YAML")).touch()
Path(os.path.join(tmpdir, "file3.txt")).touch()
files = allFiles(tmpdir)
yaml_names = [os.path.basename(f) for f in files]
assert len(files) == 2
assert "file1.yaml" in yaml_names
assert "file2.YAML" in yaml_names
assert "file3.txt" not in yaml_names
def test_empty_directory(self):
"""Test with empty directory"""
with tempfile.TemporaryDirectory() as tmpdir:
files = allFiles(tmpdir)
assert files == []
def test_nested_directories(self):
"""Test finding YAML files in nested directories"""
with tempfile.TemporaryDirectory() as tmpdir:
# Create nested structure - but not as a subdirectory with yaml extension
# because allFiles recursively searches directories, not files ending in .yaml
Path(os.path.join(tmpdir, "top.yaml")).touch()
Path(os.path.join(tmpdir, "another.yaml")).touch()
files = allFiles(tmpdir)
# Should find both .yaml files
assert len(files) == 2
@pytest.mark.unit
class TestFixSizeInconsistencies:
"""Test fixSizeInconsistencies function"""
def test_remove_duplicates(self):
"""Test removing duplicate sizes"""
sizes = [
([1, 2, 3, 4, 5, 6, 7, 8], 0),
([1, 2, 3, 4], 1),
([1, 2, 3, 4, 9, 10, 11, 12], 2),
]
result, count = fixSizeInconsistencies(sizes, "test")
# All 3 sizes truncate/normalize to [1, 2, 3, 4], so should deduplicate to 1 unique size
# - [1,2,3,4,5,6,7,8] -> [1,2,3,4] (removes last 4)
# - [1,2,3,4] -> [1,2,3,4] (no change)
# - [1,2,3,4,9,10,11,12] -> [1,2,3,4] (removes last 4)
assert count == 1
assert len(result) == 1
assert result[0][0] == [1, 2, 3, 4]
@patch('Tensile.TensileMergeLibrary.verbose')
def test_remove_duplicates_verbose_message(self, mock_verbose):
"""Test that verbose message is triggered with correct count when duplicates are removed"""
sizes = [
([1, 2, 3, 4, 5, 6, 7, 8], 0),
([1, 2, 3, 4], 1),
([1, 2, 3, 4, 9, 10, 11, 12], 2),
]
result, count = fixSizeInconsistencies(sizes, "test")
# Verify verbose was called with correct count: origNumSizes - numSize = 3 - 1 = 2
mock_verbose.assert_called_once_with(2, "duplicate size(s) removed from", "test", "logic file")
def test_no_duplicates(self):
"""Test with no duplicates"""
sizes = [
([1, 2, 3], 0),
([4, 5, 6], 1),
]
result, count = fixSizeInconsistencies(sizes, "test")
assert count == 2
@pytest.mark.unit
class TestAddKernel:
"""Test addKernel function"""
def test_add_new_kernel(self):
"""Test adding a new kernel"""
solutionPool = []
solDict = {}
solution = {"SolutionNameMin": "kernel_1", "data": "test"}
pool, sol_dict, index = addKernel(solutionPool, solDict, solution)
assert len(pool) == 1
assert index == 0
assert pool[0]["SolutionIndex"] == 0
assert "kernel_1" in sol_dict
def test_reuse_existing_kernel(self):
"""Test reusing an existing kernel"""
solution_existing = {"SolutionNameMin": "kernel_1", "SolutionIndex": 0, "data": "test"}
solutionPool = [solution_existing]
solDict = {"kernel_1": solution_existing}
solution_new = {"SolutionNameMin": "kernel_1", "data": "new"}
pool, sol_dict, index = addKernel(solutionPool, solDict, solution_new)
assert len(pool) == 1 # Should not add duplicate
assert index == 0
def test_add_multiple_kernels(self):
"""Test adding multiple different kernels"""
solutionPool = []
solDict = {}
sol1 = {"SolutionNameMin": "kernel_1"}
sol2 = {"SolutionNameMin": "kernel_2"}
pool, sol_dict, idx1 = addKernel(solutionPool, solDict, sol1)
pool, sol_dict, idx2 = addKernel(pool, sol_dict, sol2)
assert len(pool) == 2
assert idx1 == 0
assert idx2 == 1
@pytest.mark.unit
class TestSanitizeSolutions:
"""Test sanitizeSolutions function"""
def test_sanitize_with_stagger_u_zero(self):
"""Test sanitizing solutions with StaggerU == 0"""
solList = [
{"StaggerU": 0, "StaggerUMapping": 5, "StaggerUStride": 10, "_staggerStrideShift": 3}
]
sanitizeSolutions(solList)
assert solList[0]["StaggerUMapping"] == 0
assert solList[0]["StaggerUStride"] == 0
assert solList[0]["_staggerStrideShift"] == 0
def test_sanitize_with_stagger_u_nonzero(self):
"""Test solutions with StaggerU != 0 remain unchanged"""
solList = [
{"StaggerU": 1, "StaggerUMapping": 5, "StaggerUStride": 10, "_staggerStrideShift": 3}
]
original = deepcopy(solList)
sanitizeSolutions(solList)
assert solList[0]["StaggerUMapping"] == 5
assert solList[0]["StaggerUStride"] == 10
def test_sanitize_without_stagger_u(self):
"""Test solutions without StaggerU key remain unchanged"""
solList = [{"other": "value", "data": 123}]
original = deepcopy(solList)
sanitizeSolutions(solList)
# Verify solution is unchanged
assert solList == original
assert solList[0]["other"] == "value"
assert solList[0]["data"] == 123
@pytest.mark.unit
class TestReNameSolutions:
"""Test reNameSolutions function"""
@patch('Tensile.TensileMergeLibrary.getSolutionNameMin')
@patch('Tensile.TensileMergeLibrary.getKernelNameMin')
@patch('Tensile.TensileMergeLibrary.assignParameterWithDefault')
def test_rename_solutions(self, mock_assign, mock_kernel_name, mock_solution_name):
"""Test renaming solutions"""
mock_solution_name.return_value = "sol_min"
mock_kernel_name.return_value = "kernel_min"
problem_type = {"OperationType": "GEMM"}
solutions = [{"key": "value"}]
data = [None, None, None, None, problem_type, solutions]
reNameSolutions(data)
assert solutions[0]["SolutionNameMin"] == "sol_min"
assert solutions[0]["KernelNameMin"] == "kernel_min"
assert "ProblemType" not in solutions[0]
@pytest.mark.unit
class TestRemoveUnusedSolutions:
"""Test removeUnusedSolutions function"""
def test_remove_unused_solutions(self):
"""Test removing unused solutions"""
solutions = [
{"SolutionIndex": 0, "name": "sol0"},
{"SolutionIndex": 1, "name": "sol1"},
{"SolutionIndex": 2, "name": "sol2"},
]
size_map = [
([1, 2, 3], [0, 0.9]), # Uses solution 0
([4, 5, 6], [2, 0.8]), # Uses solution 2
]
data = [None, None, None, None, None, solutions, None, size_map]
result, num_removed = removeUnusedSolutions(data)
assert len(result[5]) == 2 # Only 2 solutions should remain
assert num_removed == 1
# Check reindexing
assert result[5][0]["SolutionIndex"] == 0
assert result[5][1]["SolutionIndex"] == 1
def test_all_solutions_used(self):
"""Test when all solutions are used"""
solutions = [
{"SolutionIndex": 0, "name": "sol0"},
{"SolutionIndex": 1, "name": "sol1"},
]
size_map = [
([1, 2, 3], [0, 0.9]),
([4, 5, 6], [1, 0.8]),
]
data = [None, None, None, None, None, solutions, None, size_map]
result, num_removed = removeUnusedSolutions(data)
assert len(result[5]) == 2
assert num_removed == 0
@pytest.mark.unit
class TestRemoveDuplicatedSolutions:
"""Test removeDuplicatedSolutions function"""
def test_remove_duplicates_by_solution_name(self):
"""Test removing solutions with duplicate SolutionNameMin"""
solutions = [
{"SolutionIndex": 0, "SolutionNameMin": "sol_A", "KernelNameMin": "kernel_1"},
{"SolutionIndex": 1, "SolutionNameMin": "sol_B", "KernelNameMin": "kernel_2"},
{"SolutionIndex": 2, "SolutionNameMin": "sol_A", "KernelNameMin": "kernel_1"}, # Duplicate
]
size_map = [
([1, 2, 3], [0, 0.9]),
([4, 5, 6], [2, 0.8]), # References duplicate (index 2 -> should become 0)
]
data = [None, None, None, None, None, solutions, None, size_map]
result, num_removed, num_solutions, num_kernels = removeDuplicatedSolutions(data)
# Verify return values
assert num_solutions == 2
assert num_removed == 1
assert num_kernels == 2
# Verify solutions list was deduplicated
assert len(result[5]) == 2
assert result[5][0]["SolutionNameMin"] == "sol_A"
assert result[5][1]["SolutionNameMin"] == "sol_B"
# Verify solutions were reindexed
assert result[5][0]["SolutionIndex"] == 0
assert result[5][1]["SolutionIndex"] == 1
# Verify size_map was updated to reference correct solution indices
assert result[7][0][1][0] == 0 # First size still references sol_A (index 0)
assert result[7][1][1][0] == 0 # Second size now references sol_A (was 2, now 0)
def test_no_duplicates(self):
"""Test with no duplicate solutions"""
solutions = [
{"SolutionIndex": 0, "SolutionNameMin": "sol_A", "KernelNameMin": "kernel_1"},
{"SolutionIndex": 1, "SolutionNameMin": "sol_B", "KernelNameMin": "kernel_2"},
]
size_map = [
([1, 2, 3], [0, 0.9]),
([4, 5, 6], [1, 0.8]),
]
data = [None, None, None, None, None, solutions, None, size_map]
result, num_removed, num_solutions, num_kernels = removeDuplicatedSolutions(data)
# Verify return values
assert num_solutions == 2
assert num_removed == 0
assert num_kernels == 2
# Verify solutions list is unchanged (still has both)
assert len(result[5]) == 2
assert result[5][0]["SolutionNameMin"] == "sol_A"
assert result[5][1]["SolutionNameMin"] == "sol_B"
# Verify solutions are still correctly indexed
assert result[5][0]["SolutionIndex"] == 0
assert result[5][1]["SolutionIndex"] == 1
# Verify size_map is unchanged
assert result[7][0][1][0] == 0 # First size references sol_A (index 0)
assert result[7][1][1][0] == 1 # Second size references sol_B (index 1)
@pytest.mark.unit
class TestLoadData:
"""Test loadData function"""
@patch('Tensile.TensileMergeLibrary.load_yaml_stream')
def test_load_data(self, mock_load):
"""Test loading data from YAML file"""
mock_data = {"key": "value"}
mock_load.return_value = mock_data
result = loadData("test.yaml")
assert result[0] == "test.yaml"
assert result[1] == mock_data
mock_load.assert_called_once()
@pytest.mark.unit
class TestCompareDestFolderToYaml:
"""Test compareDestFolderToYaml function"""
@pytest.mark.parametrize("folder,yaml_attr,should_raise,test_id", [
# Matching cases - should not raise
("Equality", "Equality", False, "equality_matches"),
("GridBased", "GridBased", False, "gridbased_matches"),
# Mismatching cases - should raise
("Equality", "GridBased", True, "equality_mismatch"),
("GridBased", "Equality", True, "gridbased_mismatch"),
# Empty attribute - should raise
("Equality", None, True, "empty_attribute"),
# Non-checkable folder - should not raise (ignored)
("OtherFolder", "Equality", False, "non_checkable_folder"),
("OtherFolder", "GridBased", False, "non_checkable_folder_gridbased"),
("OtherFolder", None, True, "non_checkable_empty_attr"),
])
def test_folder_yaml_comparison(self, folder, yaml_attr, should_raise, test_id):
"""Test folder and YAML attribute comparison with various scenarios"""
original_dir = f"/path/to/{folder}"
inc_data = [None] * 12
inc_data[11] = yaml_attr
if should_raise:
with pytest.raises(SystemExit):
compareDestFolderToYaml(original_dir, "test.yaml", inc_data)
else:
# Should not raise
compareDestFolderToYaml(original_dir, "test.yaml", inc_data)
@pytest.mark.unit
class TestCompareProblemType:
"""Test compareProblemType function"""
@patch('Tensile.TensileMergeLibrary.problemTypeToEnum')
@patch('Tensile.TensileMergeLibrary.ProblemType')
def test_matching_problem_types(self, mock_problem_type, mock_enum):
"""Test with matching problem types"""
mock_pt = Mock()
mock_pt.state = {"OperationType": "GEMM", "DataType": "FP32"}
mock_problem_type.return_value = mock_pt
ori_data = [None, None, None, None, {"OperationType": "GEMM"}]
inc_data = [None, None, None, None, {"OperationType": "GEMM"}]
# Should not raise
compareProblemType(ori_data, inc_data)
@patch('Tensile.TensileMergeLibrary.problemTypeToEnum')
@patch('Tensile.TensileMergeLibrary.ProblemType')
def test_mismatching_problem_types(self, mock_problem_type, mock_enum):
"""Test with mismatching problem types"""
mock_pt1 = Mock()
mock_pt1.state = {"OperationType": "GEMM", "DataType": "FP32"}
mock_pt2 = Mock()
mock_pt2.state = {"OperationType": "GEMM", "DataType": "FP16"}
mock_problem_type.side_effect = [mock_pt1, mock_pt2]
ori_data = [None, None, None, None, {"OperationType": "GEMM"}]
inc_data = [None, None, None, None, {"OperationType": "GEMM"}]
with pytest.raises(SystemExit):
compareProblemType(ori_data, inc_data)
@pytest.mark.unit
class TestMessageFunctions:
"""Test msg, verbose, and debug functions"""
@patch('builtins.print')
def test_msg_output(self, mock_print):
"""Test msg function"""
msg("test", "message")
assert mock_print.call_count >= 1
@patch('builtins.print')
@patch('Tensile.TensileMergeLibrary.verbosity', 1)
def test_verbose_output_when_enabled(self, mock_print):
"""Test verbose function when verbosity >= 1"""
verbose("test", "message")
assert mock_print.call_count >= 1
@patch('builtins.print')
@patch('Tensile.TensileMergeLibrary.verbosity', 0)
def test_verbose_no_output_when_disabled(self, mock_print):
"""Test verbose function when verbosity < 1"""
verbose("test", "message")
assert mock_print.call_count == 0
@patch('builtins.print')
@patch('Tensile.TensileMergeLibrary.verbosity', 2)
def test_debug_output_when_enabled(self, mock_print):
"""Test debug function when verbosity >= 2"""
debug("test", "message")
assert mock_print.call_count >= 1
@patch('builtins.print')
@patch('Tensile.TensileMergeLibrary.verbosity', 1)
def test_debug_no_output_when_disabled(self, mock_print):
"""Test debug function when verbosity < 2"""
debug("test", "message")
assert mock_print.call_count == 0
@pytest.mark.unit
class TestFindSolutionWithIndex:
"""Test findSolutionWithIndex function"""
def test_find_solution_at_correct_index(self):
"""Test finding solution when index matches position"""
solutions = [
{"SolutionIndex": 0, "name": "sol0"},
{"SolutionIndex": 1, "name": "sol1"},
{"SolutionIndex": 2, "name": "sol2"},
]
result = findSolutionWithIndex(solutions, 1)
assert result["name"] == "sol1"
def test_find_solution_with_search(self):
"""Test finding solution when index doesn't match position"""
solutions = [
{"SolutionIndex": 5, "name": "sol5"},
{"SolutionIndex": 10, "name": "sol10"},
{"SolutionIndex": 15, "name": "sol15"},
]
result = findSolutionWithIndex(solutions, 10)
assert result["name"] == "sol10"
def test_find_nonexistent_solution(self):
"""Test finding non-existent solution raises assertion"""
solutions = [
{"SolutionIndex": 0, "name": "sol0"},
]
with pytest.raises(AssertionError):
findSolutionWithIndex(solutions, 99)
@pytest.mark.unit
class TestMergeLogic:
"""Test mergeLogic function"""
@patch('Tensile.TensileMergeLibrary.findSolutionWithIndex')
@patch('Tensile.TensileMergeLibrary.removeUnusedSolutions')
@patch('Tensile.TensileMergeLibrary.fixSizeInconsistencies')
def test_merge_with_new_sizes(self, mock_fix, mock_remove, mock_find):
"""Test merging logic with new sizes"""
# Setup mocks
mock_fix.side_effect = lambda x, t: (x, len(x))
mock_remove.side_effect = lambda x, prefix="": (x, 0)
mock_find.return_value = {"SolutionNameMin": "sol1", "KernelNameMin": "ker1"}
ori_data = [
None, None, None, None, None,
[{"SolutionNameMin": "sol0", "SolutionIndex": 0}], # Solutions
None,
[[list([1, 2, 3]), [0, 0.5]]], # Size map - use list for mutability
]
inc_data = [
None, None, None, None, None,
[{"SolutionNameMin": "sol1", "SolutionIndex": 0}],
None,
[[list([4, 5, 6]), [0, 0.8]]], # New size
]
result, num_sizes, num_solutions, num_removed = mergeLogic(
ori_data, inc_data, forceMerge=False, noEff=False
)
# Verify all four return values
assert num_sizes == 1 # One new size added
assert num_solutions == 1 # One solution added (sol1)
assert num_removed == 0 # Mock returns 0
assert isinstance(result, list)
# Verify merge policy: new size added
assert len(result[7]) == 2 # Original 1 + new 1 = 2 sizes
assert result[7][0][0] == [1, 2, 3] # Original size preserved
assert result[7][1][0] == [4, 5, 6] # New size added
# Verify post-merge cleanup ran
assert len(result[5]) == 2 # Original sol0 + new sol1
mock_find.assert_called_once()
@patch('Tensile.TensileMergeLibrary.findSolutionWithIndex')
@patch('Tensile.TensileMergeLibrary.removeUnusedSolutions')
@patch('Tensile.TensileMergeLibrary.fixSizeInconsistencies')
def test_merge_with_better_efficiency(self, mock_fix, mock_remove, mock_find):
"""Test merging with improved efficiency"""
mock_fix.side_effect = lambda x, t: (x, len(x))
mock_remove.side_effect = lambda x, prefix="": (x, 0)
mock_find.return_value = {"SolutionNameMin": "sol_new", "KernelNameMin": "ker_new"}
ori_data = [
None, None, None, None, None,
[{"SolutionNameMin": "sol0", "SolutionIndex": 0}],
None,
[[list([1, 2, 3]), [0, 0.5]]], # Use list instead of tuple for mutability
]
inc_data = [
None, None, None, None, None,
[{"SolutionNameMin": "sol_new", "SolutionIndex": 0}],
None,
[[list([1, 2, 3]), [0, 0.9]]], # Better efficiency 0.9
]
result, num_sizes, num_solutions, num_removed = mergeLogic(
ori_data, inc_data, forceMerge=False, noEff=False
)
# Verify all four return values
assert num_sizes == 0 # No new sizes (same size, better efficiency replaces)
assert num_solutions == 1 # One solution added (sol_new)
assert num_removed == 0 # Mock returns 0 for removeUnusedSolutions
assert isinstance(result, list)
# Verify merge policy: better solution accepted
assert len(result[7]) == 1 # Still has 1 size (replaced, not added)
assert result[7][0][1][1] == 0.9 # Efficiency updated (0.5 -> 0.9)
# Verify post-merge cleanup ran (solution pool updated)
assert len(result[5]) == 2 # Original sol0 + new sol_new
# Verify findSolutionWithIndex was called to get the new solution
mock_find.assert_called_once()
@patch('Tensile.TensileMergeLibrary.findSolutionWithIndex')
@patch('Tensile.TensileMergeLibrary.removeUnusedSolutions')
@patch('Tensile.TensileMergeLibrary.fixSizeInconsistencies')
def test_merge_with_force_merge(self, mock_fix, mock_remove, mock_find):
"""Test force merge even with worse efficiency"""
mock_fix.side_effect = lambda x, t: (x, len(x))
mock_remove.side_effect = lambda x, prefix="": (x, 0)
mock_find.return_value = {"SolutionNameMin": "sol_forced", "KernelNameMin": "ker"}
ori_data = [
None, None, None, None, None,
[{"SolutionNameMin": "sol0", "SolutionIndex": 0}],
None,
[[list([1, 2, 3]), [0, 0.9]]], # Use list instead of tuple for mutability
]
inc_data = [
None, None, None, None, None,
[{"SolutionNameMin": "sol_forced", "SolutionIndex": 0}],
None,
[[list([1, 2, 3]), [0, 0.3]]], # Worse efficiency
]
result, num_sizes, num_solutions, num_removed = mergeLogic(
ori_data, inc_data, forceMerge=True, noEff=False
)
# Verify all four return values
assert num_sizes == 0 # No new sizes (same size replaced)
assert num_solutions == 1 # One solution added (sol_forced)
assert num_removed == 0 # Mock returns 0
assert isinstance(result, list)
# Verify merge policy: forceMerge accepts even worse efficiency
assert len(result[7]) == 1 # Still 1 size (replaced)
assert result[7][0][1][1] == 0.3 # Efficiency downgraded (0.9 -> 0.3) due to forceMerge
# Verify post-merge cleanup ran
assert len(result[5]) == 2 # Original sol0 + forced sol_forced
mock_find.assert_called_once()
@patch('Tensile.TensileMergeLibrary.findSolutionWithIndex')
@patch('Tensile.TensileMergeLibrary.removeUnusedSolutions')
@patch('Tensile.TensileMergeLibrary.fixSizeInconsistencies')
def test_merge_with_no_eff(self, mock_fix, mock_remove, mock_find):
"""Test merge with noEff flag"""
mock_fix.side_effect = lambda x, t: (x, len(x))
mock_remove.side_effect = lambda x, prefix="": (x, 0)
mock_find.return_value = {"SolutionNameMin": "sol1", "KernelNameMin": "ker1"}
ori_data = [
None, None, None, None, None,
[{"SolutionNameMin": "sol0", "SolutionIndex": 0}],
None,
[[list([1, 2, 3]), [0, 0.5]]], # Use list for mutability
]
inc_data = [
None, None, None, None, None,
[{"SolutionNameMin": "sol1", "SolutionIndex": 0}],
None,
[[list([4, 5, 6]), [0, 0.8]]],
]
result, num_sizes, num_solutions, num_removed = mergeLogic(
ori_data, inc_data, forceMerge=False, noEff=True
)
# Verify all four return values
assert num_sizes == 1 # One new size added
assert num_solutions == 1 # One solution added (sol1)
assert num_removed == 0 # Mock returns 0
assert isinstance(result, list)
# Verify merge policy: noEff flag sets efficiency to 0.0
assert len(result[7]) == 2 # Original 1 + new 1 = 2 sizes
assert result[7][0][1][1] == 0.5 # Original efficiency preserved
assert result[7][1][1][1] == 0.0 # New size efficiency set to 0.0 (not 0.8) due to noEff
# Verify post-merge cleanup ran
assert len(result[5]) == 2 # Original sol0 + new sol1
mock_find.assert_called_once()
@pytest.mark.unit
class TestMainFunction:
"""Test main function argument parsing"""
@patch('Tensile.TensileMergeLibrary.avoidRegressions')
@patch('sys.argv', ['script', '/orig', '/inc', '/out', '-v', '2'])
def test_main_with_arguments(self, mock_avoid):
"""Test main function with command line arguments"""
from Tensile.TensileMergeLibrary import main
main()
mock_avoid.assert_called_once()
args = mock_avoid.call_args[0]
assert args[0] == '/orig'
assert args[1] == '/inc'
assert args[2] == '/out'
@patch('Tensile.TensileMergeLibrary.avoidRegressions')
@patch('sys.argv', ['script', '/orig', '/inc', '/out', '--force_merge', 'true'])
def test_main_with_force_merge_true(self, mock_avoid):
"""Test main with force_merge=true"""
from Tensile.TensileMergeLibrary import main
main()
args = mock_avoid.call_args[0]
assert args[3] == True # forceMerge
@patch('Tensile.TensileMergeLibrary.avoidRegressions')
@patch('sys.argv', ['script', '/orig', '/inc', '/out', '--force_merge', 'false'])
def test_main_with_force_merge_false(self, mock_avoid):
"""Test main with force_merge=false"""
from Tensile.TensileMergeLibrary import main
main()
args = mock_avoid.call_args[0]
assert args[3] == False # forceMerge
@patch('Tensile.TensileMergeLibrary.avoidRegressions')
@patch('sys.argv', ['script', '/orig', '/inc', '/out', '--no_eff'])
def test_main_with_no_eff_flag(self, mock_avoid):
"""Test main with --no_eff flag"""
from Tensile.TensileMergeLibrary import main
main()
kwargs = mock_avoid.call_args[0]
assert kwargs[4] == True # no_eff