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
488 lines
16 KiB
C++
488 lines
16 KiB
C++
/* ************************************************************************
|
|
* Copyright (C) 2025 Advanced Micro Devices, Inc.
|
|
*
|
|
* 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.
|
|
*
|
|
* ************************************************************************ */
|
|
|
|
// Unit tests for error handling mechanisms in StinkyTofu
|
|
// Tests Expected<T> and STINKY_UNREACHABLE() functionality
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <chrono>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "stinkytofu/support/ErrorHandling.hpp"
|
|
|
|
using namespace stinkytofu;
|
|
|
|
// ============================================================================
|
|
// Expected<T> - Basic Tests
|
|
// ============================================================================
|
|
|
|
class ExpectedTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {}
|
|
};
|
|
|
|
// Test successful Expected creation with primitive types
|
|
TEST_F(ExpectedTest, SuccessWithInt) {
|
|
Expected<int> result(42);
|
|
|
|
EXPECT_TRUE(result.hasValue());
|
|
EXPECT_FALSE(result.hasError());
|
|
EXPECT_TRUE(static_cast<bool>(result));
|
|
EXPECT_EQ(*result, 42);
|
|
}
|
|
|
|
// Test successful Expected creation with pointer types
|
|
TEST_F(ExpectedTest, SuccessWithPointer) {
|
|
int value = 123;
|
|
Expected<int*> result(&value);
|
|
|
|
EXPECT_TRUE(result.hasValue());
|
|
EXPECT_FALSE(result.hasError());
|
|
EXPECT_EQ(*result, &value);
|
|
EXPECT_EQ(**result, 123);
|
|
}
|
|
|
|
// Test successful Expected creation with std::string
|
|
TEST_F(ExpectedTest, SuccessWithString) {
|
|
Expected<std::string> result("hello world");
|
|
|
|
EXPECT_TRUE(result.hasValue());
|
|
EXPECT_FALSE(result.hasError());
|
|
EXPECT_EQ(*result, "hello world");
|
|
}
|
|
|
|
// Test successful Expected creation with std::vector
|
|
TEST_F(ExpectedTest, SuccessWithVector) {
|
|
std::vector<int> vec = {1, 2, 3, 4, 5};
|
|
Expected<std::vector<int>> result(vec);
|
|
|
|
EXPECT_TRUE(result.hasValue());
|
|
EXPECT_FALSE(result.hasError());
|
|
EXPECT_EQ(result->size(), 5);
|
|
EXPECT_EQ((*result)[2], 3);
|
|
}
|
|
|
|
// Test error Expected creation
|
|
TEST_F(ExpectedTest, ErrorCreation) {
|
|
Expected<int> result = Expected<int>::Error("Something went wrong");
|
|
|
|
EXPECT_FALSE(result.hasValue());
|
|
EXPECT_TRUE(result.hasError());
|
|
EXPECT_FALSE(static_cast<bool>(result));
|
|
EXPECT_EQ(result.getError(), "Something went wrong");
|
|
}
|
|
|
|
// Test error Expected with detailed message
|
|
TEST_F(ExpectedTest, ErrorWithDetailedMessage) {
|
|
std::string archName = "gfx900";
|
|
Expected<void*> result = Expected<void*>::Error("Instruction s_mul_lo_u32 not supported on " +
|
|
archName + " (requires gfx1250+)");
|
|
|
|
EXPECT_TRUE(result.hasError());
|
|
EXPECT_TRUE(result.getError().find("gfx900") != std::string::npos);
|
|
EXPECT_TRUE(result.getError().find("gfx1250") != std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Expected<T> - Move Semantics Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(ExpectedTest, MoveConstructorSuccess) {
|
|
Expected<std::string> original("test string");
|
|
Expected<std::string> moved(std::move(original));
|
|
|
|
EXPECT_TRUE(moved.hasValue());
|
|
EXPECT_EQ(*moved, "test string");
|
|
}
|
|
|
|
TEST_F(ExpectedTest, MoveConstructorError) {
|
|
Expected<int> original = Expected<int>::Error("error message");
|
|
Expected<int> moved(std::move(original));
|
|
|
|
EXPECT_TRUE(moved.hasError());
|
|
EXPECT_EQ(moved.getError(), "error message");
|
|
}
|
|
|
|
TEST_F(ExpectedTest, MoveAssignmentSuccess) {
|
|
Expected<int> original(999);
|
|
Expected<int> assigned(0);
|
|
assigned = std::move(original);
|
|
|
|
EXPECT_TRUE(assigned.hasValue());
|
|
EXPECT_EQ(*assigned, 999);
|
|
}
|
|
|
|
TEST_F(ExpectedTest, MoveAssignmentError) {
|
|
Expected<int> original = Expected<int>::Error("test error");
|
|
Expected<int> assigned(0);
|
|
assigned = std::move(original);
|
|
|
|
EXPECT_TRUE(assigned.hasError());
|
|
EXPECT_EQ(assigned.getError(), "test error");
|
|
}
|
|
|
|
// ============================================================================
|
|
// Expected<T> - Access Operators Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(ExpectedTest, DereferenceOperator) {
|
|
Expected<int> result(42);
|
|
EXPECT_EQ(*result, 42);
|
|
|
|
// Test mutability through dereference
|
|
*result = 100;
|
|
EXPECT_EQ(*result, 100);
|
|
}
|
|
|
|
TEST_F(ExpectedTest, ArrowOperator) {
|
|
struct TestStruct {
|
|
int value;
|
|
std::string name;
|
|
};
|
|
|
|
TestStruct obj{42, "test"};
|
|
Expected<TestStruct> result(obj);
|
|
|
|
EXPECT_EQ(result->value, 42);
|
|
EXPECT_EQ(result->name, "test");
|
|
|
|
// Test mutability through arrow operator
|
|
result->value = 100;
|
|
EXPECT_EQ(result->value, 100);
|
|
}
|
|
|
|
TEST_F(ExpectedTest, RValueDereference) {
|
|
auto makeExpected = []() -> Expected<std::string> { return std::string("temporary"); };
|
|
|
|
std::string value = *makeExpected();
|
|
EXPECT_EQ(value, "temporary");
|
|
}
|
|
|
|
// ============================================================================
|
|
// Expected<T> - Pattern Matching / Error Handling Tests
|
|
// ============================================================================
|
|
|
|
// Helper function that returns Expected<int>
|
|
static Expected<int> divideOrError(int numerator, int denominator) {
|
|
if (denominator == 0) {
|
|
return Expected<int>::Error("Division by zero");
|
|
}
|
|
return numerator / denominator;
|
|
}
|
|
|
|
TEST_F(ExpectedTest, PatternMatchingSuccess) {
|
|
auto result = divideOrError(10, 2);
|
|
|
|
if (result) {
|
|
EXPECT_EQ(*result, 5);
|
|
} else {
|
|
FAIL() << "Expected success but got error: " << result.getError();
|
|
}
|
|
}
|
|
|
|
TEST_F(ExpectedTest, PatternMatchingError) {
|
|
auto result = divideOrError(10, 0);
|
|
|
|
if (result) {
|
|
FAIL() << "Expected error but got success: " << *result;
|
|
} else {
|
|
EXPECT_EQ(result.getError(), "Division by zero");
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Expected<T> - Complex Type Tests
|
|
// ============================================================================
|
|
|
|
struct ComplexType {
|
|
std::vector<int> data;
|
|
std::unique_ptr<int> ptr;
|
|
std::string name;
|
|
|
|
ComplexType(std::vector<int> d, int val, std::string n)
|
|
: data(std::move(d)), ptr(std::make_unique<int>(val)), name(std::move(n)) {}
|
|
|
|
// Move-only type
|
|
ComplexType(ComplexType&&) noexcept = default;
|
|
ComplexType& operator=(ComplexType&&) noexcept = default;
|
|
ComplexType(const ComplexType&) = delete;
|
|
ComplexType& operator=(const ComplexType&) = delete;
|
|
};
|
|
|
|
TEST_F(ExpectedTest, MoveOnlyType) {
|
|
ComplexType obj({1, 2, 3}, 42, "test");
|
|
Expected<ComplexType> result(std::move(obj));
|
|
|
|
EXPECT_TRUE(result.hasValue());
|
|
EXPECT_EQ(result->data.size(), 3);
|
|
EXPECT_EQ(*result->ptr, 42);
|
|
EXPECT_EQ(result->name, "test");
|
|
}
|
|
|
|
TEST_F(ExpectedTest, UniquePointer) {
|
|
auto ptr = std::make_unique<int>(999);
|
|
Expected<std::unique_ptr<int>> result(std::move(ptr));
|
|
|
|
EXPECT_TRUE(result.hasValue());
|
|
EXPECT_EQ(**result, 999);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Expected<T> - Real-World Use Cases
|
|
// ============================================================================
|
|
|
|
// Simulate architecture check function
|
|
static Expected<void*> createInstructionForArch(const std::string& arch,
|
|
const std::string& instruction) {
|
|
if (arch == "gfx900" && instruction == "s_mul_lo_u32") {
|
|
return Expected<void*>::Error("s_mul_lo_u32 not supported on gfx900 (requires gfx1250+)");
|
|
}
|
|
if (arch == "gfx1250") {
|
|
return static_cast<void*>(nullptr); // Placeholder
|
|
}
|
|
return Expected<void*>::Error("Unknown architecture: " + arch);
|
|
}
|
|
|
|
TEST_F(ExpectedTest, ArchitectureLimitation) {
|
|
auto result1 = createInstructionForArch("gfx900", "s_mul_lo_u32");
|
|
EXPECT_TRUE(result1.hasError());
|
|
EXPECT_TRUE(result1.getError().find("gfx1250") != std::string::npos);
|
|
|
|
auto result2 = createInstructionForArch("gfx1250", "s_mul_lo_u32");
|
|
EXPECT_TRUE(result2.hasValue());
|
|
}
|
|
|
|
TEST_F(ExpectedTest, ChainedErrorPropagation) {
|
|
// Simulate function that depends on another Expected-returning function
|
|
auto processInstruction = [](const std::string& arch) -> Expected<int> {
|
|
auto inst = createInstructionForArch(arch, "s_mul_lo_u32");
|
|
if (!inst) {
|
|
return Expected<int>::Error("Failed to create instruction: " + inst.getError());
|
|
}
|
|
return 42; // Success
|
|
};
|
|
|
|
auto result1 = processInstruction("gfx900");
|
|
EXPECT_TRUE(result1.hasError());
|
|
EXPECT_TRUE(result1.getError().find("Failed to create instruction") != std::string::npos);
|
|
|
|
auto result2 = processInstruction("gfx1250");
|
|
EXPECT_TRUE(result2.hasValue());
|
|
EXPECT_EQ(*result2, 42);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Expected<T> - Edge Cases
|
|
// ============================================================================
|
|
|
|
TEST_F(ExpectedTest, EmptyString) {
|
|
Expected<std::string> result("");
|
|
EXPECT_TRUE(result.hasValue());
|
|
EXPECT_EQ(result->size(), 0);
|
|
}
|
|
|
|
TEST_F(ExpectedTest, EmptyErrorMessage) {
|
|
Expected<int> result = Expected<int>::Error("");
|
|
EXPECT_TRUE(result.hasError());
|
|
EXPECT_EQ(result.getError(), "");
|
|
}
|
|
|
|
TEST_F(ExpectedTest, LargeErrorMessage) {
|
|
std::string longMsg(10000, 'x');
|
|
Expected<int> result = Expected<int>::Error(longMsg);
|
|
EXPECT_TRUE(result.hasError());
|
|
EXPECT_EQ(result.getError().size(), 10000);
|
|
}
|
|
|
|
TEST_F(ExpectedTest, NullPointer) {
|
|
Expected<int*> result(nullptr);
|
|
EXPECT_TRUE(result.hasValue());
|
|
EXPECT_EQ(*result, nullptr);
|
|
}
|
|
|
|
// ============================================================================
|
|
// STINKY_UNREACHABLE() Tests
|
|
// ============================================================================
|
|
// Note: STINKY_UNREACHABLE() aborts the process, so we can't directly test it
|
|
// in unit tests. Instead, we test that the code compiles and that we can
|
|
// use it in dead code paths.
|
|
|
|
class UnreachableTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {}
|
|
};
|
|
|
|
// Test that STINKY_UNREACHABLE compiles and can be used in conditional branches
|
|
TEST_F(UnreachableTest, CompilationTest) {
|
|
auto testFunction = [](int value) -> int {
|
|
if (value == 0) {
|
|
return 0;
|
|
} else if (value == 1) {
|
|
return 1;
|
|
} else {
|
|
// This branch should never execute in this test
|
|
if (value == 2) {
|
|
return 2;
|
|
}
|
|
// If we somehow get here with value != 2, it's a bug
|
|
// STINKY_UNREACHABLE("Unexpected value");
|
|
// Note: Can't actually execute this in test, just verify compilation
|
|
return -1; // Placeholder for test
|
|
}
|
|
};
|
|
|
|
EXPECT_EQ(testFunction(0), 0);
|
|
EXPECT_EQ(testFunction(1), 1);
|
|
EXPECT_EQ(testFunction(2), 2);
|
|
}
|
|
|
|
// Test pattern: validation followed by unreachable
|
|
TEST_F(UnreachableTest, ValidationPattern) {
|
|
auto createWithValidation = [](bool isValid) -> Expected<int> {
|
|
if (!isValid) {
|
|
return Expected<int>::Error("Validation failed");
|
|
}
|
|
|
|
// After validation, some operation that should never fail
|
|
int* ptr = new int(42);
|
|
if (!ptr) {
|
|
// This should never happen on modern systems
|
|
// STINKY_UNREACHABLE("Failed to allocate memory after validation");
|
|
// Can't execute in test, but pattern is correct
|
|
}
|
|
|
|
int value = *ptr;
|
|
delete ptr;
|
|
return value;
|
|
};
|
|
|
|
auto result1 = createWithValidation(true);
|
|
EXPECT_TRUE(result1.hasValue());
|
|
|
|
auto result2 = createWithValidation(false);
|
|
EXPECT_TRUE(result2.hasError());
|
|
}
|
|
|
|
// ============================================================================
|
|
// Integration Tests - Expected + assert pattern
|
|
// ============================================================================
|
|
|
|
TEST_F(ExpectedTest, AssertPlusExpectedPattern) {
|
|
// Simulate function with precondition checks and recoverable errors
|
|
auto processData = [](int* data, size_t size, bool supportedArch) -> Expected<int> {
|
|
// Debug-time precondition check
|
|
assert(data != nullptr && "Data pointer must not be null");
|
|
assert(size > 0 && "Size must be positive");
|
|
|
|
// Recoverable error (architecture limitation)
|
|
if (!supportedArch) {
|
|
return Expected<int>::Error("Operation not supported on this architecture");
|
|
}
|
|
|
|
// Success
|
|
int sum = 0;
|
|
for (size_t i = 0; i < size; i++) {
|
|
sum += data[i];
|
|
}
|
|
return sum;
|
|
};
|
|
|
|
int testData[] = {1, 2, 3, 4, 5};
|
|
auto result1 = processData(testData, 5, true);
|
|
EXPECT_TRUE(result1.hasValue());
|
|
EXPECT_EQ(*result1, 15);
|
|
|
|
auto result2 = processData(testData, 5, false);
|
|
EXPECT_TRUE(result2.hasError());
|
|
EXPECT_TRUE(result2.getError().find("not supported") != std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Performance Tests (Sanity Check)
|
|
// ============================================================================
|
|
|
|
TEST_F(ExpectedTest, PerformanceSanity) {
|
|
// Test that Expected doesn't have crazy overhead for simple types
|
|
const int iterations = 10000;
|
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (int i = 0; i < iterations; i++) {
|
|
Expected<int> result(i);
|
|
if (result) {
|
|
volatile int x = *result; // Prevent optimization
|
|
(void)x;
|
|
}
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
|
|
|
|
// This should complete in well under 1ms for 10k iterations
|
|
EXPECT_LT(duration, 10000) << "Expected<T> has unreasonable overhead";
|
|
}
|
|
|
|
// ============================================================================
|
|
// Documentation Examples Tests
|
|
// ============================================================================
|
|
|
|
// Verify examples from ErrorHandling.hpp actually work
|
|
TEST_F(ExpectedTest, DocumentationExample1) {
|
|
auto createInstruction = [](bool arch_supports) -> Expected<int*> {
|
|
if (!arch_supports) {
|
|
return Expected<int*>::Error("Not supported");
|
|
}
|
|
static int inst = 42;
|
|
return &inst;
|
|
};
|
|
|
|
// Usage from documentation
|
|
auto result = createInstruction(false);
|
|
if (!result) {
|
|
EXPECT_EQ(result.getError(), "Not supported");
|
|
} else {
|
|
FAIL() << "Should have failed";
|
|
}
|
|
}
|
|
|
|
TEST_F(ExpectedTest, DocumentationExample2) {
|
|
struct StinkyRegister {
|
|
bool isValid() const {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
auto createInst = [](StinkyRegister dst) -> Expected<int*> {
|
|
assert(dst.isValid() && "Invalid destination register");
|
|
static int inst = 42;
|
|
return &inst;
|
|
};
|
|
|
|
StinkyRegister reg;
|
|
auto result = createInst(reg);
|
|
EXPECT_TRUE(result.hasValue());
|
|
}
|