| /* |
| * 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 |