Files
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

261 lines
8.8 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
/**
* @file TestHeuristicPluginManager.cpp
* @brief Unit tests for HeuristicPluginManager validation logic
*
* These tests verify the plugin discovery and validation layer including:
* - API version compatibility validation
* - Policy ID uniqueness validation
* - Policy ID ↔ policy name consistency validation
*/
#include "HipdnnException.hpp"
#include "plugin/HeuristicPlugin.hpp"
#include "plugin/HeuristicPluginManager.hpp"
#include <hipdnn_data_sdk/utilities/PolicyNames.hpp>
#include <hipdnn_test_sdk/utilities/FileUtilities.hpp>
#include <filesystem>
#include <gtest/gtest.h>
using namespace hipdnn_backend;
using namespace hipdnn_backend::plugin;
class TestHeuristicPluginManager : public ::testing::Test
{
protected:
void SetUp() override
{
// Most tests use validation logic, not actual plugin loading
}
void TearDown() override {}
// Helper to create a valid policy name/ID pair
static std::pair<std::string, int64_t> makeValidPolicyPair(const std::string& baseName)
{
const int64_t policyId = hipdnn_data_sdk::utilities::policyNameToId(baseName);
return {baseName, policyId};
}
};
// ========== Construction Tests ==========
TEST_F(TestHeuristicPluginManager, ConstructorSucceeds)
{
EXPECT_NO_THROW(const HeuristicPluginManager manager);
}
// ========== Plugin Loading Tests ==========
TEST_F(TestHeuristicPluginManager, LoadPluginsFromEmptyDirectorySucceeds)
{
HeuristicPluginManager manager;
const auto uniqueName = std::string("hipdnn_test_empty_")
+ std::to_string(::testing::UnitTest::GetInstance()->random_seed());
const hipdnn_test_sdk::utilities::ScopedDirectory emptyDir(
std::filesystem::temp_directory_path() / uniqueName);
EXPECT_NO_THROW(manager.loadPlugins({emptyDir.path()}, HIPDNN_PLUGIN_LOADING_ABSOLUTE));
}
TEST_F(TestHeuristicPluginManager, LoadPluginsFromNonexistentDirectorySucceeds)
{
HeuristicPluginManager manager;
const std::filesystem::path nonexistentDir
= std::filesystem::temp_directory_path() / "nonexistent_path_to_plugins";
// Should not throw - just logs warning and continues
EXPECT_NO_THROW(manager.loadPlugins({nonexistentDir}, HIPDNN_PLUGIN_LOADING_ABSOLUTE));
}
TEST_F(TestHeuristicPluginManager, LoadPluginsWithMultiplePathsSucceeds)
{
HeuristicPluginManager manager;
const std::set<std::filesystem::path> paths
= {std::filesystem::temp_directory_path() / "path1",
std::filesystem::temp_directory_path() / "path2",
std::filesystem::temp_directory_path() / "path3"};
// Should not throw
EXPECT_NO_THROW(manager.loadPlugins(paths, HIPDNN_PLUGIN_LOADING_ABSOLUTE));
}
// Note: Actual validation happens inside validateBeforeAdding() which is protected.
// It is exercised end-to-end in TestHeuristicPluginManagerValidationPaths.cpp using
// real test plugins.
// ========== Multiple Load Cycles Tests ==========
TEST_F(TestHeuristicPluginManager, MultipleLoadCyclesSucceed)
{
HeuristicPluginManager manager;
const std::set<std::filesystem::path> paths
= {std::filesystem::temp_directory_path() / "test_plugins"};
// Load multiple times
for(int i = 0; i < 5; ++i)
{
EXPECT_NO_THROW(manager.loadPlugins(paths, HIPDNN_PLUGIN_LOADING_ABSOLUTE));
}
}
TEST_F(TestHeuristicPluginManager, LoadingDifferentPathsSucceeds)
{
HeuristicPluginManager manager;
// Load from path 1
EXPECT_NO_THROW(manager.loadPlugins({std::filesystem::temp_directory_path() / "path1"},
HIPDNN_PLUGIN_LOADING_ABSOLUTE));
// Load from path 2
EXPECT_NO_THROW(manager.loadPlugins({std::filesystem::temp_directory_path() / "path2"},
HIPDNN_PLUGIN_LOADING_ABSOLUTE));
}
// ========== Thread Safety Tests ==========
TEST_F(TestHeuristicPluginManager, MultipleInstancesAreIndependent)
{
HeuristicPluginManager manager1;
HeuristicPluginManager manager2;
// Load different paths
manager1.loadPlugins({std::filesystem::temp_directory_path() / "path1"},
HIPDNN_PLUGIN_LOADING_ABSOLUTE);
manager2.loadPlugins({std::filesystem::temp_directory_path() / "path2"},
HIPDNN_PLUGIN_LOADING_ABSOLUTE);
// Only the always-registered Config + StaticOrdering built-ins remain; no
// external plugin loaded from a non-existent path.
EXPECT_EQ(manager1.getPlugins().size(), 2u);
EXPECT_EQ(manager2.getPlugins().size(), 2u);
}
// ========== Edge Cases Tests ==========
TEST_F(TestHeuristicPluginManager, LoadPluginsWithEmptyPathListSucceeds)
{
HeuristicPluginManager manager;
const std::set<std::filesystem::path> emptyPaths;
// Should not throw
EXPECT_NO_THROW(manager.loadPlugins(emptyPaths, HIPDNN_PLUGIN_LOADING_ABSOLUTE));
}
TEST_F(TestHeuristicPluginManager, LoadPluginsWithSamePathTwiceSucceeds)
{
HeuristicPluginManager manager;
const std::set<std::filesystem::path> paths = {std::filesystem::temp_directory_path() / "test"};
// Set automatically handles duplicates, but test that loading twice works
manager.loadPlugins(paths, HIPDNN_PLUGIN_LOADING_ABSOLUTE);
EXPECT_NO_THROW(manager.loadPlugins(paths, HIPDNN_PLUGIN_LOADING_ABSOLUTE));
}
// ========== Plugin Loading Mode Tests ==========
TEST_F(TestHeuristicPluginManager, LoadPluginsWithAbsoluteModeResets)
{
HeuristicPluginManager manager;
const std::set<std::filesystem::path> paths1
= {std::filesystem::temp_directory_path() / "path1"};
const std::set<std::filesystem::path> paths2
= {std::filesystem::temp_directory_path() / "path2"};
manager.loadPlugins(paths1, HIPDNN_PLUGIN_LOADING_ABSOLUTE);
const size_t count1 = manager.getPlugins().size();
manager.loadPlugins(paths2, HIPDNN_PLUGIN_LOADING_ABSOLUTE);
const size_t count2 = manager.getPlugins().size();
// Both should be 0 since paths don't exist, but operation should succeed
EXPECT_EQ(count1, count2);
}
TEST_F(TestHeuristicPluginManager, LoadPluginsWithAdditiveModeAdds)
{
HeuristicPluginManager manager;
const std::set<std::filesystem::path> paths1
= {std::filesystem::temp_directory_path() / "path1"};
const std::set<std::filesystem::path> paths2
= {std::filesystem::temp_directory_path() / "path2"};
// Exercise the additive code path. With non-existent paths the plugin count
// stays at 0, but the additive branch in PluginManagerBase::loadPlugins is
// executed and any leak/crash inside it would be caught under ASAN.
EXPECT_NO_THROW(manager.loadPlugins(paths1, HIPDNN_PLUGIN_LOADING_ABSOLUTE));
EXPECT_NO_THROW(manager.loadPlugins(paths2, HIPDNN_PLUGIN_LOADING_ADDITIVE));
}
// Destructor coverage with real loaded plugins lives in
// TestHeuristicPluginManagerValidationPaths.cpp::DestructorUnloadsLoadedPluginLibraries,
// where _testPluginPath provides actual shared libraries to exercise the unload path.
// ========== Policy ID/Name Validation Tests ==========
TEST_F(TestHeuristicPluginManager, PolicyNameToIdIsConsistent)
{
const std::string name1 = "Vendor::PolicyA";
const int64_t id1a = hipdnn_data_sdk::utilities::policyNameToId(name1);
const int64_t id1b = hipdnn_data_sdk::utilities::policyNameToId(name1);
EXPECT_EQ(id1a, id1b);
}
TEST_F(TestHeuristicPluginManager, DifferentNamesProduceDifferentIds)
{
const std::string name1 = "Vendor::PolicyA";
const std::string name2 = "SelectionHeuristic::StaticOrdering";
const int64_t id1 = hipdnn_data_sdk::utilities::policyNameToId(name1);
const int64_t id2 = hipdnn_data_sdk::utilities::policyNameToId(name2);
EXPECT_NE(id1, id2);
}
TEST_F(TestHeuristicPluginManager, PolicyIdIsNonZero)
{
const std::string name = "Vendor::PolicyA";
const int64_t id = hipdnn_data_sdk::utilities::policyNameToId(name);
EXPECT_NE(id, 0);
}
TEST_F(TestHeuristicPluginManager, EmptyPolicyNameProducesZeroId)
{
const std::string emptyName;
const int64_t id = hipdnn_data_sdk::utilities::policyNameToId(emptyName);
// Empty string should produce a specific ID (FNV-1a hash of empty string)
EXPECT_EQ(id, 0);
}
// ========== State Verification Tests ==========
TEST_F(TestHeuristicPluginManager, GetPluginsAfterEmptyLoadReturnsEmpty)
{
HeuristicPluginManager manager;
const auto uniqueName = std::string("hipdnn_empty_load_test_")
+ std::to_string(::testing::UnitTest::GetInstance()->random_seed());
const hipdnn_test_sdk::utilities::ScopedDirectory emptyDir(
std::filesystem::temp_directory_path() / uniqueName);
manager.loadPlugins({emptyDir.path()}, HIPDNN_PLUGIN_LOADING_ABSOLUTE);
// Only the always-registered Config + StaticOrdering built-ins remain; the
// empty dir contributed nothing.
EXPECT_EQ(manager.getPlugins().size(), 2u);
}