350 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
#include "test_helpers.h"
 | 
						|
#include "xray_segmented_array.h"
 | 
						|
#include "gmock/gmock.h"
 | 
						|
#include "gtest/gtest.h"
 | 
						|
#include <algorithm>
 | 
						|
#include <numeric>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
namespace __xray {
 | 
						|
namespace {
 | 
						|
 | 
						|
using ::testing::SizeIs;
 | 
						|
 | 
						|
struct TestData {
 | 
						|
  s64 First;
 | 
						|
  s64 Second;
 | 
						|
 | 
						|
  // Need a constructor for emplace operations.
 | 
						|
  TestData(s64 F, s64 S) : First(F), Second(S) {}
 | 
						|
};
 | 
						|
 | 
						|
void PrintTo(const TestData &D, std::ostream *OS) {
 | 
						|
  *OS << "{ " << D.First << ", " << D.Second << " }";
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, ConstructWithAllocators) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 4);
 | 
						|
  Array<TestData> Data(A);
 | 
						|
  (void)Data;
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, ConstructAndPopulate) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 4);
 | 
						|
  Array<TestData> data(A);
 | 
						|
  ASSERT_NE(data.Append(TestData{0, 0}), nullptr);
 | 
						|
  ASSERT_NE(data.Append(TestData{1, 1}), nullptr);
 | 
						|
  ASSERT_EQ(data.size(), 2u);
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, ConstructPopulateAndLookup) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 4);
 | 
						|
  Array<TestData> data(A);
 | 
						|
  ASSERT_NE(data.Append(TestData{0, 1}), nullptr);
 | 
						|
  ASSERT_EQ(data.size(), 1u);
 | 
						|
  ASSERT_EQ(data[0].First, 0);
 | 
						|
  ASSERT_EQ(data[0].Second, 1);
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, PopulateWithMoreElements) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 24);
 | 
						|
  Array<TestData> data(A);
 | 
						|
  static const auto kMaxElements = 100u;
 | 
						|
  for (auto I = 0u; I < kMaxElements; ++I) {
 | 
						|
    ASSERT_NE(data.Append(TestData{I, I + 1}), nullptr);
 | 
						|
  }
 | 
						|
  ASSERT_EQ(data.size(), kMaxElements);
 | 
						|
  for (auto I = 0u; I < kMaxElements; ++I) {
 | 
						|
    ASSERT_EQ(data[I].First, I);
 | 
						|
    ASSERT_EQ(data[I].Second, I + 1);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, AppendEmplace) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 4);
 | 
						|
  Array<TestData> data(A);
 | 
						|
  ASSERT_NE(data.AppendEmplace(1, 1), nullptr);
 | 
						|
  ASSERT_EQ(data[0].First, 1);
 | 
						|
  ASSERT_EQ(data[0].Second, 1);
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, AppendAndTrim) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 4);
 | 
						|
  Array<TestData> data(A);
 | 
						|
  ASSERT_NE(data.AppendEmplace(1, 1), nullptr);
 | 
						|
  ASSERT_EQ(data.size(), 1u);
 | 
						|
  data.trim(1);
 | 
						|
  ASSERT_EQ(data.size(), 0u);
 | 
						|
  ASSERT_TRUE(data.empty());
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, IteratorAdvance) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 4);
 | 
						|
  Array<TestData> data(A);
 | 
						|
  ASSERT_TRUE(data.empty());
 | 
						|
  ASSERT_EQ(data.begin(), data.end());
 | 
						|
  auto I0 = data.begin();
 | 
						|
  ASSERT_EQ(I0++, data.begin());
 | 
						|
  ASSERT_NE(I0, data.begin());
 | 
						|
  for (const auto &D : data) {
 | 
						|
    (void)D;
 | 
						|
    FAIL();
 | 
						|
  }
 | 
						|
  ASSERT_NE(data.AppendEmplace(1, 1), nullptr);
 | 
						|
  ASSERT_EQ(data.size(), 1u);
 | 
						|
  ASSERT_NE(data.begin(), data.end());
 | 
						|
  auto &D0 = *data.begin();
 | 
						|
  ASSERT_EQ(D0.First, 1);
 | 
						|
  ASSERT_EQ(D0.Second, 1);
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, IteratorRetreat) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 4);
 | 
						|
  Array<TestData> data(A);
 | 
						|
  ASSERT_TRUE(data.empty());
 | 
						|
  ASSERT_EQ(data.begin(), data.end());
 | 
						|
  ASSERT_NE(data.AppendEmplace(1, 1), nullptr);
 | 
						|
  ASSERT_EQ(data.size(), 1u);
 | 
						|
  ASSERT_NE(data.begin(), data.end());
 | 
						|
  auto &D0 = *data.begin();
 | 
						|
  ASSERT_EQ(D0.First, 1);
 | 
						|
  ASSERT_EQ(D0.Second, 1);
 | 
						|
 | 
						|
  auto I0 = data.end();
 | 
						|
  ASSERT_EQ(I0--, data.end());
 | 
						|
  ASSERT_NE(I0, data.end());
 | 
						|
  ASSERT_EQ(I0, data.begin());
 | 
						|
  ASSERT_EQ(I0->First, 1);
 | 
						|
  ASSERT_EQ(I0->Second, 1);
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, IteratorTrimBehaviour) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 20);
 | 
						|
  Array<TestData> Data(A);
 | 
						|
  ASSERT_TRUE(Data.empty());
 | 
						|
  auto I0Begin = Data.begin(), I0End = Data.end();
 | 
						|
  // Add enough elements in Data to have more than one chunk.
 | 
						|
  constexpr auto Segment = Array<TestData>::SegmentSize;
 | 
						|
  constexpr auto SegmentX2 = Segment * 2;
 | 
						|
  for (auto i = SegmentX2; i > 0u; --i) {
 | 
						|
    Data.AppendEmplace(static_cast<s64>(i), static_cast<s64>(i));
 | 
						|
  }
 | 
						|
  ASSERT_EQ(Data.size(), SegmentX2);
 | 
						|
  {
 | 
						|
    auto &Back = Data.back();
 | 
						|
    ASSERT_EQ(Back.First, 1);
 | 
						|
    ASSERT_EQ(Back.Second, 1);
 | 
						|
  }
 | 
						|
 | 
						|
  // Trim one chunk's elements worth.
 | 
						|
  Data.trim(Segment);
 | 
						|
  ASSERT_EQ(Data.size(), Segment);
 | 
						|
 | 
						|
  // Check that we are still able to access 'back' properly.
 | 
						|
  {
 | 
						|
    auto &Back = Data.back();
 | 
						|
    ASSERT_EQ(Back.First, static_cast<s64>(Segment + 1));
 | 
						|
    ASSERT_EQ(Back.Second, static_cast<s64>(Segment + 1));
 | 
						|
  }
 | 
						|
 | 
						|
  // Then trim until it's empty.
 | 
						|
  Data.trim(Segment);
 | 
						|
  ASSERT_TRUE(Data.empty());
 | 
						|
 | 
						|
  // Here our iterators should be the same.
 | 
						|
  auto I1Begin = Data.begin(), I1End = Data.end();
 | 
						|
  EXPECT_EQ(I0Begin, I1Begin);
 | 
						|
  EXPECT_EQ(I0End, I1End);
 | 
						|
 | 
						|
  // Then we ensure that adding elements back works just fine.
 | 
						|
  for (auto i = SegmentX2; i > 0u; --i) {
 | 
						|
    Data.AppendEmplace(static_cast<s64>(i), static_cast<s64>(i));
 | 
						|
  }
 | 
						|
  EXPECT_EQ(Data.size(), SegmentX2);
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, HandleExhaustedAllocator) {
 | 
						|
  using AllocatorType = typename Array<TestData>::AllocatorType;
 | 
						|
  constexpr auto Segment = Array<TestData>::SegmentSize;
 | 
						|
  constexpr auto MaxElements = Array<TestData>::ElementsPerSegment;
 | 
						|
  AllocatorType A(Segment);
 | 
						|
  Array<TestData> Data(A);
 | 
						|
  for (auto i = MaxElements; i > 0u; --i)
 | 
						|
    EXPECT_NE(Data.AppendEmplace(static_cast<s64>(i), static_cast<s64>(i)),
 | 
						|
              nullptr);
 | 
						|
  EXPECT_EQ(Data.AppendEmplace(0, 0), nullptr);
 | 
						|
  EXPECT_THAT(Data, SizeIs(MaxElements));
 | 
						|
 | 
						|
  // Trimming more elements than there are in the container should be fine.
 | 
						|
  Data.trim(MaxElements + 1);
 | 
						|
  EXPECT_THAT(Data, SizeIs(0u));
 | 
						|
}
 | 
						|
 | 
						|
