Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/tests/gtest/TestTArray.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "nsTArray.h"
8
#include "gtest/gtest.h"
9
#include "mozilla/ArrayUtils.h"
10
11
using namespace mozilla;
12
13
namespace TestTArray {
14
15
struct Copyable
16
{
17
  Copyable()
18
    : mDestructionCounter(nullptr)
19
0
  {
20
0
  }
21
22
  ~Copyable()
23
0
  {
24
0
    if (mDestructionCounter) {
25
0
      (*mDestructionCounter)++;
26
0
    }
27
0
  }
28
29
  Copyable(const Copyable&) = default;
30
  Copyable& operator=(const Copyable&) = default;
31
32
  uint32_t* mDestructionCounter;
33
};
34
35
struct Movable
36
{
37
  Movable()
38
    : mDestructionCounter(nullptr)
39
0
  {
40
0
  }
41
42
  ~Movable()
43
0
  {
44
0
    if (mDestructionCounter) {
45
0
      (*mDestructionCounter)++;
46
0
    }
47
0
  }
48
49
  Movable(Movable&& aOther)
50
    : mDestructionCounter(aOther.mDestructionCounter)
51
0
  {
52
0
    aOther.mDestructionCounter = nullptr;
53
0
  }
54
55
  uint32_t* mDestructionCounter;
56
};
57
58
} // namespace TestTArray
59
60
template<>
61
struct nsTArray_CopyChooser<TestTArray::Copyable>
62
{
63
  typedef nsTArray_CopyWithConstructors<TestTArray::Copyable> Type;
64
};
65
66
template<>
67
struct nsTArray_CopyChooser<TestTArray::Movable>
68
{
69
  typedef nsTArray_CopyWithConstructors<TestTArray::Movable> Type;
70
};
71
72
namespace TestTArray {
73
74
const nsTArray<int>& DummyArray()
75
0
{
76
0
  static nsTArray<int> sArray;
77
0
  if (sArray.IsEmpty()) {
78
0
    const int data[] = {4, 1, 2, 8};
79
0
    sArray.AppendElements(data, ArrayLength(data));
80
0
  }
81
0
  return sArray;
82
0
}
83
84
// This returns an invalid nsTArray with a huge length in order to test that
85
// fallible operations actually fail.
86
#ifdef DEBUG
87
const nsTArray<int>& FakeHugeArray()
88
{
89
  static nsTArray<int> sArray;
90
  if (sArray.IsEmpty()) {
91
    sArray.AppendElement();
92
    ((nsTArrayHeader*)sArray.DebugGetHeader())->mLength = UINT32_MAX;
93
  }
94
  return sArray;
95
}
96
#endif
97
98
TEST(TArray, AppendElementsRvalue)
99
0
{
100
0
  nsTArray<int> array;
101
0
102
0
  nsTArray<int> temp(DummyArray());
103
0
  array.AppendElements(std::move(temp));
104
0
  ASSERT_EQ(DummyArray(), array);
105
0
  ASSERT_TRUE(temp.IsEmpty());
106
0
107
0
  temp = DummyArray();
108
0
  array.AppendElements(std::move(temp));
109
0
  nsTArray<int> expected;
110
0
  expected.AppendElements(DummyArray());
111
0
  expected.AppendElements(DummyArray());
112
0
  ASSERT_EQ(expected, array);
113
0
  ASSERT_TRUE(temp.IsEmpty());
114
0
}
115
116
TEST(TArray, Assign)
117
0
{
118
0
  nsTArray<int> array;
119
0
  array.Assign(DummyArray());
120
0
  ASSERT_EQ(DummyArray(), array);
121
0
122
0
  ASSERT_TRUE(array.Assign(DummyArray(), fallible));
123
0
  ASSERT_EQ(DummyArray(), array);
124
0
125
#ifdef DEBUG
126
  ASSERT_FALSE(array.Assign(FakeHugeArray(), fallible));
127
#endif
128
129
0
  nsTArray<int> array2;
130
0
  array2.Assign(std::move(array));
131
0
  ASSERT_TRUE(array.IsEmpty());
132
0
  ASSERT_EQ(DummyArray(), array2);
133
0
}
134
135
TEST(TArray, AssignmentOperatorSelfAssignment)
136
0
{
137
0
  nsTArray<int> array;
138
0
  array = DummyArray();
139
0
140
0
  array = *&array;
141
0
  ASSERT_EQ(DummyArray(), array);
142
0
143
0
#if defined(__clang__)
144
0
#pragma clang diagnostic push
145
0
#pragma clang diagnostic ignored "-Wself-move"
146
0
#endif
147
0
  array = std::move(array); // self-move
148
0
  ASSERT_EQ(DummyArray(), array);
149
0
#if defined(__clang__)
150
0
#pragma clang diagnostic pop
151
0
#endif
152
0
}
153
154
TEST(TArray, CopyOverlappingForwards)
155
0
{
156
0
  const size_t rangeLength = 8;
157
0
  const size_t initialLength = 2 * rangeLength;
158
0
  uint32_t destructionCounters[initialLength];
159
0
  nsTArray<Movable> array;
160
0
  array.AppendElements(initialLength);
161
0
162
0
  for (uint32_t i = 0; i < initialLength; ++i) {
163
0
    destructionCounters[i] = 0;
164
0
  }
165
0
  for (uint32_t i = 0; i < initialLength; ++i) {
166
0
    array[i].mDestructionCounter = &destructionCounters[i];
167
0
  }
168
0
169
0
  const size_t removedLength = rangeLength / 2;
170
0
  array.RemoveElementsAt(0, removedLength);
171
0
172
0
  for (uint32_t i = 0; i < removedLength; ++i) {
173
0
    ASSERT_EQ(destructionCounters[i], 1u);
174
0
  }
175
0
  for (uint32_t i = removedLength; i < initialLength; ++i) {
176
0
    ASSERT_EQ(destructionCounters[i], 0u);
177
0
  }
178
0
}
179
180
// The code to copy overlapping regions had a bug in that it wouldn't correctly
181
// destroy all over the source elements being copied.
182
TEST(TArray, CopyOverlappingBackwards)
183
0
{
184
0
  const size_t rangeLength = 8;
185
0
  const size_t initialLength = 2 * rangeLength;
186
0
  uint32_t destructionCounters[initialLength];
187
0
  nsTArray<Copyable> array;
188
0
  array.SetCapacity(3 * rangeLength);
189
0
  array.AppendElements(initialLength);
190
0
  // To tickle the bug, we need to copy a source region:
191
0
  //
192
0
  //   ..XXXXX..
193
0
  //
194
0
  // such that it overlaps the destination region:
195
0
  //
196
0
  //   ....XXXXX
197
0
  //
198
0
  // so we are forced to copy back-to-front to ensure correct behavior.
199
0
  // The easiest way to do that is to call InsertElementsAt, which will force
200
0
  // the desired kind of shift.
201
0
  for (uint32_t i = 0; i < initialLength; ++i) {
202
0
    destructionCounters[i] = 0;
203
0
  }
204
0
  for (uint32_t i = 0; i < initialLength; ++i) {
205
0
    array[i].mDestructionCounter = &destructionCounters[i];
206
0
  }
207
0
208
0
  array.InsertElementsAt(0, rangeLength);
209
0
210
0
  for (uint32_t i = 0; i < initialLength; ++i) {
211
0
    ASSERT_EQ(destructionCounters[i], 1u);
212
0
  }
213
0
}
214
215
TEST(TArray, UnorderedRemoveElements)
216
0
{
217
0
  // When removing an element from the end of the array, it can be removed in
218
0
  // place, by destroying it and decrementing the length.
219
0
  //
220
0
  // [ 1, 2, 3 ] => [ 1, 2 ]
221
0
  //         ^
222
0
  {
223
0
    nsTArray<int> array{ 1, 2, 3 };
224
0
    array.UnorderedRemoveElementAt(2);
225
0
226
0
    nsTArray<int> goal{ 1, 2 };
227
0
    ASSERT_EQ(array, goal);
228
0
  }
229
0
230
0
  // When removing any other single element, it is removed by swapping it with
231
0
  // the last element, and then decrementing the length as before.
232
0
  //
233
0
  // [ 1, 2, 3, 4, 5, 6 ]  => [ 1, 6, 3, 4, 5 ]
234
0
  //      ^
235
0
  {
236
0
    nsTArray<int> array{1, 2, 3, 4, 5, 6};
237
0
    array.UnorderedRemoveElementAt(1);
238
0
239
0
    nsTArray<int> goal{1, 6, 3, 4, 5};
240
0
    ASSERT_EQ(array, goal);
241
0
  }
242
0
243
0
  // This method also supports efficiently removing a range of elements. If they
244
0
  // are at the end, then they can all be removed like in the one element case.
245
0
  //
246
0
  // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ]
247
0
  //         ^--------^
248
0
  {
249
0
    nsTArray<int> array{1, 2, 3, 4, 5, 6};
250
0
    array.UnorderedRemoveElementsAt(2, 4);
251
0
252
0
    nsTArray<int> goal{1, 2};
253
0
    ASSERT_EQ(array, goal);
254
0
  }
255
0
256
0
  // If more elements are removed than exist after the removed section, the
257
0
  // remaining elements will be shifted down like in a normal removal.
258
0
  //
259
0
  // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ]
260
0
  //         ^--------^
261
0
  {
262
0
    nsTArray<int> array{1, 2, 3, 4, 5, 6, 7, 8};
263
0
    array.UnorderedRemoveElementsAt(2, 4);
264
0
265
0
    nsTArray<int> goal{1, 2, 7, 8};
266
0
    ASSERT_EQ(array, goal);
267
0
  }
268
0
269
0
  // And if fewer elements are removed than exist after the removed section,
270
0
  // elements will be moved from the end of the array to fill the vacated space.
271
0
  //
272
0
  // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ]
273
0
  //      ^--^
274
0
  {
275
0
    nsTArray<int> array{1, 2, 3, 4, 5, 6, 7, 8};
276
0
    array.UnorderedRemoveElementsAt(1, 2);
277
0
278
0
    nsTArray<int> goal{1, 7, 8, 4, 5, 6};
279
0
    ASSERT_EQ(array, goal);
280
0
  }
281
0
282
0
  // We should do the right thing if we drain the entire array.
283
0
  {
284
0
    nsTArray<int> array{1, 2, 3, 4, 5};
285
0
    array.UnorderedRemoveElementsAt(0, 5);
286
0
287
0
    nsTArray<int> goal{};
288
0
    ASSERT_EQ(array, goal);
289
0
  }
290
0
291
0
  {
292
0
    nsTArray<int> array{1};
293
0
    array.UnorderedRemoveElementAt(0);
294
0
295
0
    nsTArray<int> goal{};
296
0
    ASSERT_EQ(array, goal);
297
0
  }
298
0
299
0
  // We should do the right thing if we remove the same number of elements that
300
0
  // we have remaining.
301
0
  {
302
0
    nsTArray<int> array{1, 2, 3, 4, 5, 6};
303
0
    array.UnorderedRemoveElementsAt(2, 2);
304
0
305
0
    nsTArray<int> goal{1, 2, 5, 6};
306
0
    ASSERT_EQ(array, goal);
307
0
  }
308
0
309
0
  {
310
0
    nsTArray<int> array{1, 2, 3};
311
0
    array.UnorderedRemoveElementAt(1);
312
0
313
0
    nsTArray<int> goal{1, 3};
314
0
    ASSERT_EQ(array, goal);
315
0
  }
316
0
317
0
  // We should be able to remove elements from the front without issue.
318
0
  {
319
0
    nsTArray<int> array{1, 2, 3, 4, 5, 6};
320
0
    array.UnorderedRemoveElementsAt(0, 2);
321
0
322
0
    nsTArray<int> goal{5, 6, 3, 4};
323
0
    ASSERT_EQ(array, goal);
324
0
  }
325
0
326
0
  {
327
0
    nsTArray<int> array{1, 2, 3, 4};
328
0
    array.UnorderedRemoveElementAt(0);
329
0
330
0
    nsTArray<int> goal{4, 2, 3};
331
0
    ASSERT_EQ(array, goal);
332
0
  }
333
0
}
334
335
TEST(TArray, RemoveFromEnd)
336
0
{
337
0
  {
338
0
    nsTArray<int> array{1,2,3,4};
339
0
    ASSERT_EQ(array.PopLastElement(), 4);
340
0
    array.RemoveLastElement();
341
0
    ASSERT_EQ(array.PopLastElement(), 2);
342
0
    array.RemoveLastElement();
343
0
    ASSERT_TRUE(array.IsEmpty());
344
0
  }
345
0
}
346
347
} // namespace TestTArray