summaryrefslogtreecommitdiffstats
path: root/gfx/tests/gtest/TestArena.cpp
blob: 4822b32f9549537fa7d043becfcb82c2b701cad3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

#include "gtest/gtest.h"
#include "gmock/gmock.h"

#include "mozilla/gfx/IterableArena.h"
#include <string>

using namespace mozilla;
using namespace mozilla::gfx;

#ifdef A
#undef A
#endif

#ifdef B
#undef B
#endif

// to avoid having symbols that collide easily like A and B in the global namespace
namespace test_arena {

class A;
class B;

class Base {
public:
  virtual ~Base() {}
  virtual A* AsA() { return nullptr; }
  virtual B* AsB() { return nullptr; }
};

static int sDtorItemA = 0;
static int sDtorItemB = 0;

class A : public Base {
public:
  virtual A* AsA() override { return this; }

  explicit A(uint64_t val) : mVal(val) {}
  ~A() { ++sDtorItemA; }

  uint64_t mVal;
};

class B : public Base {
public:
  virtual B* AsB() override { return this; }

  explicit B(const string& str) : mVal(str) {}
  ~B() { ++sDtorItemB; }

  std::string mVal;
};

struct BigStruct {
  uint64_t mVal;
  uint8_t data[120];

  explicit BigStruct(uint64_t val) : mVal(val) {}
};

void TestArenaAlloc(IterableArena::ArenaType aType)
{
  sDtorItemA = 0;
  sDtorItemB = 0;
  IterableArena arena(aType, 256);

  // An empty arena has no items to iterate over.
  {
    int iterations = 0;
    arena.ForEach([&](void* item){
      iterations++;
    });
    ASSERT_EQ(iterations, 0);
  }

  auto a1 = arena.Alloc<A>(42);
  auto b1 = arena.Alloc<B>("Obladi oblada");
  auto a2 = arena.Alloc<A>(1337);
  auto b2 = arena.Alloc<B>("Yellow submarine");
  auto b3 = arena.Alloc<B>("She's got a ticket to ride");

  // Alloc returns a non-negative offset if the allocation succeeded.
  ASSERT_TRUE(a1 >= 0);
  ASSERT_TRUE(a2 >= 0);
  ASSERT_TRUE(b1 >= 0);
  ASSERT_TRUE(b2 >= 0);
  ASSERT_TRUE(b3 >= 0);

  ASSERT_TRUE(arena.GetStorage(a1) != nullptr);
  ASSERT_TRUE(arena.GetStorage(a2) != nullptr);
  ASSERT_TRUE(arena.GetStorage(b1) != nullptr);
  ASSERT_TRUE(arena.GetStorage(b2) != nullptr);
  ASSERT_TRUE(arena.GetStorage(b3) != nullptr);

  ASSERT_TRUE(((Base*)arena.GetStorage(a1))->AsA() != nullptr);
  ASSERT_TRUE(((Base*)arena.GetStorage(a2))->AsA() != nullptr);

  ASSERT_TRUE(((Base*)arena.GetStorage(b1))->AsB() != nullptr);
  ASSERT_TRUE(((Base*)arena.GetStorage(b2))->AsB() != nullptr);
  ASSERT_TRUE(((Base*)arena.GetStorage(b3))->AsB() != nullptr);

  ASSERT_EQ(((Base*)arena.GetStorage(a1))->AsA()->mVal, (uint64_t)42);
  ASSERT_EQ(((Base*)arena.GetStorage(a2))->AsA()->mVal, (uint64_t)1337);

  ASSERT_EQ(((Base*)arena.GetStorage(b1))->AsB()->mVal, std::string("Obladi oblada"));
  ASSERT_EQ(((Base*)arena.GetStorage(b2))->AsB()->mVal, std::string("Yellow submarine"));
  ASSERT_EQ(((Base*)arena.GetStorage(b3))->AsB()->mVal, std::string("She's got a ticket to ride"));

  {
    int iterations = 0;
    arena.ForEach([&](void* item){
      iterations++;
    });
    ASSERT_EQ(iterations, 5);
  }

  // Typically, running the destructors of the elements in the arena will is done
  // manually like this:
  arena.ForEach([](void* item){
    ((Base*)item)->~Base();
  });
  arena.Clear();
  ASSERT_EQ(sDtorItemA, 2);
  ASSERT_EQ(sDtorItemB, 3);

  // An empty arena has no items to iterate over (we just cleared it).
  {
    int iterations = 0;
    arena.ForEach([&](void* item){
      iterations++;
    });
    ASSERT_EQ(iterations, 0);
  }

}

void TestArenaLimit(IterableArena::ArenaType aType, bool aShouldReachLimit)
{
  IterableArena arena(aType, 128);

  // A non-growable arena should return a negative offset when running out
  // of space, without crashing.
  // We should not run out of space with a growable arena (unless the os is
  // running out of memory but this isn't expected for this test).
  bool reachedLimit = false;
  for (int i = 0; i < 100; ++i) {
    auto offset = arena.Alloc<A>(42);
    if (offset < 0) {
      reachedLimit = true;
      break;
    }
  }
  ASSERT_EQ(reachedLimit, aShouldReachLimit);
}

} // namespace test_arena

using namespace test_arena;

TEST(Moz2D, FixedArena) {
  TestArenaAlloc(IterableArena::FIXED_SIZE);
  TestArenaLimit(IterableArena::FIXED_SIZE, true);
}

TEST(Moz2D, GrowableArena) {
  TestArenaAlloc(IterableArena::GROWABLE);
  TestArenaLimit(IterableArena::GROWABLE, false);

  IterableArena arena(IterableArena::GROWABLE, 16);
  // sizeof(BigStruct) is more than twice the initial capacity, make sure that
  // this doesn't blow everything up, since the arena doubles its storage size each
  // time it grows (until it finds a size that fits).
  auto a = arena.Alloc<BigStruct>(1);
  auto b = arena.Alloc<BigStruct>(2);
  auto c = arena.Alloc<BigStruct>(3);

  // Offsets should also still point to the appropriate values after reallocation.
  ASSERT_EQ(((BigStruct*)arena.GetStorage(a))->mVal, (uint64_t)1);
  ASSERT_EQ(((BigStruct*)arena.GetStorage(b))->mVal, (uint64_t)2);
  ASSERT_EQ(((BigStruct*)arena.GetStorage(c))->mVal, (uint64_t)3);

  arena.Clear();
}