blob: 66b3fbfa28bb742723223588d26edfdbe61e2da5 [file] [log] [blame]
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001// Copyright 2017 The Abseil Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "ceres/internal/fixed_array.h"
16
Austin Schuh3de38b02024-06-25 18:25:10 -070017#include <cstdio>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080018#include <cstring>
19#include <list>
20#include <memory>
21#include <numeric>
22#include <scoped_allocator>
23#include <stdexcept>
24#include <string>
25#include <vector>
26
27#include "gmock/gmock.h"
28#include "gtest/gtest.h"
29
30using ::testing::ElementsAreArray;
31
32namespace {
33
34// CERES_INTERNAL_ARRAYSIZE()
35//
36// Returns the number of elements in an array as a compile-time constant, which
37// can be used in defining new arrays. If you use this macro on a pointer by
38// mistake, you will get a compile-time error.
39#define CERES_INTERNAL_ARRAYSIZE(array) (sizeof(ArraySizeHelper(array)))
40
41// Note: this internal template function declaration is used by
42// CERES_INTERNAL_ARRAYSIZE. The function doesn't need a definition, as we only
43// use its type.
44template <typename T, size_t N>
45auto ArraySizeHelper(const T (&array)[N]) -> char (&)[N];
46
47// Helper routine to determine if a ceres::internal::FixedArray used stack
48// allocation.
49template <typename ArrayType>
50static bool IsOnStack(const ArrayType& a) {
51 return a.size() <= ArrayType::inline_elements;
52}
53
54class ConstructionTester {
55 public:
Austin Schuh3de38b02024-06-25 18:25:10 -070056 ConstructionTester() : self_ptr_(this) { constructions++; }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080057 ~ConstructionTester() {
58 assert(self_ptr_ == this);
59 self_ptr_ = nullptr;
60 destructions++;
61 }
62
63 // These are incremented as elements are constructed and destructed so we can
64 // be sure all elements are properly cleaned up.
65 static int constructions;
66 static int destructions;
67
68 void CheckConstructed() { assert(self_ptr_ == this); }
69
70 void set(int value) { value_ = value; }
71 int get() { return value_; }
72
73 private:
74 // self_ptr_ should always point to 'this' -- that's how we can be sure the
75 // constructor has been called.
76 ConstructionTester* self_ptr_;
Austin Schuh3de38b02024-06-25 18:25:10 -070077 int value_{0};
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080078};
79
80int ConstructionTester::constructions = 0;
81int ConstructionTester::destructions = 0;
82
83// ThreeInts will initialize its three ints to the value stored in
84// ThreeInts::counter. The constructor increments counter so that each object
85// in an array of ThreeInts will have different values.
86class ThreeInts {
87 public:
88 ThreeInts() {
89 x_ = counter;
90 y_ = counter;
91 z_ = counter;
92 ++counter;
93 }
94
95 static int counter;
96
97 int x_, y_, z_;
98};
99
100int ThreeInts::counter = 0;
101
102TEST(FixedArrayTest, CopyCtor) {
103 ceres::internal::FixedArray<int, 10> on_stack(5);
104 std::iota(on_stack.begin(), on_stack.end(), 0);
105 ceres::internal::FixedArray<int, 10> stack_copy = on_stack;
106 EXPECT_THAT(stack_copy, ElementsAreArray(on_stack));
107 EXPECT_TRUE(IsOnStack(stack_copy));
108
109 ceres::internal::FixedArray<int, 10> allocated(15);
110 std::iota(allocated.begin(), allocated.end(), 0);
111 ceres::internal::FixedArray<int, 10> alloced_copy = allocated;
112 EXPECT_THAT(alloced_copy, ElementsAreArray(allocated));
113 EXPECT_FALSE(IsOnStack(alloced_copy));
114}
115
116TEST(FixedArrayTest, MoveCtor) {
117 ceres::internal::FixedArray<std::unique_ptr<int>, 10> on_stack(5);
118 for (int i = 0; i < 5; ++i) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700119 on_stack[i] = std::make_unique<int>(i);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800120 }
121
122 ceres::internal::FixedArray<std::unique_ptr<int>, 10> stack_copy =
123 std::move(on_stack);
124 for (int i = 0; i < 5; ++i) EXPECT_EQ(*(stack_copy[i]), i);
125 EXPECT_EQ(stack_copy.size(), on_stack.size());
126
127 ceres::internal::FixedArray<std::unique_ptr<int>, 10> allocated(15);
128 for (int i = 0; i < 15; ++i) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700129 allocated[i] = std::make_unique<int>(i);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800130 }
131
132 ceres::internal::FixedArray<std::unique_ptr<int>, 10> alloced_copy =
133 std::move(allocated);
134 for (int i = 0; i < 15; ++i) EXPECT_EQ(*(alloced_copy[i]), i);
135 EXPECT_EQ(allocated.size(), alloced_copy.size());
136}
137
138TEST(FixedArrayTest, SmallObjects) {
139 // Small object arrays
140 {
141 // Short arrays should be on the stack
142 ceres::internal::FixedArray<int> array(4);
143 EXPECT_TRUE(IsOnStack(array));
144 }
145
146 {
147 // Large arrays should be on the heap
148 ceres::internal::FixedArray<int> array(1048576);
149 EXPECT_FALSE(IsOnStack(array));
150 }
151
152 {
153 // Arrays of <= default size should be on the stack
154 ceres::internal::FixedArray<int, 100> array(100);
155 EXPECT_TRUE(IsOnStack(array));
156 }
157
158 {
159 // Arrays of > default size should be on the heap
160 ceres::internal::FixedArray<int, 100> array(101);
161 EXPECT_FALSE(IsOnStack(array));
162 }
163
164 {
165 // Arrays with different size elements should use approximately
166 // same amount of stack space
167 ceres::internal::FixedArray<int> array1(0);
168 ceres::internal::FixedArray<char> array2(0);
169 EXPECT_LE(sizeof(array1), sizeof(array2) + 100);
170 EXPECT_LE(sizeof(array2), sizeof(array1) + 100);
171 }
172
173 {
174 // Ensure that vectors are properly constructed inside a fixed array.
175 ceres::internal::FixedArray<std::vector<int>> array(2);
176 EXPECT_EQ(0, array[0].size());
177 EXPECT_EQ(0, array[1].size());
178 }
179
180 {
181 // Regardless of ceres::internal::FixedArray implementation, check that a
182 // type with a low alignment requirement and a non power-of-two size is
183 // initialized correctly.
184 ThreeInts::counter = 1;
185 ceres::internal::FixedArray<ThreeInts> array(2);
186 EXPECT_EQ(1, array[0].x_);
187 EXPECT_EQ(1, array[0].y_);
188 EXPECT_EQ(1, array[0].z_);
189 EXPECT_EQ(2, array[1].x_);
190 EXPECT_EQ(2, array[1].y_);
191 EXPECT_EQ(2, array[1].z_);
192 }
193}
194
195TEST(FixedArrayRelationalsTest, EqualArrays) {
196 for (int i = 0; i < 10; ++i) {
197 ceres::internal::FixedArray<int, 5> a1(i);
198 std::iota(a1.begin(), a1.end(), 0);
199 ceres::internal::FixedArray<int, 5> a2(a1.begin(), a1.end());
200
201 EXPECT_TRUE(a1 == a2);
202 EXPECT_FALSE(a1 != a2);
203 EXPECT_TRUE(a2 == a1);
204 EXPECT_FALSE(a2 != a1);
205 EXPECT_FALSE(a1 < a2);
206 EXPECT_FALSE(a1 > a2);
207 EXPECT_FALSE(a2 < a1);
208 EXPECT_FALSE(a2 > a1);
209 EXPECT_TRUE(a1 <= a2);
210 EXPECT_TRUE(a1 >= a2);
211 EXPECT_TRUE(a2 <= a1);
212 EXPECT_TRUE(a2 >= a1);
213 }
214}
215
216TEST(FixedArrayRelationalsTest, UnequalArrays) {
217 for (int i = 1; i < 10; ++i) {
218 ceres::internal::FixedArray<int, 5> a1(i);
219 std::iota(a1.begin(), a1.end(), 0);
220 ceres::internal::FixedArray<int, 5> a2(a1.begin(), a1.end());
221 --a2[i / 2];
222
223 EXPECT_FALSE(a1 == a2);
224 EXPECT_TRUE(a1 != a2);
225 EXPECT_FALSE(a2 == a1);
226 EXPECT_TRUE(a2 != a1);
227 EXPECT_FALSE(a1 < a2);
228 EXPECT_TRUE(a1 > a2);
229 EXPECT_TRUE(a2 < a1);
230 EXPECT_FALSE(a2 > a1);
231 EXPECT_FALSE(a1 <= a2);
232 EXPECT_TRUE(a1 >= a2);
233 EXPECT_TRUE(a2 <= a1);
234 EXPECT_FALSE(a2 >= a1);
235 }
236}
237
238template <int stack_elements>
239static void TestArray(int n) {
240 SCOPED_TRACE(n);
241 SCOPED_TRACE(stack_elements);
242 ConstructionTester::constructions = 0;
243 ConstructionTester::destructions = 0;
244 {
245 ceres::internal::FixedArray<ConstructionTester, stack_elements> array(n);
246
247 EXPECT_THAT(array.size(), n);
248 EXPECT_THAT(array.memsize(), sizeof(ConstructionTester) * n);
249 EXPECT_THAT(array.begin() + n, array.end());
250
251 // Check that all elements were constructed
252 for (int i = 0; i < n; i++) {
253 array[i].CheckConstructed();
254 }
255 // Check that no other elements were constructed
256 EXPECT_THAT(ConstructionTester::constructions, n);
257
258 // Test operator[]
259 for (int i = 0; i < n; i++) {
260 array[i].set(i);
261 }
262 for (int i = 0; i < n; i++) {
263 EXPECT_THAT(array[i].get(), i);
264 EXPECT_THAT(array.data()[i].get(), i);
265 }
266
267 // Test data()
268 for (int i = 0; i < n; i++) {
269 array.data()[i].set(i + 1);
270 }
271 for (int i = 0; i < n; i++) {
272 EXPECT_THAT(array[i].get(), i + 1);
273 EXPECT_THAT(array.data()[i].get(), i + 1);
274 }
275 } // Close scope containing 'array'.
276
277 // Check that all constructed elements were destructed.
278 EXPECT_EQ(ConstructionTester::constructions,
279 ConstructionTester::destructions);
280}
281
282template <int elements_per_inner_array, int inline_elements>
283static void TestArrayOfArrays(int n) {
284 SCOPED_TRACE(n);
285 SCOPED_TRACE(inline_elements);
286 SCOPED_TRACE(elements_per_inner_array);
287 ConstructionTester::constructions = 0;
288 ConstructionTester::destructions = 0;
289 {
290 using InnerArray = ConstructionTester[elements_per_inner_array];
291 // Heap-allocate the FixedArray to avoid blowing the stack frame.
292 auto array_ptr = std::unique_ptr<
293 ceres::internal::FixedArray<InnerArray, inline_elements>>(
294 new ceres::internal::FixedArray<InnerArray, inline_elements>(n));
295 auto& array = *array_ptr;
296
297 ASSERT_EQ(array.size(), n);
298 ASSERT_EQ(array.memsize(),
299 sizeof(ConstructionTester) * elements_per_inner_array * n);
300 ASSERT_EQ(array.begin() + n, array.end());
301
302 // Check that all elements were constructed
303 for (int i = 0; i < n; i++) {
304 for (int j = 0; j < elements_per_inner_array; j++) {
305 (array[i])[j].CheckConstructed();
306 }
307 }
308 // Check that no other elements were constructed
309 ASSERT_EQ(ConstructionTester::constructions, n * elements_per_inner_array);
310
311 // Test operator[]
312 for (int i = 0; i < n; i++) {
313 for (int j = 0; j < elements_per_inner_array; j++) {
314 (array[i])[j].set(i * elements_per_inner_array + j);
315 }
316 }
317 for (int i = 0; i < n; i++) {
318 for (int j = 0; j < elements_per_inner_array; j++) {
319 ASSERT_EQ((array[i])[j].get(), i * elements_per_inner_array + j);
320 ASSERT_EQ((array.data()[i])[j].get(), i * elements_per_inner_array + j);
321 }
322 }
323
324 // Test data()
325 for (int i = 0; i < n; i++) {
326 for (int j = 0; j < elements_per_inner_array; j++) {
327 (array.data()[i])[j].set((i + 1) * elements_per_inner_array + j);
328 }
329 }
330 for (int i = 0; i < n; i++) {
331 for (int j = 0; j < elements_per_inner_array; j++) {
332 ASSERT_EQ((array[i])[j].get(), (i + 1) * elements_per_inner_array + j);
333 ASSERT_EQ((array.data()[i])[j].get(),
334 (i + 1) * elements_per_inner_array + j);
335 }
336 }
337 } // Close scope containing 'array'.
338
339 // Check that all constructed elements were destructed.
340 EXPECT_EQ(ConstructionTester::constructions,
341 ConstructionTester::destructions);
342}
343
344TEST(IteratorConstructorTest, NonInline) {
345 int const kInput[] = {2, 3, 5, 7, 11, 13, 17};
346 ceres::internal::FixedArray<int, CERES_INTERNAL_ARRAYSIZE(kInput) - 1> const
347 fixed(kInput, kInput + CERES_INTERNAL_ARRAYSIZE(kInput));
348 ASSERT_EQ(CERES_INTERNAL_ARRAYSIZE(kInput), fixed.size());
349 for (size_t i = 0; i < CERES_INTERNAL_ARRAYSIZE(kInput); ++i) {
350 ASSERT_EQ(kInput[i], fixed[i]);
351 }
352}
353
354TEST(IteratorConstructorTest, Inline) {
355 int const kInput[] = {2, 3, 5, 7, 11, 13, 17};
356 ceres::internal::FixedArray<int, CERES_INTERNAL_ARRAYSIZE(kInput)> const
357 fixed(kInput, kInput + CERES_INTERNAL_ARRAYSIZE(kInput));
358 ASSERT_EQ(CERES_INTERNAL_ARRAYSIZE(kInput), fixed.size());
359 for (size_t i = 0; i < CERES_INTERNAL_ARRAYSIZE(kInput); ++i) {
360 ASSERT_EQ(kInput[i], fixed[i]);
361 }
362}
363
364TEST(IteratorConstructorTest, NonPod) {
365 char const* kInput[] = {
366 "red", "orange", "yellow", "green", "blue", "indigo", "violet"};
367 ceres::internal::FixedArray<std::string> const fixed(
368 kInput, kInput + CERES_INTERNAL_ARRAYSIZE(kInput));
369 ASSERT_EQ(CERES_INTERNAL_ARRAYSIZE(kInput), fixed.size());
370 for (size_t i = 0; i < CERES_INTERNAL_ARRAYSIZE(kInput); ++i) {
371 ASSERT_EQ(kInput[i], fixed[i]);
372 }
373}
374
375TEST(IteratorConstructorTest, FromEmptyVector) {
376 std::vector<int> const empty;
377 ceres::internal::FixedArray<int> const fixed(empty.begin(), empty.end());
378 EXPECT_EQ(0, fixed.size());
379 EXPECT_EQ(empty.size(), fixed.size());
380}
381
382TEST(IteratorConstructorTest, FromNonEmptyVector) {
383 int const kInput[] = {2, 3, 5, 7, 11, 13, 17};
384 std::vector<int> const items(kInput,
385 kInput + CERES_INTERNAL_ARRAYSIZE(kInput));
386 ceres::internal::FixedArray<int> const fixed(items.begin(), items.end());
387 ASSERT_EQ(items.size(), fixed.size());
388 for (size_t i = 0; i < items.size(); ++i) {
389 ASSERT_EQ(items[i], fixed[i]);
390 }
391}
392
393TEST(IteratorConstructorTest, FromBidirectionalIteratorRange) {
394 int const kInput[] = {2, 3, 5, 7, 11, 13, 17};
395 std::list<int> const items(kInput, kInput + CERES_INTERNAL_ARRAYSIZE(kInput));
396 ceres::internal::FixedArray<int> const fixed(items.begin(), items.end());
397 EXPECT_THAT(fixed, testing::ElementsAreArray(kInput));
398}
399
400TEST(InitListConstructorTest, InitListConstruction) {
401 ceres::internal::FixedArray<int> fixed = {1, 2, 3};
402 EXPECT_THAT(fixed, testing::ElementsAreArray({1, 2, 3}));
403}
404
405TEST(FillConstructorTest, NonEmptyArrays) {
406 ceres::internal::FixedArray<int> stack_array(4, 1);
407 EXPECT_THAT(stack_array, testing::ElementsAreArray({1, 1, 1, 1}));
408
409 ceres::internal::FixedArray<int, 0> heap_array(4, 1);
410 EXPECT_THAT(stack_array, testing::ElementsAreArray({1, 1, 1, 1}));
411}
412
413TEST(FillConstructorTest, EmptyArray) {
414 ceres::internal::FixedArray<int> empty_fill(0, 1);
415 ceres::internal::FixedArray<int> empty_size(0);
416 EXPECT_EQ(empty_fill, empty_size);
417}
418
419TEST(FillConstructorTest, NotTriviallyCopyable) {
420 std::string str = "abcd";
421 ceres::internal::FixedArray<std::string> strings = {str, str, str, str};
422
423 ceres::internal::FixedArray<std::string> array(4, str);
424 EXPECT_EQ(array, strings);
425}
426
427TEST(FillConstructorTest, Disambiguation) {
428 ceres::internal::FixedArray<size_t> a(1, 2);
429 EXPECT_THAT(a, testing::ElementsAre(2));
430}
431
432TEST(FixedArrayTest, ManySizedArrays) {
433 std::vector<int> sizes;
434 for (int i = 1; i < 100; i++) sizes.push_back(i);
435 for (int i = 100; i <= 1000; i += 100) sizes.push_back(i);
436 for (int n : sizes) {
437 TestArray<0>(n);
438 TestArray<1>(n);
439 TestArray<64>(n);
440 TestArray<1000>(n);
441 }
442}
443
444TEST(FixedArrayTest, ManySizedArraysOfArraysOf1) {
445 for (int n = 1; n < 1000; n++) {
446 ASSERT_NO_FATAL_FAILURE((TestArrayOfArrays<1, 0>(n)));
447 ASSERT_NO_FATAL_FAILURE((TestArrayOfArrays<1, 1>(n)));
448 ASSERT_NO_FATAL_FAILURE((TestArrayOfArrays<1, 64>(n)));
449 ASSERT_NO_FATAL_FAILURE((TestArrayOfArrays<1, 1000>(n)));
450 }
451}
452
453TEST(FixedArrayTest, ManySizedArraysOfArraysOf2) {
454 for (int n = 1; n < 1000; n++) {
455 TestArrayOfArrays<2, 0>(n);
456 TestArrayOfArrays<2, 1>(n);
457 TestArrayOfArrays<2, 64>(n);
458 TestArrayOfArrays<2, 1000>(n);
459 }
460}
461
462// If value_type is put inside of a struct container,
463// we might evoke this error in a hardened build unless data() is carefully
464// written, so check on that.
465// error: call to int __builtin___sprintf_chk(etc...)
466// will always overflow destination buffer [-Werror]
467TEST(FixedArrayTest, AvoidParanoidDiagnostics) {
468 ceres::internal::FixedArray<char, 32> buf(32);
Austin Schuh3de38b02024-06-25 18:25:10 -0700469 snprintf(buf.data(), 32, "foo");
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800470}
471
472TEST(FixedArrayTest, TooBigInlinedSpace) {
473 struct TooBig {
474 char c[1 << 20];
475 }; // too big for even one on the stack
476
477 // Simulate the data members of ceres::internal::FixedArray, a pointer and a
478 // size_t.
479 struct Data {
480 std::tuple<size_t, std::allocator<double>> size_alloc_;
481 TooBig* p;
482 };
483
484 // Make sure TooBig objects are not inlined for 0 or default size.
485 static_assert(
486 sizeof(ceres::internal::FixedArray<TooBig, 0>) == sizeof(Data),
487 "0-sized ceres::internal::FixedArray should have same size as Data.");
488 static_assert(
489 alignof(ceres::internal::FixedArray<TooBig, 0>) == alignof(Data),
490 "0-sized ceres::internal::FixedArray should have same alignment as "
491 "Data.");
492 static_assert(sizeof(ceres::internal::FixedArray<TooBig>) == sizeof(Data),
493 "default-sized ceres::internal::FixedArray should have same "
494 "size as Data");
495 static_assert(alignof(ceres::internal::FixedArray<TooBig>) == alignof(Data),
496 "default-sized ceres::internal::FixedArray should have same "
497 "alignment as Data.");
498}
499
500// PickyDelete EXPECTs its class-scope deallocation funcs are unused.
501struct PickyDelete {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800502 void operator delete(void* p) {
503 EXPECT_TRUE(false) << __FUNCTION__;
504 ::operator delete(p);
505 }
506 void operator delete[](void* p) {
507 EXPECT_TRUE(false) << __FUNCTION__;
508 ::operator delete[](p);
509 }
510};
511
512TEST(FixedArrayTest, UsesGlobalAlloc) {
513 ceres::internal::FixedArray<PickyDelete, 0> a(5);
514}
515
516TEST(FixedArrayTest, Data) {
517 static const int kInput[] = {2, 3, 5, 7, 11, 13, 17};
518 ceres::internal::FixedArray<int> fa(std::begin(kInput), std::end(kInput));
519 EXPECT_EQ(fa.data(), &*fa.begin());
520 EXPECT_EQ(fa.data(), &fa[0]);
521
522 const ceres::internal::FixedArray<int>& cfa = fa;
523 EXPECT_EQ(cfa.data(), &*cfa.begin());
524 EXPECT_EQ(cfa.data(), &cfa[0]);
525}
526
527TEST(FixedArrayTest, Empty) {
528 ceres::internal::FixedArray<int> empty(0);
529 ceres::internal::FixedArray<int> inline_filled(1);
530 ceres::internal::FixedArray<int, 0> heap_filled(1);
531 EXPECT_TRUE(empty.empty());
532 EXPECT_FALSE(inline_filled.empty());
533 EXPECT_FALSE(heap_filled.empty());
534}
535
536TEST(FixedArrayTest, FrontAndBack) {
537 ceres::internal::FixedArray<int, 3 * sizeof(int)> inlined = {1, 2, 3};
538 EXPECT_EQ(inlined.front(), 1);
539 EXPECT_EQ(inlined.back(), 3);
540
541 ceres::internal::FixedArray<int, 0> allocated = {1, 2, 3};
542 EXPECT_EQ(allocated.front(), 1);
543 EXPECT_EQ(allocated.back(), 3);
544
545 ceres::internal::FixedArray<int> one_element = {1};
546 EXPECT_EQ(one_element.front(), one_element.back());
547}
548
549TEST(FixedArrayTest, ReverseIteratorInlined) {
550 ceres::internal::FixedArray<int, 5 * sizeof(int)> a = {0, 1, 2, 3, 4};
551
552 int counter = 5;
553 for (ceres::internal::FixedArray<int>::reverse_iterator iter = a.rbegin();
554 iter != a.rend();
555 ++iter) {
556 counter--;
557 EXPECT_EQ(counter, *iter);
558 }
559 EXPECT_EQ(counter, 0);
560
561 counter = 5;
562 for (ceres::internal::FixedArray<int>::const_reverse_iterator iter =
563 a.rbegin();
564 iter != a.rend();
565 ++iter) {
566 counter--;
567 EXPECT_EQ(counter, *iter);
568 }
569 EXPECT_EQ(counter, 0);
570
571 counter = 5;
572 for (auto iter = a.crbegin(); iter != a.crend(); ++iter) {
573 counter--;
574 EXPECT_EQ(counter, *iter);
575 }
576 EXPECT_EQ(counter, 0);
577}
578
579TEST(FixedArrayTest, ReverseIteratorAllocated) {
580 ceres::internal::FixedArray<int, 0> a = {0, 1, 2, 3, 4};
581
582 int counter = 5;
583 for (ceres::internal::FixedArray<int>::reverse_iterator iter = a.rbegin();
584 iter != a.rend();
585 ++iter) {
586 counter--;
587 EXPECT_EQ(counter, *iter);
588 }
589 EXPECT_EQ(counter, 0);
590
591 counter = 5;
592 for (ceres::internal::FixedArray<int>::const_reverse_iterator iter =
593 a.rbegin();
594 iter != a.rend();
595 ++iter) {
596 counter--;
597 EXPECT_EQ(counter, *iter);
598 }
599 EXPECT_EQ(counter, 0);
600
601 counter = 5;
602 for (auto iter = a.crbegin(); iter != a.crend(); ++iter) {
603 counter--;
604 EXPECT_EQ(counter, *iter);
605 }
606 EXPECT_EQ(counter, 0);
607}
608
609TEST(FixedArrayTest, Fill) {
610 ceres::internal::FixedArray<int, 5 * sizeof(int)> inlined(5);
611 int fill_val = 42;
612 inlined.fill(fill_val);
613 for (int i : inlined) EXPECT_EQ(i, fill_val);
614
615 ceres::internal::FixedArray<int, 0> allocated(5);
616 allocated.fill(fill_val);
617 for (int i : allocated) EXPECT_EQ(i, fill_val);
618
619 // It doesn't do anything, just make sure this compiles.
620 ceres::internal::FixedArray<int> empty(0);
621 empty.fill(fill_val);
622}
623
624// TODO(johnsoncj): Investigate InlinedStorage default initialization in GCC 4.x
625#ifndef __GNUC__
626TEST(FixedArrayTest, DefaultCtorDoesNotValueInit) {
627 using T = char;
628 constexpr auto capacity = 10;
629 using FixedArrType = ceres::internal::FixedArray<T, capacity>;
630 using FixedArrBuffType =
631 typename std::aligned_storage<sizeof(FixedArrType),
632 alignof(FixedArrType)>::type;
633 constexpr auto scrubbed_bits = 0x95;
634 constexpr auto length = capacity / 2;
635
636 FixedArrBuffType buff;
637 std::memset(std::addressof(buff), scrubbed_bits, sizeof(FixedArrBuffType));
638
639 FixedArrType* arr =
640 ::new (static_cast<void*>(std::addressof(buff))) FixedArrType(length);
641 EXPECT_THAT(*arr, testing::Each(scrubbed_bits));
642 arr->~FixedArrType();
643}
644#endif // __GNUC__
645
646// This is a stateful allocator, but the state lives outside of the
647// allocator (in whatever test is using the allocator). This is odd
648// but helps in tests where the allocator is propagated into nested
649// containers - that chain of allocators uses the same state and is
650// thus easier to query for aggregate allocation information.
651template <typename T>
652class CountingAllocator : public std::allocator<T> {
653 public:
654 using Alloc = std::allocator<T>;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800655 using size_type = typename Alloc::size_type;
656
Austin Schuh3de38b02024-06-25 18:25:10 -0700657 CountingAllocator() = default;
658 explicit CountingAllocator(int64_t* b) : bytes_used_(b) {}
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800659 CountingAllocator(int64_t* b, int64_t* a)
660 : bytes_used_(b), instance_count_(a) {}
661
662 template <typename U>
663 explicit CountingAllocator(const CountingAllocator<U>& x)
664 : Alloc(x),
665 bytes_used_(x.bytes_used_),
666 instance_count_(x.instance_count_) {}
667
Austin Schuh3de38b02024-06-25 18:25:10 -0700668 T* allocate(size_type n) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800669 assert(bytes_used_ != nullptr);
670 *bytes_used_ += n * sizeof(T);
Austin Schuh3de38b02024-06-25 18:25:10 -0700671 return Alloc::allocate(n);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800672 }
673
Austin Schuh3de38b02024-06-25 18:25:10 -0700674 void deallocate(T* p, size_type n) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800675 Alloc::deallocate(p, n);
676 assert(bytes_used_ != nullptr);
677 *bytes_used_ -= n * sizeof(T);
678 }
679
Austin Schuh3de38b02024-06-25 18:25:10 -0700680 int64_t* bytes_used_{nullptr};
681 int64_t* instance_count_{nullptr};
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800682};
683
684TEST(AllocatorSupportTest, CountInlineAllocations) {
685 constexpr size_t inlined_size = 4;
686 using Alloc = CountingAllocator<int>;
687 using AllocFxdArr = ceres::internal::FixedArray<int, inlined_size, Alloc>;
688
689 int64_t allocated = 0;
690 int64_t active_instances = 0;
691
692 {
693 const int ia[] = {0, 1, 2, 3, 4, 5, 6, 7};
694
695 Alloc alloc(&allocated, &active_instances);
696
697 AllocFxdArr arr(ia, ia + inlined_size, alloc);
698 static_cast<void>(arr);
699 }
700
701 EXPECT_EQ(allocated, 0);
702 EXPECT_EQ(active_instances, 0);
703}
704
705TEST(AllocatorSupportTest, CountOutoflineAllocations) {
706 constexpr size_t inlined_size = 4;
707 using Alloc = CountingAllocator<int>;
708 using AllocFxdArr = ceres::internal::FixedArray<int, inlined_size, Alloc>;
709
710 int64_t allocated = 0;
711 int64_t active_instances = 0;
712
713 {
714 const int ia[] = {0, 1, 2, 3, 4, 5, 6, 7};
715 Alloc alloc(&allocated, &active_instances);
716
717 AllocFxdArr arr(ia, ia + CERES_INTERNAL_ARRAYSIZE(ia), alloc);
718
719 EXPECT_EQ(allocated, arr.size() * sizeof(int));
720 static_cast<void>(arr);
721 }
722
723 EXPECT_EQ(active_instances, 0);
724}
725
726TEST(AllocatorSupportTest, CountCopyInlineAllocations) {
727 constexpr size_t inlined_size = 4;
728 using Alloc = CountingAllocator<int>;
729 using AllocFxdArr = ceres::internal::FixedArray<int, inlined_size, Alloc>;
730
731 int64_t allocated1 = 0;
732 int64_t allocated2 = 0;
733 int64_t active_instances = 0;
734 Alloc alloc(&allocated1, &active_instances);
735 Alloc alloc2(&allocated2, &active_instances);
736
737 {
738 int initial_value = 1;
739
740 AllocFxdArr arr1(inlined_size / 2, initial_value, alloc);
741
742 EXPECT_EQ(allocated1, 0);
743
744 AllocFxdArr arr2(arr1, alloc2);
745
746 EXPECT_EQ(allocated2, 0);
747 static_cast<void>(arr1);
748 static_cast<void>(arr2);
749 }
750
751 EXPECT_EQ(active_instances, 0);
752}
753
754TEST(AllocatorSupportTest, CountCopyOutoflineAllocations) {
755 constexpr size_t inlined_size = 4;
756 using Alloc = CountingAllocator<int>;
757 using AllocFxdArr = ceres::internal::FixedArray<int, inlined_size, Alloc>;
758
759 int64_t allocated1 = 0;
760 int64_t allocated2 = 0;
761 int64_t active_instances = 0;
762 Alloc alloc(&allocated1, &active_instances);
763 Alloc alloc2(&allocated2, &active_instances);
764
765 {
766 int initial_value = 1;
767
768 AllocFxdArr arr1(inlined_size * 2, initial_value, alloc);
769
770 EXPECT_EQ(allocated1, arr1.size() * sizeof(int));
771
772 AllocFxdArr arr2(arr1, alloc2);
773
774 EXPECT_EQ(allocated2, inlined_size * 2 * sizeof(int));
775 static_cast<void>(arr1);
776 static_cast<void>(arr2);
777 }
778
779 EXPECT_EQ(active_instances, 0);
780}
781
782TEST(AllocatorSupportTest, SizeValAllocConstructor) {
783 using testing::AllOf;
784 using testing::Each;
785 using testing::SizeIs;
786
787 constexpr size_t inlined_size = 4;
788 using Alloc = CountingAllocator<int>;
789 using AllocFxdArr = ceres::internal::FixedArray<int, inlined_size, Alloc>;
790
791 {
792 auto len = inlined_size / 2;
793 auto val = 0;
794 int64_t allocated = 0;
795 AllocFxdArr arr(len, val, Alloc(&allocated));
796
797 EXPECT_EQ(allocated, 0);
798 EXPECT_THAT(arr, AllOf(SizeIs(len), Each(0)));
799 }
800
801 {
802 auto len = inlined_size * 2;
803 auto val = 0;
804 int64_t allocated = 0;
805 AllocFxdArr arr(len, val, Alloc(&allocated));
806
807 EXPECT_EQ(allocated, len * sizeof(int));
808 EXPECT_THAT(arr, AllOf(SizeIs(len), Each(0)));
809 }
810}
811
812struct EigenStruct {
813 Eigen::Vector4d data;
814};
815
816static_assert(
817 std::is_same<ceres::internal::FixedArrayDefaultAllocator<double>,
818 std::allocator<double>>::value,
819 "Double is a trivial type, so std::allocator should be used here.");
820static_assert(
821 std::is_same<ceres::internal::FixedArrayDefaultAllocator<double*>,
822 std::allocator<double*>>::value,
823 "A pointer is a trivial type, so std::allocator should be used here.");
824static_assert(
825 std::is_same<ceres::internal::FixedArrayDefaultAllocator<Eigen::Matrix4d>,
826 Eigen::aligned_allocator<Eigen::Matrix4d>>::value,
827 "An Eigen::Matrix4d needs the Eigen::aligned_allocator for proper "
828 "alignment.");
829static_assert(
830 std::is_same<ceres::internal::FixedArrayDefaultAllocator<EigenStruct>,
831 Eigen::aligned_allocator<EigenStruct>>::value,
832 "A struct containing fixed size Eigen types needs Eigen::aligned_allocator "
833 "for proper alignment.");
834
835} // namespace