/* * Copyright 2014 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkRefCnt.h" #include "include/private/SkTArray.h" #include "include/utils/SkRandom.h" #include "tests/Test.h" // This class is used to test SkTArray's behavior with classes containing a vtable. namespace { class TestClass { public: TestClass() = default; TestClass(const TestClass&) = default; TestClass& operator=(const TestClass&) = default; TestClass(int v) : value(v) {} virtual ~TestClass() {} bool operator==(const TestClass& c) const { return value == c.value; } int value = 0; }; } // namespace // Tests the SkTArray class template. template static void TestTSet_basic(skiatest::Reporter* reporter) { SkTArray a; // Starts empty. REPORTER_ASSERT(reporter, a.empty()); REPORTER_ASSERT(reporter, a.count() == 0); // { }, add a default constructed element a.push_back() = T{0}; REPORTER_ASSERT(reporter, !a.empty()); REPORTER_ASSERT(reporter, a.count() == 1); // { 0 }, removeShuffle the only element. a.removeShuffle(0); REPORTER_ASSERT(reporter, a.empty()); REPORTER_ASSERT(reporter, a.count() == 0); // { }, add a default, add a 1, remove first a.push_back() = T{0}; a.push_back() = T{1}; a.removeShuffle(0); REPORTER_ASSERT(reporter, !a.empty()); REPORTER_ASSERT(reporter, a.count() == 1); REPORTER_ASSERT(reporter, a[0] == T{1}); // { 1 }, replace with new array T b[5] = {T{0}, T{1}, T{2}, T{3}, T{4}}; a.reset(b, SK_ARRAY_COUNT(b)); REPORTER_ASSERT(reporter, a.count() == SK_ARRAY_COUNT(b)); REPORTER_ASSERT(reporter, a[2] == T{2}); REPORTER_ASSERT(reporter, a[4] == T{4}); // { 0, 1, 2, 3, 4 }, removeShuffle the last a.removeShuffle(4); REPORTER_ASSERT(reporter, a.count() == SK_ARRAY_COUNT(b) - 1); REPORTER_ASSERT(reporter, a[3] == T{3}); // { 0, 1, 2, 3 }, remove a middle, note shuffle a.removeShuffle(1); REPORTER_ASSERT(reporter, a.count() == SK_ARRAY_COUNT(b) - 2); REPORTER_ASSERT(reporter, a[0] == T{0}); REPORTER_ASSERT(reporter, a[1] == T{3}); REPORTER_ASSERT(reporter, a[2] == T{2}); // { 0, 3, 2 } } template static void test_construction(skiatest::Reporter* reporter) { using ValueType = typename T::value_type; // No arguments: Creates an empty array with no initial storage. T arrayNoArgs; REPORTER_ASSERT(reporter, arrayNoArgs.empty()); // Single integer: Creates an empty array that will preallocate space for reserveCount elements. T arrayReserve(15); REPORTER_ASSERT(reporter, arrayReserve.empty()); REPORTER_ASSERT(reporter, arrayReserve.capacity() == 15); // Another array, const&: Copies one array to another. T arrayInitial; arrayInitial.push_back(ValueType{1}); arrayInitial.push_back(ValueType{2}); arrayInitial.push_back(ValueType{3}); T arrayCopy(arrayInitial); REPORTER_ASSERT(reporter, arrayInitial.size() == 3); REPORTER_ASSERT(reporter, arrayInitial[0] == ValueType{1}); REPORTER_ASSERT(reporter, arrayInitial[1] == ValueType{2}); REPORTER_ASSERT(reporter, arrayInitial[2] == ValueType{3}); REPORTER_ASSERT(reporter, arrayCopy.size() == 3); REPORTER_ASSERT(reporter, arrayCopy[0] == ValueType{1}); REPORTER_ASSERT(reporter, arrayCopy[1] == ValueType{2}); REPORTER_ASSERT(reporter, arrayCopy[2] == ValueType{3}); // Another array, &&: Moves one array to another. T arrayMove(std::move(arrayInitial)); REPORTER_ASSERT(reporter, arrayInitial.empty()); // NOLINT(bugprone-use-after-move) REPORTER_ASSERT(reporter, arrayMove.size() == 3); REPORTER_ASSERT(reporter, arrayMove[0] == ValueType{1}); REPORTER_ASSERT(reporter, arrayMove[1] == ValueType{2}); REPORTER_ASSERT(reporter, arrayMove[2] == ValueType{3}); // Pointer and count: Copies contents of a standard C array. typename T::value_type data[3] = { 7, 8, 9 }; T arrayPtrCount(data, 3); REPORTER_ASSERT(reporter, arrayPtrCount.size() == 3); REPORTER_ASSERT(reporter, arrayPtrCount[0] == ValueType{7}); REPORTER_ASSERT(reporter, arrayPtrCount[1] == ValueType{8}); REPORTER_ASSERT(reporter, arrayPtrCount[2] == ValueType{9}); // Initializer list. T arrayInitializer{8, 6, 7, 5, 3, 0, 9}; REPORTER_ASSERT(reporter, arrayInitializer.size() == 7); REPORTER_ASSERT(reporter, arrayInitializer[0] == ValueType{8}); REPORTER_ASSERT(reporter, arrayInitializer[1] == ValueType{6}); REPORTER_ASSERT(reporter, arrayInitializer[2] == ValueType{7}); REPORTER_ASSERT(reporter, arrayInitializer[3] == ValueType{5}); REPORTER_ASSERT(reporter, arrayInitializer[4] == ValueType{3}); REPORTER_ASSERT(reporter, arrayInitializer[5] == ValueType{0}); REPORTER_ASSERT(reporter, arrayInitializer[6] == ValueType{9}); } template static void test_skstarray_compatibility(skiatest::Reporter* reporter) { // We expect SkTArrays of the same type to be copyable and movable, even when: // - one side is an SkTArray, and the other side is an SkSTArray // - both sides are SkSTArray, but each side has a different internal capacity T tArray; tArray.push_back(1); tArray.push_back(2); tArray.push_back(3); T tArray2 = tArray; // Copy construction from other-type array. U arrayCopy(tArray); REPORTER_ASSERT(reporter, tArray.size() == 3); REPORTER_ASSERT(reporter, tArray[0] == 1); REPORTER_ASSERT(reporter, tArray[1] == 2); REPORTER_ASSERT(reporter, tArray[2] == 3); REPORTER_ASSERT(reporter, arrayCopy.size() == 3); REPORTER_ASSERT(reporter, arrayCopy[0] == 1); REPORTER_ASSERT(reporter, arrayCopy[1] == 2); REPORTER_ASSERT(reporter, arrayCopy[2] == 3); // Assignment from other-type array. U arrayAssignment; arrayAssignment = tArray; REPORTER_ASSERT(reporter, tArray.size() == 3); REPORTER_ASSERT(reporter, tArray[0] == 1); REPORTER_ASSERT(reporter, tArray[1] == 2); REPORTER_ASSERT(reporter, tArray[2] == 3); REPORTER_ASSERT(reporter, arrayAssignment.size() == 3); REPORTER_ASSERT(reporter, arrayAssignment[0] == 1); REPORTER_ASSERT(reporter, arrayAssignment[1] == 2); REPORTER_ASSERT(reporter, arrayAssignment[2] == 3); // Move construction from other-type array. U arrayMove(std::move(tArray)); REPORTER_ASSERT(reporter, tArray.empty()); // NOLINT(bugprone-use-after-move) REPORTER_ASSERT(reporter, arrayMove.size() == 3); REPORTER_ASSERT(reporter, arrayMove[0] == 1); REPORTER_ASSERT(reporter, arrayMove[1] == 2); REPORTER_ASSERT(reporter, arrayMove[2] == 3); // Move assignment from other-type array. U arrayMoveAssign; arrayMoveAssign = std::move(tArray2); REPORTER_ASSERT(reporter, tArray2.empty()); // NOLINT(bugprone-use-after-move) REPORTER_ASSERT(reporter, arrayMoveAssign.size() == 3); REPORTER_ASSERT(reporter, arrayMoveAssign[0] == 1); REPORTER_ASSERT(reporter, arrayMoveAssign[1] == 2); REPORTER_ASSERT(reporter, arrayMoveAssign[2] == 3); } template static void test_swap(skiatest::Reporter* reporter, SkTArray* (&arrays)[4], int (&sizes)[7]) { for (auto a : arrays) { for (auto b : arrays) { if (a == b) { continue; } for (auto sizeA : sizes) { for (auto sizeB : sizes) { a->reset(); b->reset(); int curr = 0; for (int i = 0; i < sizeA; i++) { a->push_back(curr++); } for (int i = 0; i < sizeB; i++) { b->push_back(curr++); } a->swap(*b); REPORTER_ASSERT(reporter, b->count() == sizeA); REPORTER_ASSERT(reporter, a->count() == sizeB); curr = 0; for (auto&& x : *b) { REPORTER_ASSERT(reporter, x == curr++); } for (auto&& x : *a) { REPORTER_ASSERT(reporter, x == curr++); } a->swap(*a); curr = sizeA; for (auto&& x : *a) { REPORTER_ASSERT(reporter, x == curr++); } }} }} } static void test_swap(skiatest::Reporter* reporter) { int sizes[] = {0, 1, 5, 10, 15, 20, 25}; SkTArray arr; SkSTArray< 5, int> arr5; SkSTArray<10, int> arr10; SkSTArray<20, int> arr20; SkTArray* arrays[] = { &arr, &arr5, &arr10, &arr20 }; test_swap(reporter, arrays, sizes); struct MoveOnlyInt { MoveOnlyInt(int i) : fInt(i) {} MoveOnlyInt(MoveOnlyInt&& that) : fInt(that.fInt) {} bool operator==(int i) { return fInt == i; } int fInt; }; SkTArray moi; SkSTArray< 5, MoveOnlyInt> moi5; SkSTArray<10, MoveOnlyInt> moi10; SkSTArray<20, MoveOnlyInt> moi20; SkTArray* arraysMoi[] = { &moi, &moi5, &moi10, &moi20 }; test_swap(reporter, arraysMoi, sizes); } void test_unnecessary_alloc(skiatest::Reporter* reporter) { { SkTArray a; REPORTER_ASSERT(reporter, a.capacity() == 0); } { SkSTArray<10, int> a; REPORTER_ASSERT(reporter, a.capacity() == 10); } { SkTArray a(1); REPORTER_ASSERT(reporter, a.capacity() >= 1); } { SkTArray a, b; b = a; REPORTER_ASSERT(reporter, b.capacity() == 0); } { SkSTArray<10, int> a; SkTArray b; b = a; REPORTER_ASSERT(reporter, b.capacity() == 0); } { SkTArray a; SkTArray b(a); // NOLINT(performance-unnecessary-copy-initialization) REPORTER_ASSERT(reporter, b.capacity() == 0); } { SkSTArray<10, int> a; SkTArray b(a); // NOLINT(performance-unnecessary-copy-initialization) REPORTER_ASSERT(reporter, b.capacity() == 0); } { SkTArray a; SkTArray b(std::move(a)); REPORTER_ASSERT(reporter, b.capacity() == 0); } { SkSTArray<10, int> a; SkTArray b(std::move(a)); REPORTER_ASSERT(reporter, b.capacity() == 0); } { SkTArray a; SkTArray b; b = std::move(a); REPORTER_ASSERT(reporter, b.capacity() == 0); } { SkSTArray<10, int> a; SkTArray b; b = std::move(a); REPORTER_ASSERT(reporter, b.capacity() == 0); } } static void test_self_assignment(skiatest::Reporter* reporter) { SkTArray a; a.push_back(1); REPORTER_ASSERT(reporter, !a.empty()); REPORTER_ASSERT(reporter, a.count() == 1); REPORTER_ASSERT(reporter, a[0] == 1); a = static_cast(a); REPORTER_ASSERT(reporter, !a.empty()); REPORTER_ASSERT(reporter, a.count() == 1); REPORTER_ASSERT(reporter, a[0] == 1); } template static void test_array_reserve(skiatest::Reporter* reporter, Array* array, int reserveCount) { SkRandom random; REPORTER_ASSERT(reporter, array->capacity() >= reserveCount); array->push_back(); REPORTER_ASSERT(reporter, array->capacity() >= reserveCount); array->pop_back(); REPORTER_ASSERT(reporter, array->capacity() >= reserveCount); while (array->count() < reserveCount) { // Two steps forward, one step back if (random.nextULessThan(3) < 2) { array->push_back(); } else if (array->count() > 0) { array->pop_back(); } REPORTER_ASSERT(reporter, array->capacity() >= reserveCount); } } template static void test_reserve(skiatest::Reporter* reporter) { // Test that our allocated space stays >= to the reserve count until the array is filled to // the reserve count for (int reserveCount : {1, 2, 10, 100}) { // Test setting reserve in constructor. Array array1(reserveCount); test_array_reserve(reporter, &array1, reserveCount); // Test setting reserve after constructor. Array array2; array2.reserve_back(reserveCount); test_array_reserve(reporter, &array2, reserveCount); // Test increasing reserve after constructor. Array array3(reserveCount/2); array3.reserve_back(reserveCount); test_array_reserve(reporter, &array3, reserveCount); // Test setting reserve on non-empty array. Array array4; array4.push_back_n(reserveCount); array4.reserve_back(reserveCount); array4.pop_back_n(reserveCount); test_array_reserve(reporter, &array4, 2 * reserveCount); } } DEF_TEST(TArray, reporter) { // ints are POD types and can work with either MEM_MOVE=true or false. TestTSet_basic(reporter); TestTSet_basic(reporter); // TestClass has a vtable and can only work with MEM_MOVE=false. TestTSet_basic(reporter); test_swap(reporter); test_unnecessary_alloc(reporter); test_self_assignment(reporter); test_reserve>(reporter); test_reserve>(reporter); test_reserve>(reporter); test_reserve>(reporter); test_reserve>(reporter); test_reserve>(reporter); test_reserve>(reporter); test_reserve>(reporter); test_construction>(reporter); test_construction>(reporter); test_construction>(reporter); test_construction>(reporter); test_construction>(reporter); test_construction>(reporter); test_construction>(reporter); test_skstarray_compatibility, SkTArray>(reporter); test_skstarray_compatibility, SkTArray>(reporter); test_skstarray_compatibility, SkTArray>(reporter); test_skstarray_compatibility, SkSTArray<1, int>>(reporter); test_skstarray_compatibility, SkSTArray<5, char>>(reporter); test_skstarray_compatibility, SkSTArray<10, float>>(reporter); test_skstarray_compatibility, SkSTArray<1, uint8_t>>(reporter); test_skstarray_compatibility, SkSTArray<10, long>>(reporter); test_skstarray_compatibility, SkSTArray<4, double>>(reporter); test_skstarray_compatibility, SkSTArray<1, short>>(reporter); }