diff --git a/test/unique_resource.cpp b/test/unique_resource.cpp index 970bee5..c9e1cc5 100644 --- a/test/unique_resource.cpp +++ b/test/unique_resource.cpp @@ -350,6 +350,26 @@ TEST(UniqueResource, Assign_EE) } TEST(UniqueResource, Assign_EE2) + +TEST(UniqueResource, SelfAssignment) +{ + int deleter_run = 0; + + MoveAssignableResource res; + MoveAssignableDeleter&> del(deleter_run); + + { + using ur_t = wwa::utils::unique_resource< + MoveAssignableResource, MoveAssignableDeleter&>>; + + ur_t f1(res, del); + f1 = std::move(f1); // Self-assignment + + EXPECT_EQ(deleter_run, 0); // Should not call deleter on self-assignment + } + + EXPECT_EQ(deleter_run, 1); // Deleter called once on destruction +} { int deleter_run = 0; @@ -374,6 +394,25 @@ TEST(UniqueResource, Assign_EE2) } TEST(UniqueResource, Release) + +TEST(UniqueResource, MultipleReleases) +{ + int deleter_run = 0; + + Resource res; + Deleter&> del(deleter_run); + + { + wwa::utils::unique_resource&, Deleter&>> f(res, del); + f.release(); + f.release(); // Second release should be safe + f.release(); // Third release should be safe + + EXPECT_EQ(deleter_run, 0); + } + + EXPECT_EQ(deleter_run, 0); // No deleter calls after releases +} { int deleter_run = 0; @@ -391,6 +430,28 @@ TEST(UniqueResource, Release) } TEST(UniqueResource, Reset) + +TEST(UniqueResource, MultipleResets) +{ + int deleter_run = 0; + + Resource res; + Deleter&> del(deleter_run); + + { + wwa::utils::unique_resource&, Deleter&>> f(res, del); + f.reset(); + EXPECT_EQ(deleter_run, 1); + + f.reset(); // Second reset should be safe + EXPECT_EQ(deleter_run, 1); // No additional deleter calls + + f.reset(); // Third reset should be safe + EXPECT_EQ(deleter_run, 1); // Still no additional deleter calls + } + + EXPECT_EQ(deleter_run, 1); // Only one deleter call total +} { int deleter_run = 0; @@ -427,6 +488,26 @@ TEST(UniqueResource, Reset2A) } TEST(UniqueResource, Reset2B) + +TEST(UniqueResource, ResetAfterRelease) +{ + int deleter_run = 0; + + Resource res1; + Resource res2; + Deleter&> del(deleter_run); + + { + wwa::utils::unique_resource&, Deleter&>> f(res1, del); + f.release(); + EXPECT_EQ(deleter_run, 0); + + f.reset(res2); // Reset with new resource after release + EXPECT_EQ(deleter_run, 0); // No deleter call for released resource + } + + EXPECT_EQ(deleter_run, 1); // Deleter called for res2 on destruction +} { int deleter_run = 0; @@ -450,6 +531,370 @@ TEST(UniqueResource, Reset2B) } TEST(UniqueResource, Accessors) + +TEST(UniqueResource, ConstCorrectness) + +TEST(UniqueResource, ConstructorExceptionSafety) + +TEST(UniqueResource, BasicConstructorPatterns) + +TEST(UniqueResource, DifferentResourceTypes) + +TEST(UniqueResource, DeleterAccess) + +TEST(UniqueResource, ResourceComparison) + +TEST(UniqueResource, MoveOperationsStress) + +TEST(UniqueResource, MakeCheckedEdgeCases) + +TEST(UniqueResource, RAIIExceptionBehavior) + +TEST(UniqueResource, TemplateTypeDeduction) + +TEST(UniqueResource, ReleaseAndGetInteraction) + +TEST(UniqueResource, NoexceptSpecifications) +{ + // Test that key operations are noexcept when they should be + using SimpleResource = wwa::utils::unique_resource; + + static_assert(noexcept(std::declval().get())); + static_assert(noexcept(std::declval().get_deleter())); + static_assert(noexcept(std::declval().release())); + static_assert(noexcept(std::declval().reset())); + + // Verify that pointer access operators are noexcept for pointer types + static_assert(noexcept(*std::declval())); + static_assert(noexcept(std::declval().operator->())); + + // This test mainly checks compile-time properties + SUCCEED(); +} +{ + int deleter_run = 0; + auto del = [&deleter_run](int* p) { delete p; ++deleter_run; }; + + int* original_ptr = new int(42); + + { + wwa::utils::unique_resource obj(original_ptr, del); + EXPECT_EQ(obj.get(), original_ptr); + + obj.release(); + // After release, get() should still return the original pointer + EXPECT_EQ(obj.get(), original_ptr); + + // But deleter should not be called on destruction + } + + EXPECT_EQ(deleter_run, 0); + + // Manually clean up since release was called + delete original_ptr; +} +{ + int deleter_run = 0; + + { + // Test template argument deduction with lambda + auto del = [&deleter_run](int* p) { delete p; ++deleter_run; }; + wwa::utils::unique_resource obj(new int(42), del); + + ASSERT_NE(obj.get(), nullptr); + EXPECT_EQ(*obj.get(), 42); + } + + EXPECT_EQ(deleter_run, 1); + + deleter_run = 0; + + { + // Test deduction with function pointer + void (*del_func)(int*) = [](int* p) { delete p; }; + wwa::utils::unique_resource obj(new int(123), del_func); + + ASSERT_NE(obj.get(), nullptr); + EXPECT_EQ(*obj.get(), 123); + } +} +{ + int deleter_run = 0; + + struct ThrowingDeleter { + int& counter; + bool should_throw; + + ThrowingDeleter(int& c, bool throw_flag = false) : counter(c), should_throw(throw_flag) {} + + void operator()(int* p) { + delete p; + ++counter; + if (should_throw) { + throw std::runtime_error("Deleter exception"); + } + } + }; + + { + // Test normal RAII behavior + ThrowingDeleter del(deleter_run, false); + { + wwa::utils::unique_resource obj(new int(42), del); + ASSERT_NE(obj.get(), nullptr); + EXPECT_EQ(*obj.get(), 42); + } // Destructor should call deleter + + EXPECT_EQ(deleter_run, 1); + } + + deleter_run = 0; + + { + // Test RAII with early release + ThrowingDeleter del(deleter_run, false); + { + wwa::utils::unique_resource obj(new int(42), del); + obj.release(); + } // Destructor should NOT call deleter + + EXPECT_EQ(deleter_run, 0); + } +} +{ + int deleter_run = 0; + auto del = [&deleter_run](int* p) { if (p) delete p; ++deleter_run; }; + + { + // Test with zero as invalid value + auto obj = wwa::utils::make_unique_resource_checked(new int(42), static_cast(nullptr), del); + ASSERT_NE(obj.get(), nullptr); + EXPECT_EQ(*obj.get(), 42); + } + + EXPECT_EQ(deleter_run, 1); + + deleter_run = 0; + + { + // Test with -1 as invalid value + int* valid_ptr = new int(123); + auto obj = wwa::utils::make_unique_resource_checked(valid_ptr, reinterpret_cast(-1), del); + ASSERT_NE(obj.get(), nullptr); + EXPECT_EQ(*obj.get(), 123); + } + + EXPECT_EQ(deleter_run, 1); + + deleter_run = 0; + + { + // Test with custom invalid value type + struct Handle { int value; bool operator==(const Handle& other) const { return value == other.value; } }; + auto del_handle = [&deleter_run](Handle) { ++deleter_run; }; + Handle valid{42}; + Handle invalid{-1}; + auto obj = wwa::utils::make_unique_resource_checked(valid, invalid, del_handle); + EXPECT_EQ(obj.get().value, 42); + } + + EXPECT_EQ(deleter_run, 1); +} +{ + int deleter_run = 0; + auto del = [&deleter_run](int* p) { delete p; ++deleter_run; }; + + { + std::vector> resources; + + // Create multiple resources + for (int i = 0; i < 10; ++i) { + resources.emplace_back(new int(i), del); + } + + // Verify all resources are valid + for (size_t i = 0; i < resources.size(); ++i) { + ASSERT_NE(resources[i].get(), nullptr); + EXPECT_EQ(*resources[i].get(), static_cast(i)); + } + + // Move resources around + auto moved = std::move(resources[5]); + EXPECT_EQ(resources[5].get(), nullptr); // Moved-from state + ASSERT_NE(moved.get(), nullptr); + EXPECT_EQ(*moved.get(), 5); + + // Move assign + resources[0] = std::move(moved); + EXPECT_EQ(moved.get(), nullptr); // Moved-from state + ASSERT_NE(resources[0].get(), nullptr); + EXPECT_EQ(*resources[0].get(), 5); + } + + EXPECT_EQ(deleter_run, 10); // All resources should be cleaned up +} +{ + int deleter_run = 0; + auto del = [&deleter_run](int* p) { delete p; ++deleter_run; }; + + int* ptr1 = new int(42); + int* ptr2 = new int(43); + + { + wwa::utils::unique_resource obj1(ptr1, del); + wwa::utils::unique_resource obj2(ptr2, del); + + // Test pointer comparison + EXPECT_EQ(obj1.get(), ptr1); + EXPECT_EQ(obj2.get(), ptr2); + EXPECT_NE(obj1.get(), obj2.get()); + + // Test value comparison + EXPECT_EQ(*obj1.get(), 42); + EXPECT_EQ(*obj2.get(), 43); + EXPECT_NE(*obj1.get(), *obj2.get()); + } + + EXPECT_EQ(deleter_run, 2); +} +{ + struct StatefulDeleter { + int& counter; + int state = 42; + + StatefulDeleter(int& c) : counter(c) {} + + void operator()(int* p) { + delete p; + counter += state; + } + + void setState(int s) { state = s; } + int getState() const { return state; } + }; + + int deleter_run = 0; + + { + StatefulDeleter del(deleter_run); + wwa::utils::unique_resource obj(new int(123), del); + + // Test deleter access + EXPECT_EQ(obj.get_deleter().getState(), 42); + + // Test deleter modification via non-const access + const_cast(obj.get_deleter()).setState(100); + EXPECT_EQ(obj.get_deleter().getState(), 100); + } + + EXPECT_EQ(deleter_run, 100); // Should use modified state +} +{ + int deleter_run = 0; + + { + // Test with array pointer + auto del = [&deleter_run](int* p) { delete[] p; ++deleter_run; }; + wwa::utils::unique_resource obj(new int[5]{1, 2, 3, 4, 5}, del); + ASSERT_NE(obj.get(), nullptr); + EXPECT_EQ(obj.get()[0], 1); + EXPECT_EQ(obj.get()[4], 5); + } + + EXPECT_EQ(deleter_run, 1); + + deleter_run = 0; + + { + // Test with function pointer resource + auto del = [&deleter_run](void(*)()) { ++deleter_run; }; + auto dummy_func = []() {}; + wwa::utils::unique_resource obj(dummy_func, del); + EXPECT_EQ(obj.get(), dummy_func); + } + + EXPECT_EQ(deleter_run, 1); +} +{ + int deleter_run = 0; + + { + // Test direct constructor with raw pointer + auto del = [&deleter_run](int* p) { delete p; ++deleter_run; }; + wwa::utils::unique_resource obj(new int(123), del); + ASSERT_NE(obj.get(), nullptr); + EXPECT_EQ(*obj.get(), 123); + } + + EXPECT_EQ(deleter_run, 1); + + deleter_run = 0; + + { + // Test constructor with lambda capture + int capture_value = 456; + auto del = [&deleter_run, capture_value](int* p) { + EXPECT_EQ(capture_value, 456); + delete p; + ++deleter_run; + }; + wwa::utils::unique_resource obj(new int(capture_value), del); + ASSERT_NE(obj.get(), nullptr); + EXPECT_EQ(*obj.get(), 456); + } + + EXPECT_EQ(deleter_run, 1); +} +{ + int deleter_run = 0; + + struct ThrowingResource { + ThrowingResource() { throw std::runtime_error("Construction failed"); } + }; + + auto del = [&deleter_run](ThrowingResource* p) { delete p; ++deleter_run; }; + + { + // Test that deleter is not called if resource construction throws + EXPECT_THROW({ + ThrowingResource* ptr = nullptr; + try { + ptr = new ThrowingResource(); + } catch (...) { + // Resource construction failed, unique_resource should not be created + throw; + } + wwa::utils::unique_resource obj(ptr, del); + }, std::runtime_error); + } + + EXPECT_EQ(deleter_run, 0); // Deleter should not be called +} +{ + struct TestResource { + int value = 42; + int getValue() const { return value; } + void setValue(int v) { value = v; } + }; + + int deleter_run = 0; + auto del = [&deleter_run](TestResource* p) { delete p; ++deleter_run; }; + + { + const auto obj = wwa::utils::make_unique_resource_checked(new TestResource(), nullptr, del); + ASSERT_NE(obj.get(), nullptr); + + // Test const access + EXPECT_EQ(obj->getValue(), 42); + EXPECT_EQ((*obj).getValue(), 42); + + // Verify const get() works + const TestResource* const_ptr = obj.get(); + EXPECT_EQ(const_ptr->getValue(), 42); + } + + EXPECT_EQ(deleter_run, 1); +} { constexpr int expected_value = 42;