/* * libfud * Copyright 2024 Dominick Allen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "fud_vector.hpp" #include "fud_array.hpp" #include "gtest/gtest.h" namespace fud { template struct TestLinearAllocator : public Allocator { virtual ~TestLinearAllocator() override = default; virtual Result allocate(size_t bytes, size_t alignment = alignof(std::max_align_t)) override { auto allocIndex = m_next; if (allocIndex % alignment != 0) { allocIndex += alignment - allocIndex % alignment; } if ((allocIndex + bytes) > Size) { return FudError{FudStatus::AllocFailure}; } m_next = allocIndex + bytes; return Okay{m_backing.data() + allocIndex}; } virtual void deallocate(std::byte* pointer, size_t bytes) override { static_cast(pointer); static_cast(bytes); } virtual bool isEqual(const Allocator& rhs) const override { return &rhs == static_cast(this); } Array m_backing{Array::constFill({})}; size_t m_next{0}; }; TEST(VectorTest, TrivialVector) { Vector intVector{}; ASSERT_EQ(intVector.size(), 0); ASSERT_EQ(intVector.capacity(), 0); ASSERT_TRUE(intVector.ref(0).isError()); ASSERT_EQ(intVector.resize(10), FudStatus::Success); ASSERT_EQ(intVector.size(), 10); ASSERT_EQ(intVector.capacity(), 10); ASSERT_TRUE(intVector.ref(0).isOkay()); ASSERT_EQ(intVector.ref(0).getOkay(), 0); intVector.get(0).takeOkay().get() = 10; ASSERT_EQ(intVector.ref(0).getOkay(), 10); } struct NonTrivial { static thread_local int counter; NonTrivial() { counter++; } explicit NonTrivial(int val) : value{val} { counter++; } NonTrivial(const NonTrivial&) = delete; NonTrivial(NonTrivial&& rhs) : value{rhs.value}, destroyed{rhs.destroyed} { rhs.destroyed = true; } ~NonTrivial() { if (!destroyed) { counter--; destroyed = true; } } NonTrivial& operator=(const NonTrivial& rhs) = delete; NonTrivial& operator=(NonTrivial&& rhs) { value = rhs.value; destroyed = rhs.destroyed; rhs.destroyed = true; return *this; } int value{0}; bool destroyed{false}; }; int thread_local NonTrivial::counter = 0; TEST(VectorTest, NonTrivialVector) { constexpr size_t testAllocSize = sizeof(NonTrivial) * 30; TestLinearAllocator testLinearAllocator{}; auto& counter = NonTrivial::counter; counter = 0; Vector nonTrivialVector{testLinearAllocator}; ASSERT_EQ(nonTrivialVector.size(), 0); ASSERT_EQ(nonTrivialVector.capacity(), 0); ASSERT_TRUE(nonTrivialVector.ref(0).isError()); ASSERT_EQ(counter, 0); ASSERT_EQ(nonTrivialVector.resize(10), FudStatus::Success); ASSERT_EQ(nonTrivialVector.size(), 10); ASSERT_EQ(nonTrivialVector.capacity(), 10); ASSERT_EQ(counter, 10); ASSERT_TRUE(nonTrivialVector.ref(0).isOkay()); ASSERT_EQ(nonTrivialVector.ref(0).getOkay().get().value, 0); nonTrivialVector.get(0).takeOkay().get().value = 10; ASSERT_EQ(nonTrivialVector.ref(0).getOkay().get().value, 10); ASSERT_EQ(nonTrivialVector.pushBack(NonTrivial{42}), FudStatus::Success); ASSERT_EQ(nonTrivialVector.size(), 11); ASSERT_GE(nonTrivialVector.capacity(), 11); ASSERT_EQ(counter, 11); auto capacity = nonTrivialVector.capacity(); constexpr auto FAIL_RESERVE_SIZE = 0x10000000000 / sizeof(NonTrivial); ASSERT_EQ(nonTrivialVector.reserve(FAIL_RESERVE_SIZE), FudStatus::AllocFailure); ASSERT_EQ(nonTrivialVector.capacity(), capacity); { auto popResult{nonTrivialVector.popBack()}; ASSERT_TRUE(popResult.isOkay()); auto value{popResult.takeOkay()}; ASSERT_EQ(value.value, 42); ASSERT_EQ(counter, 11); } ASSERT_EQ(counter, 10); ASSERT_EQ(nonTrivialVector.eraseBack(), FudStatus::Success); ASSERT_EQ(counter, 9); int val = 1; for (auto& element : nonTrivialVector) { element.value = val; val++; } ASSERT_EQ(nonTrivialVector.insert(3, NonTrivial{13}), FudStatus::Success); for (size_t idx = 0; idx < 3; ++idx) { ASSERT_EQ(nonTrivialVector[idx].value, idx + 1); } ASSERT_EQ(counter, 10); ASSERT_EQ(nonTrivialVector[3].value, 13); for (size_t idx = 4; idx < nonTrivialVector.size(); ++idx) { ASSERT_EQ(nonTrivialVector[idx].value, idx); } ASSERT_EQ(counter, nonTrivialVector.size()); ASSERT_EQ(nonTrivialVector.erase(3), FudStatus::Success); for (size_t idx = 0; idx < nonTrivialVector.size(); ++idx) { EXPECT_EQ(nonTrivialVector[idx].value, idx + 1); } ASSERT_EQ(counter, nonTrivialVector.size()); } TEST(VectorTest, NestedVector) { struct FallibleObject {}; auto intVectorVectorResult{Vector>::withSizeFallible(10, [](auto& vec) { return Vector::initializeWithSize(vec, 100, &globalNullAllocator); })}; EXPECT_TRUE(intVectorVectorResult.isError()); EXPECT_EQ(intVectorVectorResult.getErrorOr(FudStatus::Success), FudStatus::AllocFailure); // Result>, FudStatus> } } // namespace fud