|  | /* | 
|  | * Copyright (C) 2021 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | #include "perfetto/ext/base/small_vector.h" | 
|  |  | 
|  | #include <tuple> | 
|  | #include <utility> | 
|  |  | 
|  | #include "test/gtest_and_gmock.h" | 
|  |  | 
|  | namespace perfetto { | 
|  | namespace base { | 
|  | namespace { | 
|  |  | 
|  | int g_instances = 0; | 
|  |  | 
|  | struct Obj { | 
|  | explicit Obj(size_t v = 0) : value(v) { | 
|  | EXPECT_FALSE(constructed); | 
|  | constructed = true; | 
|  | g_instances++; | 
|  | } | 
|  |  | 
|  | ~Obj() { | 
|  | EXPECT_TRUE(constructed); | 
|  | g_instances--; | 
|  | } | 
|  |  | 
|  | // Move operators. | 
|  | Obj(Obj&& other) noexcept { | 
|  | g_instances++; | 
|  | constructed = true; | 
|  | moved_into = true; | 
|  | value = other.value; | 
|  | other.moved_from = true; | 
|  | other.value = 0xffffffff - value; | 
|  | } | 
|  |  | 
|  | Obj& operator=(Obj&& other) noexcept { | 
|  | this->~Obj(); | 
|  | new (this) Obj(std::move(other)); | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | // Copy operators. | 
|  | Obj(const Obj& other) { | 
|  | other.copied_from = true; | 
|  | g_instances++; | 
|  | constructed = true; | 
|  | copied_into = true; | 
|  | value = other.value; | 
|  | } | 
|  |  | 
|  | Obj& operator=(const Obj& other) { | 
|  | this->~Obj(); | 
|  | new (this) Obj(other); | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | uintptr_t addr = reinterpret_cast<uintptr_t>(this); | 
|  | bool constructed = false; | 
|  | size_t value = 0; | 
|  | bool moved_from = false; | 
|  | mutable bool copied_from = false; | 
|  | bool moved_into = false; | 
|  | bool copied_into = false; | 
|  | }; | 
|  |  | 
|  | TEST(SmallVectorTest, StaySmall) { | 
|  | SmallVector<Obj, 8> v; | 
|  | EXPECT_EQ(g_instances, 0); | 
|  | EXPECT_EQ(v.size(), 0u); | 
|  | EXPECT_TRUE(v.empty()); | 
|  | EXPECT_EQ(v.begin(), v.end()); | 
|  |  | 
|  | for (size_t i = 1; i <= 8; i++) { | 
|  | v.emplace_back(i); | 
|  | EXPECT_EQ(g_instances, static_cast<int>(i)); | 
|  | EXPECT_FALSE(v.empty()); | 
|  | EXPECT_EQ(v.end(), v.begin() + i); | 
|  | EXPECT_EQ(v.back().value, i); | 
|  | EXPECT_EQ(v[static_cast<size_t>(i - 1)].value, i); | 
|  | EXPECT_EQ(v[static_cast<size_t>(i - 1)].value, i); | 
|  | } | 
|  |  | 
|  | for (size_t i = 1; i <= 3; i++) { | 
|  | v.pop_back(); | 
|  | EXPECT_EQ(g_instances, 8 - static_cast<int>(i)); | 
|  | } | 
|  |  | 
|  | v.clear(); | 
|  | EXPECT_EQ(g_instances, 0); | 
|  | } | 
|  |  | 
|  | TEST(SmallVectorTest, GrowOnHeap) { | 
|  | SmallVector<Obj, 4> v; | 
|  | for (size_t i = 0; i < 10; i++) { | 
|  | v.emplace_back(i); | 
|  | EXPECT_EQ(g_instances, static_cast<int>(i + 1)); | 
|  | EXPECT_FALSE(v.empty()); | 
|  | EXPECT_EQ(v.end(), v.begin() + i + 1); | 
|  | EXPECT_EQ(v[i].value, i); | 
|  | } | 
|  |  | 
|  | // Do a second pass and check that the initial elements aren't corrupt. | 
|  | for (size_t i = 0; i < 10; i++) { | 
|  | EXPECT_EQ(v[i].value, i); | 
|  | EXPECT_TRUE(v[i].constructed); | 
|  | } | 
|  |  | 
|  | // The first 4 elements must have been moved into because of the heap growth. | 
|  | for (size_t i = 0; i < 4; i++) | 
|  | EXPECT_TRUE(v[i].moved_into); | 
|  | EXPECT_FALSE(v.back().moved_into); | 
|  | } | 
|  |  | 
|  | class SmallVectorTestP : public testing::TestWithParam<size_t> {}; | 
|  |  | 
|  | TEST_P(SmallVectorTestP, MoveOperators) { | 
|  | size_t num_elements = GetParam(); | 
|  | static constexpr size_t kInlineCapacity = 4; | 
|  | SmallVector<Obj, kInlineCapacity> v1; | 
|  | for (size_t i = 0; i < num_elements; i++) | 
|  | v1.emplace_back(i); | 
|  |  | 
|  | SmallVector<Obj, kInlineCapacity> v2(std::move(v1)); | 
|  | EXPECT_TRUE(v1.empty()); | 
|  | EXPECT_EQ(v2.size(), num_elements); | 
|  |  | 
|  | // Check that v2 (the moved into vector) is consistent. | 
|  | for (size_t i = 0; i < num_elements; i++) { | 
|  | EXPECT_EQ(v2[i].value, i); | 
|  | EXPECT_TRUE(v2[i].constructed); | 
|  | if (num_elements <= kInlineCapacity) { | 
|  | EXPECT_TRUE(v2[i].moved_into); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check that v1 (the moved-from object) is still usable. | 
|  | EXPECT_EQ(v1.size(), 0u); | 
|  |  | 
|  | for (size_t i = 0; i < num_elements; i++) { | 
|  | v1.emplace_back(1000 + i); | 
|  | EXPECT_EQ(v1.size(), i + 1); | 
|  | } | 
|  |  | 
|  | EXPECT_NE(v1.data(), v2.data()); | 
|  |  | 
|  | for (size_t i = 0; i < num_elements; i++) { | 
|  | EXPECT_EQ(v1[i].value, 1000 + i); | 
|  | EXPECT_EQ(v2[i].value, i); | 
|  | EXPECT_TRUE(v1[i].constructed); | 
|  | EXPECT_FALSE(v1[i].moved_from); | 
|  | } | 
|  |  | 
|  | // Now swap again using the move-assignment. | 
|  |  | 
|  | v1 = std::move(v2); | 
|  | EXPECT_EQ(v1.size(), num_elements); | 
|  | EXPECT_TRUE(v2.empty()); | 
|  | for (size_t i = 0; i < num_elements; i++) { | 
|  | EXPECT_EQ(v1[i].value, i); | 
|  | EXPECT_TRUE(v1[i].constructed); | 
|  | } | 
|  |  | 
|  | { auto destroy = std::move(v1); } | 
|  |  | 
|  | EXPECT_EQ(g_instances, 0); | 
|  | } | 
|  |  | 
|  | TEST_P(SmallVectorTestP, CopyOperators) { | 
|  | size_t num_elements = GetParam(); | 
|  | static constexpr size_t kInlineCapacity = 4; | 
|  | SmallVector<Obj, kInlineCapacity> v1; | 
|  | for (size_t i = 0; i < num_elements; i++) | 
|  | v1.emplace_back(i); | 
|  |  | 
|  | SmallVector<Obj, kInlineCapacity> v2(v1); | 
|  | EXPECT_EQ(v1.size(), num_elements); | 
|  | EXPECT_EQ(v2.size(), num_elements); | 
|  | EXPECT_EQ(g_instances, static_cast<int>(num_elements * 2)); | 
|  |  | 
|  | for (size_t i = 0; i < num_elements; i++) { | 
|  | EXPECT_EQ(v1[i].value, i); | 
|  | EXPECT_TRUE(v1[i].copied_from); | 
|  | EXPECT_EQ(v2[i].value, i); | 
|  | EXPECT_TRUE(v2[i].copied_into); | 
|  | } | 
|  |  | 
|  | // Now edit v2. | 
|  | for (size_t i = 0; i < num_elements; i++) | 
|  | v2[i].value = i + 100; | 
|  | EXPECT_EQ(g_instances, static_cast<int>(num_elements * 2)); | 
|  |  | 
|  | // Append some extra elements. | 
|  | for (size_t i = 0; i < num_elements; i++) | 
|  | v2.emplace_back(i + 200); | 
|  | EXPECT_EQ(g_instances, static_cast<int>(num_elements * 3)); | 
|  |  | 
|  | for (size_t i = 0; i < num_elements * 2; i++) { | 
|  | if (i < num_elements) { | 
|  | EXPECT_EQ(v1[i].value, i); | 
|  | EXPECT_EQ(v2[i].value, 100 + i); | 
|  | } else { | 
|  | EXPECT_EQ(v2[i].value, 200 + i - num_elements); | 
|  | } | 
|  | } | 
|  |  | 
|  | v2.clear(); | 
|  | EXPECT_EQ(g_instances, static_cast<int>(num_elements)); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(SmallVectorTest, | 
|  | SmallVectorTestP, | 
|  | testing::Values(2, 4, 7, 512)); | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace base | 
|  | }  // namespace perfetto |