Files
TheRock/rocm-libraries/dnn-providers/integration-tests/tests/TestBundleMetadata.cpp
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

514 lines
17 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <gtest/gtest.h>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <string>
#include <hipdnn_test_sdk/utilities/BundleMetadata.hpp>
#include <hipdnn_test_sdk/utilities/FileUtilities.hpp>
using hipdnn_test_sdk::utilities::BundleMetadata;
using hipdnn_test_sdk::utilities::checkArchCompatibility;
using hipdnn_test_sdk::utilities::checkVramRequirement;
using hipdnn_test_sdk::utilities::isMetaJsonFile;
using hipdnn_test_sdk::utilities::loadBundleMetadata;
using hipdnn_test_sdk::utilities::metaJsonPath;
// NOLINTBEGIN(readability-identifier-naming) -- gtest macro-generated names
namespace
{
/// Helper: create a temporary directory with a fake bundle JSON and optional
/// .meta.json companion. Auto-cleans on destruction via ScopedDirectory.
class TempBundle
{
public:
explicit TempBundle(const std::string& metaJsonContent = "")
: _dir(std::filesystem::temp_directory_path()
/ ("test_bundle_" + std::to_string(std::rand())))
{
// Create a minimal bundle JSON (enough for path derivation)
std::ofstream bundleFile(_dir.path() / "Bundle.json");
bundleFile << "{}";
bundleFile.close();
if(!metaJsonContent.empty())
{
std::ofstream metaFile(_dir.path() / "Bundle.meta.json");
metaFile << metaJsonContent;
metaFile.close();
}
}
std::filesystem::path bundleJsonPath() const
{
return _dir.path() / "Bundle.json";
}
private:
hipdnn_test_sdk::utilities::ScopedDirectory _dir;
};
} // namespace
// ---------------------------------------------------------------------------
// isMetaJsonFile
// ---------------------------------------------------------------------------
TEST(TestIsMetaJsonFile, DetectsMetaJson)
{
EXPECT_TRUE(isMetaJsonFile("Small.meta.json"));
EXPECT_TRUE(isMetaJsonFile("Large.meta.json"));
EXPECT_TRUE(isMetaJsonFile("a.b.meta.json"));
EXPECT_TRUE(isMetaJsonFile("/some/path/Bundle.meta.json"));
}
TEST(TestIsMetaJsonFile, RejectsBundleJson)
{
EXPECT_FALSE(isMetaJsonFile("Small.json"));
EXPECT_FALSE(isMetaJsonFile("Large.json"));
EXPECT_FALSE(isMetaJsonFile("/some/path/Bundle.json"));
}
TEST(TestIsMetaJsonFile, RejectsPlainMetaJson)
{
// "meta.json" has stem "meta" with no compound extension — not a bundle companion
EXPECT_FALSE(isMetaJsonFile("meta.json"));
EXPECT_FALSE(isMetaJsonFile("/some/path/meta.json"));
}
TEST(TestIsMetaJsonFile, RejectsNonJsonFiles)
{
EXPECT_FALSE(isMetaJsonFile("Small.meta.bin"));
EXPECT_FALSE(isMetaJsonFile("Small.tensor0.bin"));
EXPECT_FALSE(isMetaJsonFile("README.md"));
}
// ---------------------------------------------------------------------------
// metaJsonPath
// ---------------------------------------------------------------------------
TEST(TestMetaJsonPath, DerivesMetaPathFromBundleJson)
{
EXPECT_EQ(metaJsonPath("/dir/Small.json"), "/dir/Small.meta.json");
EXPECT_EQ(metaJsonPath("/a/b/Large.json"), "/a/b/Large.meta.json");
EXPECT_EQ(metaJsonPath("Bundle.json"), "Bundle.meta.json");
}
// ---------------------------------------------------------------------------
// loadBundleMetadata — reader
// ---------------------------------------------------------------------------
TEST(TestLoadBundleMetadata, LoadsValidFullMetadata)
{
const TempBundle bundle(R"({
"format_version": 1,
"generator": "scripts/generate_golden.py",
"generator_version": "1.0.0",
"generated_at": "2026-05-04T18:00:00Z",
"gpu_architecture": "gfx942",
"rocm_version": "6.4.0",
"reference_source": "PyTorch 2.3.0",
"reference_source_hash": "a3f8c2e1",
"reference_strategy": "precision_uplift",
"operation": "conv_fwd",
"generation_command": "python generate.py --op conv_fwd",
"notes": "baseline run",
"seed": 42,
"minimum_vram_mb": 8192
})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
EXPECT_EQ(meta->formatVersion, 1);
EXPECT_EQ(meta->generator, "scripts/generate_golden.py");
EXPECT_EQ(meta->generatorVersion, "1.0.0");
EXPECT_EQ(meta->generatedAt, "2026-05-04T18:00:00Z");
EXPECT_EQ(meta->gpuArchitecture, "gfx942");
EXPECT_EQ(meta->rocmVersion, "6.4.0");
EXPECT_EQ(meta->referenceSource, "PyTorch 2.3.0");
EXPECT_EQ(meta->referenceSourceHash, "a3f8c2e1");
EXPECT_EQ(meta->referenceStrategy, "precision_uplift");
EXPECT_EQ(meta->operation, "conv_fwd");
EXPECT_EQ(meta->generationCommand, "python generate.py --op conv_fwd");
EXPECT_EQ(meta->notes, "baseline run");
EXPECT_EQ(meta->seed, 42);
EXPECT_EQ(meta->minimumVramMb, 8192);
}
TEST(TestLoadBundleMetadata, LoadsMinimalMetadata)
{
const TempBundle bundle(R"({"format_version": 1})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
EXPECT_EQ(meta->formatVersion, 1);
EXPECT_FALSE(meta->generator.has_value());
EXPECT_FALSE(meta->generatorVersion.has_value());
EXPECT_FALSE(meta->generatedAt.has_value());
EXPECT_FALSE(meta->gpuArchitecture.has_value());
EXPECT_FALSE(meta->rocmVersion.has_value());
EXPECT_FALSE(meta->referenceSource.has_value());
EXPECT_FALSE(meta->referenceSourceHash.has_value());
EXPECT_FALSE(meta->referenceStrategy.has_value());
EXPECT_FALSE(meta->operation.has_value());
EXPECT_FALSE(meta->generationCommand.has_value());
EXPECT_FALSE(meta->notes.has_value());
EXPECT_FALSE(meta->seed.has_value());
EXPECT_FALSE(meta->minimumVramMb.has_value());
}
TEST(TestLoadBundleMetadata, ReturnsNulloptWhenFileNotFound)
{
const TempBundle bundle; // no meta.json created
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
EXPECT_FALSE(meta.has_value());
}
TEST(TestLoadBundleMetadata, ReturnsNulloptOnMalformedJson)
{
const TempBundle bundle("{not valid json");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
EXPECT_FALSE(meta.has_value());
}
TEST(TestLoadBundleMetadata, ReturnsNulloptOnMissingFormatVersion)
{
const TempBundle bundle(R"({"operation": "conv_fwd"})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
EXPECT_FALSE(meta.has_value());
}
TEST(TestLoadBundleMetadata, ReturnsNulloptOnWrongFormatVersion)
{
const TempBundle bundle(R"({"format_version": 99})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
EXPECT_FALSE(meta.has_value());
}
TEST(TestLoadBundleMetadata, IgnoresUnknownFields)
{
const TempBundle bundle(R"({
"format_version": 1,
"unknown_field": true,
"generator_version": "1.0.0",
"another_unknown": "should be ignored"
})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
EXPECT_EQ(meta->generatorVersion, "1.0.0");
}
TEST(TestLoadBundleMetadata, HandlesPartialMetadata)
{
const TempBundle bundle(R"({
"format_version": 1,
"operation": "batchnorm_fwd",
"minimum_vram_mb": 4096
})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
EXPECT_EQ(meta->operation, "batchnorm_fwd");
EXPECT_EQ(meta->minimumVramMb, 4096);
EXPECT_FALSE(meta->generatorVersion.has_value());
EXPECT_FALSE(meta->gpuArchitecture.has_value());
EXPECT_FALSE(meta->seed.has_value());
}
TEST(TestLoadBundleMetadata, HandlesNegativeVram)
{
const TempBundle bundle(R"({
"format_version": 1,
"minimum_vram_mb": -1
})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
EXPECT_EQ(meta->minimumVramMb, -1);
}
TEST(TestLoadBundleMetadata, HandlesZeroVram)
{
const TempBundle bundle(R"({
"format_version": 1,
"minimum_vram_mb": 0
})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
EXPECT_EQ(meta->minimumVramMb, 0);
}
TEST(TestLoadBundleMetadata, IgnoresFloatWhereIntegerExpected)
{
// minimum_vram_mb is 8.5 (float, not integer) — should be treated as absent
const TempBundle bundle(R"({
"format_version": 1,
"minimum_vram_mb": 8.5,
"seed": 3.14
})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
EXPECT_FALSE(meta->minimumVramMb.has_value());
EXPECT_FALSE(meta->seed.has_value());
}
TEST(TestLoadBundleMetadata, ReturnsNulloptOnStringFormatVersion)
{
const TempBundle bundle(R"({"format_version": "1"})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
EXPECT_FALSE(meta.has_value());
}
TEST(TestLoadBundleMetadata, ReturnsNulloptOnFloatFormatVersion)
{
// 1.0 is a float, not an integer — is_number_integer() returns false
const TempBundle bundle(R"({"format_version": 1.0})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
EXPECT_FALSE(meta.has_value());
}
TEST(TestLoadBundleMetadata, HandlesEmptyStringFields)
{
const TempBundle bundle(R"({
"format_version": 1,
"gpu_architecture": "",
"reference_source": ""
})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
// Empty strings are stored as-is — they are present but empty
ASSERT_TRUE(meta->gpuArchitecture.has_value());
EXPECT_EQ(*meta->gpuArchitecture, "");
ASSERT_TRUE(meta->referenceSource.has_value());
EXPECT_EQ(*meta->referenceSource, "");
}
// ---------------------------------------------------------------------------
// checkVramRequirement — pure guard function
// ---------------------------------------------------------------------------
TEST(TestCheckVramRequirement, PassesWhenVramNotSet)
{
const BundleMetadata meta;
// minimumVramMb is nullopt
EXPECT_FALSE(checkVramRequirement(meta, 8192).has_value());
}
TEST(TestCheckVramRequirement, PassesWhenVramZero)
{
BundleMetadata meta;
meta.minimumVramMb = 0;
EXPECT_FALSE(checkVramRequirement(meta, 8192).has_value());
}
TEST(TestCheckVramRequirement, PassesWhenVramNegative)
{
BundleMetadata meta;
meta.minimumVramMb = -1;
EXPECT_FALSE(checkVramRequirement(meta, 8192).has_value());
}
TEST(TestCheckVramRequirement, PassesWhenDeviceCannotBeQueried)
{
BundleMetadata meta;
meta.minimumVramMb = 16000;
// deviceTotalVramMb = 0 means "could not query device"
EXPECT_FALSE(checkVramRequirement(meta, 0).has_value());
}
TEST(TestCheckVramRequirement, PassesWhenDeviceHasEnoughVram)
{
BundleMetadata meta;
meta.minimumVramMb = 8192;
EXPECT_FALSE(checkVramRequirement(meta, 16384).has_value());
}
TEST(TestCheckVramRequirement, PassesWhenDeviceHasExactVram)
{
BundleMetadata meta;
meta.minimumVramMb = 8192;
EXPECT_FALSE(checkVramRequirement(meta, 8192).has_value());
}
TEST(TestCheckVramRequirement, SkipsWhenDeviceHasInsufficientVram)
{
BundleMetadata meta;
meta.minimumVramMb = 16000;
auto result = checkVramRequirement(meta, 8192);
ASSERT_TRUE(result.has_value());
EXPECT_NE(result->find("16000"), std::string::npos);
EXPECT_NE(result->find("8192"), std::string::npos);
}
// ---------------------------------------------------------------------------
// checkArchCompatibility — pure guard function
// ---------------------------------------------------------------------------
TEST(TestCheckArchCompatibility, PassesWhenArchNotSet)
{
// No gpu_architecture → data is portable → pass on any device
const BundleMetadata meta;
EXPECT_FALSE(checkArchCompatibility(meta, "gfx942:sramecc+:xnack-").has_value());
}
TEST(TestCheckArchCompatibility, PassesWhenArchNotSetEvenWithReferenceSource)
{
// reference_source is informational — guard only looks at gpu_architecture
BundleMetadata meta;
meta.referenceSource = "PyTorch 2.3.0";
EXPECT_FALSE(checkArchCompatibility(meta, "gfx942:sramecc+:xnack-").has_value());
}
TEST(TestCheckArchCompatibility, PassesWhenArchIsEmptyString)
{
// Empty gpu_architecture is treated as "not recorded" — data is portable
BundleMetadata meta;
meta.gpuArchitecture = "";
EXPECT_FALSE(checkArchCompatibility(meta, "gfx942:sramecc+:xnack-").has_value());
}
TEST(TestCheckArchCompatibility, PassesWhenDeviceCannotBeQueried)
{
BundleMetadata meta;
meta.gpuArchitecture = "gfx942";
// empty currentArch means "could not query device" — skip disabled
EXPECT_FALSE(checkArchCompatibility(meta, "").has_value());
}
TEST(TestCheckArchCompatibility, PassesWhenArchMatches)
{
BundleMetadata meta;
meta.gpuArchitecture = "gfx942";
// "gfx942" is a prefix of "gfx942:sramecc+:xnack-" (followed by ':')
EXPECT_FALSE(checkArchCompatibility(meta, "gfx942:sramecc+:xnack-").has_value());
}
TEST(TestCheckArchCompatibility, SkipsWhenArchMismatches)
{
BundleMetadata meta;
meta.gpuArchitecture = "gfx942";
auto result = checkArchCompatibility(meta, "gfx1100");
ASSERT_TRUE(result.has_value());
EXPECT_NE(result->find("gfx942"), std::string::npos);
EXPECT_NE(result->find("gfx1100"), std::string::npos);
}
TEST(TestCheckArchCompatibility, SkipsWhenArchDoesNotMatch)
{
BundleMetadata meta;
meta.gpuArchitecture = "gfx1100";
auto result = checkArchCompatibility(meta, "gfx942:sramecc+:xnack-");
ASSERT_TRUE(result.has_value());
}
TEST(TestCheckArchCompatibility, RejectsPartialArchName)
{
// "gfx94" is a prefix of "gfx942" but not a complete base arch.
// The guard requires an exact base-arch match (up to the ':' delimiter).
BundleMetadata meta;
meta.gpuArchitecture = "gfx94";
EXPECT_TRUE(checkArchCompatibility(meta, "gfx942:sramecc+:xnack-").has_value());
}
TEST(TestCheckArchCompatibility, ReferenceSourceDoesNotAffectGuard)
{
// reference_source is purely informational — the guard ignores it.
// Even with a GPU-sounding source, no gpu_architecture means portable.
BundleMetadata meta;
meta.referenceSource = "AITER 0.1.13";
EXPECT_FALSE(checkArchCompatibility(meta, "gfx942:sramecc+:xnack-").has_value());
}
TEST(TestCheckArchCompatibility, PassesWhenBareArchMatchesExactly)
{
// Device reports bare arch without feature flags
BundleMetadata meta;
meta.gpuArchitecture = "gfx942";
EXPECT_FALSE(checkArchCompatibility(meta, "gfx942").has_value());
}
TEST(TestCheckArchCompatibility, PassesWhenFullArchStringMatchesDevice)
{
// If metadata stores the full arch string (with feature flags),
// it matches the same full string from the device.
BundleMetadata meta;
meta.gpuArchitecture = "gfx942:sramecc+:xnack-";
EXPECT_FALSE(checkArchCompatibility(meta, "gfx942:sramecc+:xnack-").has_value());
}
TEST(TestCheckArchCompatibility, RejectsWhenFeatureFlagsDiffer)
{
// Full arch string in metadata with different feature flags on device
BundleMetadata meta;
meta.gpuArchitecture = "gfx942:sramecc+:xnack-";
EXPECT_TRUE(checkArchCompatibility(meta, "gfx942:sramecc-:xnack-").has_value());
}
// ---------------------------------------------------------------------------
// loadBundleMetadata — additional corner cases
// ---------------------------------------------------------------------------
TEST(TestLoadBundleMetadata, IgnoresIntegerWhereStringExpected)
{
// gpu_architecture is 42 (integer, not string) — readString skips it
const TempBundle bundle(R"({
"format_version": 1,
"gpu_architecture": 42,
"operation": 100
})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
EXPECT_FALSE(meta->gpuArchitecture.has_value());
EXPECT_FALSE(meta->operation.has_value());
}
TEST(TestLoadBundleMetadata, IgnoresNullFieldValues)
{
// JSON null is not a string or integer — fields become nullopt
const TempBundle bundle(R"({
"format_version": 1,
"seed": null,
"gpu_architecture": null
})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
ASSERT_TRUE(meta.has_value());
EXPECT_FALSE(meta->seed.has_value());
EXPECT_FALSE(meta->gpuArchitecture.has_value());
}
TEST(TestLoadBundleMetadata, ReturnsNulloptOnEmptyFile)
{
const TempBundle bundle(" ");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
EXPECT_FALSE(meta.has_value());
}
TEST(TestLoadBundleMetadata, ReturnsNulloptOnJsonArray)
{
// Valid JSON but not an object — format_version check fails
const TempBundle bundle("[1, 2, 3]");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
EXPECT_FALSE(meta.has_value());
}
TEST(TestLoadBundleMetadata, RejectsFormatVersionZero)
{
const TempBundle bundle(R"({"format_version": 0})");
auto meta = loadBundleMetadata(bundle.bundleJsonPath());
EXPECT_FALSE(meta.has_value());
}
// NOLINTEND(readability-identifier-naming)