struct ShadowStackEntry {
 | 
						|
  uint64_t EntryTSC = 0;
 | 
						|
  uint64_t *NodePtr = nullptr;
 | 
						|
  ShadowStackEntry(uint64_t T, uint64_t *N) : EntryTSC(T), NodePtr(N) {}
 | 
						|
};
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, SimulateStackBehaviour) {
 | 
						|
  using AllocatorType = typename Array<ShadowStackEntry>::AllocatorType;
 | 
						|
  AllocatorType A(1 << 10);
 | 
						|
  Array<ShadowStackEntry> Data(A);
 | 
						|
  static uint64_t Dummy = 0;
 | 
						|
  constexpr uint64_t Max = 9;
 | 
						|
 | 
						|
  for (uint64_t i = 0; i < Max; ++i) {
 | 
						|
    auto P = Data.Append({i, &Dummy});
 | 
						|
    ASSERT_NE(P, nullptr);
 | 
						|
    ASSERT_EQ(P->NodePtr, &Dummy);
 | 
						|
    auto &Back = Data.back();
 | 
						|
    ASSERT_EQ(Back.NodePtr, &Dummy);
 | 
						|
    ASSERT_EQ(Back.EntryTSC, i);
 | 
						|
  }
 | 
						|
 | 
						|
  // Simulate a stack by checking the data from the end as we're trimming.
 | 
						|
  auto Counter = Max;
 | 
						|
  ASSERT_EQ(Data.size(), size_t(Max));
 | 
						|
  while (!Data.empty()) {
 | 
						|
    const auto &Top = Data.back();
 | 
						|
    uint64_t *TopNode = Top.NodePtr;
 | 
						|
    EXPECT_EQ(TopNode, &Dummy) << "Counter = " << Counter;
 | 
						|
    Data.trim(1);
 | 
						|
    --Counter;
 | 
						|
    ASSERT_EQ(Data.size(), size_t(Counter));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, PlacementNewOnAlignedStorage) {
 | 
						|
  using AllocatorType = typename Array<ShadowStackEntry>::AllocatorType;
 | 
						|
  typename std::aligned_storage<sizeof(AllocatorType),
 | 
						|
                                alignof(AllocatorType)>::type AllocatorStorage;
 | 
						|
  new (&AllocatorStorage) AllocatorType(1 << 10);
 | 
						|
  auto *A = reinterpret_cast<AllocatorType *>(&AllocatorStorage);
 | 
						|
  typename std::aligned_storage<sizeof(Array<ShadowStackEntry>),
 | 
						|
                                alignof(Array<ShadowStackEntry>)>::type
 | 
						|
      ArrayStorage;
 | 
						|
  new (&ArrayStorage) Array<ShadowStackEntry>(*A);
 | 
						|
  auto *Data = reinterpret_cast<Array<ShadowStackEntry> *>(&ArrayStorage);
 | 
						|
 | 
						|
  static uint64_t Dummy = 0;
 | 
						|
  constexpr uint64_t Max = 9;
 | 
						|
 | 
						|
  for (uint64_t i = 0; i < Max; ++i) {
 | 
						|
    auto P = Data->Append({i, &Dummy});
 | 
						|
    ASSERT_NE(P, nullptr);
 | 
						|
    ASSERT_EQ(P->NodePtr, &Dummy);
 | 
						|
    auto &Back = Data->back();
 | 
						|
    ASSERT_EQ(Back.NodePtr, &Dummy);
 | 
						|
    ASSERT_EQ(Back.EntryTSC, i);
 | 
						|
  }
 | 
						|
 | 
						|
  // Simulate a stack by checking the data from the end as we're trimming.
 | 
						|
  auto Counter = Max;
 | 
						|
  ASSERT_EQ(Data->size(), size_t(Max));
 | 
						|
  while (!Data->empty()) {
 | 
						|
    const auto &Top = Data->back();
 | 
						|
    uint64_t *TopNode = Top.NodePtr;
 | 
						|
    EXPECT_EQ(TopNode, &Dummy) << "Counter = " << Counter;
 | 
						|
    Data->trim(1);
 | 
						|
    --Counter;
 | 
						|
    ASSERT_EQ(Data->size(), size_t(Counter));
 | 
						|
  }
 | 
						|
 | 
						|
  // Once the stack is exhausted, we re-use the storage.
 | 
						|
  for (uint64_t i = 0; i < Max; ++i) {
 | 
						|
    auto P = Data->Append({i, &Dummy});
 | 
						|
    ASSERT_NE(P, nullptr);
 | 
						|
    ASSERT_EQ(P->NodePtr, &Dummy);
 | 
						|
    auto &Back = Data->back();
 | 
						|
    ASSERT_EQ(Back.NodePtr, &Dummy);
 | 
						|
    ASSERT_EQ(Back.EntryTSC, i);
 | 
						|
  }
 | 
						|
 | 
						|
  // We re-initialize the storage, by calling the destructor and
 | 
						|
  // placement-new'ing again.
 | 
						|
  Data->~Array();
 | 
						|
  A->~AllocatorType();
 | 
						|
  new (A) AllocatorType(1 << 10);
 | 
						|
  new (Data) Array<ShadowStackEntry>(*A);
 | 
						|
 | 
						|
  // Then re-do the test.
 | 
						|
  for (uint64_t i = 0; i < Max; ++i) {
 | 
						|
    auto P = Data->Append({i, &Dummy});
 | 
						|
    ASSERT_NE(P, nullptr);
 | 
						|
    ASSERT_EQ(P->NodePtr, &Dummy);
 | 
						|
    auto &Back = Data->back();
 | 
						|
    ASSERT_EQ(Back.NodePtr, &Dummy);
 | 
						|
    ASSERT_EQ(Back.EntryTSC, i);
 | 
						|
  }
 | 
						|
 | 
						|
  // Simulate a stack by checking the data from the end as we're trimming.
 | 
						|
  Counter = Max;
 | 
						|
  ASSERT_EQ(Data->size(), size_t(Max));
 | 
						|
  while (!Data->empty()) {
 | 
						|
    const auto &Top = Data->back();
 | 
						|
    uint64_t *TopNode = Top.NodePtr;
 | 
						|
    EXPECT_EQ(TopNode, &Dummy) << "Counter = " << Counter;
 | 
						|
    Data->trim(1);
 | 
						|
    --Counter;
 | 
						|
    ASSERT_EQ(Data->size(), size_t(Counter));
 | 
						|
  }
 | 
						|
 | 
						|
  // Once the stack is exhausted, we re-use the storage.
 | 
						|
  for (uint64_t i = 0; i < Max; ++i) {
 | 
						|
    auto P = Data->Append({i, &Dummy});
 | 
						|
    ASSERT_NE(P, nullptr);
 | 
						|
    ASSERT_EQ(P->NodePtr, &Dummy);
 | 
						|
    auto &Back = Data->back();
 | 
						|
    ASSERT_EQ(Back.NodePtr, &Dummy);
 | 
						|
    ASSERT_EQ(Back.EntryTSC, i);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, ArrayOfPointersIteratorAccess) {
 | 
						|
  using PtrArray = Array<int *>;
 | 
						|
  PtrArray::AllocatorType Alloc(16384);
 | 
						|
  Array<int *> A(Alloc);
 | 
						|
  static constexpr size_t Count = 100;
 | 
						|
  std::vector<int> Integers(Count);
 | 
						|
  std::iota(Integers.begin(), Integers.end(), 0);
 | 
						|
  for (auto &I : Integers)
 | 
						|
    ASSERT_NE(A.Append(&I), nullptr);
 | 
						|
  int V = 0;
 | 
						|
  ASSERT_EQ(A.size(), Count);
 | 
						|
  for (auto P : A) {
 | 
						|
    ASSERT_NE(P, nullptr);
 | 
						|
    ASSERT_EQ(*P, V++);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
TEST(SegmentedArrayTest, ArrayOfPointersIteratorAccessExhaustion) {
 | 
						|
  using PtrArray = Array<int *>;
 | 
						|
  PtrArray::AllocatorType Alloc(4096);
 | 
						|
  Array<int *> A(Alloc);
 | 
						|
  static constexpr size_t Count = 1000;
 | 
						|
  std::vector<int> Integers(Count);
 | 
						|
  std::iota(Integers.begin(), Integers.end(), 0);
 | 
						|
  for (auto &I : Integers)
 | 
						|
    if (A.Append(&I) == nullptr)
 | 
						|
      break;
 | 
						|
  int V = 0;
 | 
						|
  ASSERT_LT(A.size(), Count);
 | 
						|
  for (auto P : A) {
 | 
						|
    ASSERT_NE(P, nullptr);
 | 
						|
    ASSERT_EQ(*P, V++);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
} // namespace __xray
 |