#include "shared_data.h" #include #include #include #include namespace NActors { Y_UNIT_TEST_SUITE(TSharedDataTest) { Y_UNIT_TEST(BasicBehavior) { auto data = TSharedData::Copy("Hello", 5); UNIT_ASSERT(data.IsPrivate()); UNIT_ASSERT(!data.IsShared()); UNIT_ASSERT_VALUES_EQUAL(data.size(), 5u); UNIT_ASSERT_VALUES_EQUAL(data.end() - data.begin(), 5u); UNIT_ASSERT_VALUES_EQUAL(data.mutable_end() - data.mutable_begin(), 5u); UNIT_ASSERT(data.begin() == data.data()); UNIT_ASSERT(data.mutable_data() == data.data()); UNIT_ASSERT(data.mutable_begin() == data.mutable_data()); UNIT_ASSERT_VALUES_EQUAL(data.ToString(), TString("Hello")); UNIT_ASSERT_VALUES_EQUAL(::memcmp(data.data(), "Hello", 5), 0); auto link = data; UNIT_ASSERT(!link.IsPrivate()); UNIT_ASSERT(!data.IsPrivate()); UNIT_ASSERT(link.IsShared()); UNIT_ASSERT(data.IsShared()); UNIT_ASSERT(link.data() == data.data()); UNIT_ASSERT(link.size() == data.size()); link = { }; UNIT_ASSERT(link.IsPrivate()); UNIT_ASSERT(data.IsPrivate()); UNIT_ASSERT(!link.IsShared()); UNIT_ASSERT(!data.IsShared()); UNIT_ASSERT_VALUES_EQUAL(TString(TStringBuf(data)), TString("Hello")); UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice()), TString("Hello")); UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice(1)), TString("ello")); UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice(1, 3)), TString("ell")); UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice(1, 100)), TString("ello")); UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice(0, 4)), TString("Hell")); link = data; UNIT_ASSERT(link.data() == data.data()); UNIT_ASSERT_VALUES_UNEQUAL(link.Detach(), data.data()); UNIT_ASSERT_EQUAL(data.size(), link.size()); UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice()), TString(link.Slice())); } Y_UNIT_TEST(TrimBehavior) { auto data = TSharedData::Uninitialized(42); UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); UNIT_ASSERT(data.data() != nullptr); // Trim to non-zero does not change addresses const char* ptr1 = data.data(); data.TrimBack(31); const char* ptr2 = data.data(); UNIT_ASSERT_VALUES_EQUAL(data.size(), 31u); UNIT_ASSERT(ptr1 == ptr2); // Trim to zero releases underlying data data.TrimBack(0); UNIT_ASSERT_VALUES_EQUAL(data.size(), 0u); UNIT_ASSERT(data.data() == nullptr); } class TCustomOwner : public TSharedData::IOwner { using THeader = TSharedData::THeader; public: TSharedData Allocate(size_t size) { char* raw = reinterpret_cast(y_allocate(sizeof(THeader) + size)); THeader* header = reinterpret_cast(raw); header->RefCount = 1; header->Owner = this; char* data = raw + sizeof(THeader); Y_VERIFY(Allocated_.insert(data).second); return TSharedData::AttachUnsafe(data, size); } void Deallocate(char* data) noexcept { Y_VERIFY(Allocated_.erase(data) > 0); char* raw = data - sizeof(THeader); y_deallocate(raw); Deallocated_.push_back(data); } char* NextDeallocated() { char* result = nullptr; if (Deallocated_) { result = Deallocated_.front(); Deallocated_.pop_front(); } return result; } private: THashSet Allocated_; TDeque Deallocated_; }; Y_UNIT_TEST(CustomOwner) { TCustomOwner owner; const char* ptr; // Test destructor releases data { auto data = owner.Allocate(42); UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); ptr = data.data(); UNIT_ASSERT(owner.NextDeallocated() == nullptr); } UNIT_ASSERT(owner.NextDeallocated() == ptr); UNIT_ASSERT(owner.NextDeallocated() == nullptr); // Test assignment releases data { auto data = owner.Allocate(42); UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); ptr = data.data(); UNIT_ASSERT(owner.NextDeallocated() == nullptr); data = { }; } UNIT_ASSERT(owner.NextDeallocated() == ptr); UNIT_ASSERT(owner.NextDeallocated() == nullptr); // Test copies keep references correctly { auto data = owner.Allocate(42); UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); ptr = data.data(); auto copy = data; UNIT_ASSERT_VALUES_EQUAL(copy.size(), 42u); UNIT_ASSERT(copy.data() == ptr); data = { }; UNIT_ASSERT_VALUES_EQUAL(data.size(), 0u); UNIT_ASSERT(data.data() == nullptr); UNIT_ASSERT(owner.NextDeallocated() == nullptr); } UNIT_ASSERT(owner.NextDeallocated() == ptr); UNIT_ASSERT(owner.NextDeallocated() == nullptr); // Test assignment releases correct data { auto data1 = owner.Allocate(42); UNIT_ASSERT_VALUES_EQUAL(data1.size(), 42u); auto data2 = owner.Allocate(31); UNIT_ASSERT_VALUES_EQUAL(data2.size(), 31u); ptr = data1.data(); UNIT_ASSERT(owner.NextDeallocated() == nullptr); data1 = data2; UNIT_ASSERT(owner.NextDeallocated() == ptr); UNIT_ASSERT(owner.NextDeallocated() == nullptr); ptr = data2.data(); UNIT_ASSERT_VALUES_EQUAL(data1.size(), 31u); UNIT_ASSERT(data1.data() == ptr); } UNIT_ASSERT(owner.NextDeallocated() == ptr); UNIT_ASSERT(owner.NextDeallocated() == nullptr); // Test moves don't produce dangling references { auto data = owner.Allocate(42); UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); ptr = data.data(); auto moved = std::move(data); UNIT_ASSERT_VALUES_EQUAL(moved.size(), 42u); UNIT_ASSERT(moved.data() == ptr); UNIT_ASSERT_VALUES_EQUAL(data.size(), 0u); UNIT_ASSERT(data.data() == nullptr); UNIT_ASSERT(owner.NextDeallocated() == nullptr); } UNIT_ASSERT(owner.NextDeallocated() == ptr); UNIT_ASSERT(owner.NextDeallocated() == nullptr); // Test Detach copies correctly and doesn't affect owned data { auto data = owner.Allocate(42); auto disowned = data; disowned.Detach(); UNIT_ASSERT(owner.NextDeallocated() == nullptr); } UNIT_ASSERT(owner.NextDeallocated() == ptr); UNIT_ASSERT(owner.NextDeallocated() == nullptr); } } }