/* * Copyright (C) 2019 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/base/circular_queue.h" #include <random> #include "gtest/gtest.h" namespace perfetto { namespace base { namespace { TEST(CircularQueueTest, Int) { CircularQueue<int> queue(/*initial_capacity=*/1); ASSERT_EQ(queue.size(), 0u); queue.emplace_back(101); ASSERT_EQ(queue.size(), 1u); queue.emplace_back(102); queue.emplace_back(103); queue.emplace_back(104); ASSERT_EQ(queue.size(), 4u); auto it = queue.begin(); for (int i = 101; i <= 104; i++) { ASSERT_EQ(*it, i); it++; } ASSERT_EQ(it, queue.end()); queue.erase_front(1); ASSERT_EQ(queue.size(), 3u); ASSERT_EQ(*queue.begin(), 102); *(queue.begin() + 1) = 42; ASSERT_EQ(*(queue.end() - 2), 42); queue.erase_front(2); ASSERT_EQ(queue.size(), 1u); ASSERT_EQ(*queue.begin(), 104); queue.pop_front(); ASSERT_EQ(queue.size(), 0u); ASSERT_EQ(queue.begin(), queue.end()); const size_t kNumInts = 100000u; { std::minstd_rand0 rnd_engine(0); for (size_t i = 0; i < kNumInts; i++) { int n = static_cast<int>(rnd_engine()); queue.emplace_back(n); } } ASSERT_EQ(queue.size(), kNumInts); ASSERT_EQ(static_cast<size_t>(queue.end() - queue.begin()), kNumInts); { std::minstd_rand0 rnd_engine(0); it = queue.begin(); for (size_t i = 0; i < kNumInts; ++i, ++it) { ASSERT_LT(it, queue.end()); ASSERT_EQ(*it, static_cast<int>(rnd_engine())); } } { std::minstd_rand0 del_rnd(42); std::minstd_rand0 rnd_engine(0); while (!queue.empty()) { ASSERT_EQ(*queue.begin(), static_cast<int>(rnd_engine())); size_t num_del = (del_rnd() % 8) + 1; queue.erase_front(num_del + 1); // +1 because of the read 2 lines above. rnd_engine.discard(num_del); } } } TEST(CircularQueueTest, Sorting) { CircularQueue<uint64_t> queue; std::minstd_rand0 rnd_engine(0); for (int i = 0; i < 100000; i++) { queue.emplace_back(static_cast<uint64_t>(rnd_engine())); if ((i % 100) == 0) queue.erase_front(29); } ASSERT_FALSE(std::is_sorted(queue.begin(), queue.end())); std::sort(queue.begin(), queue.end()); ASSERT_TRUE(std::is_sorted(queue.begin(), queue.end())); } TEST(CircularQueueTest, MoveOperators) { CircularQueue<int> queue; queue.emplace_back(1); queue.emplace_back(2); { CircularQueue<int> moved(std::move(queue)); ASSERT_TRUE(queue.empty()); ASSERT_EQ(moved.size(), 2u); moved.emplace_back(3); moved.emplace_back(4); ASSERT_EQ(moved.size(), 4u); } queue.emplace_back(10); queue.emplace_back(11); queue.emplace_back(12); ASSERT_EQ(queue.size(), 3u); ASSERT_EQ(queue.front(), 10); ASSERT_EQ(queue.back(), 12); { CircularQueue<int> moved; moved.emplace_back(42); moved = std::move(queue); ASSERT_TRUE(queue.empty()); ASSERT_EQ(moved.size(), 3u); ASSERT_EQ(moved.size(), 3u); ASSERT_EQ(moved.front(), 10); ASSERT_EQ(moved.back(), 12); } } TEST(CircularQueueTest, Iterators) { for (size_t repeat = 1; repeat < 8; repeat++) { size_t capacity = 8 * (1 << repeat); CircularQueue<size_t> queue(capacity); for (size_t i = 0; i < capacity - 2; i++) queue.emplace_back(0u); queue.erase_front(queue.size()); ASSERT_TRUE(queue.empty()); ASSERT_EQ(queue.capacity(), capacity); // Now the queue is empty and the internal write iterator is abut to wrap. // Add a bit more than half-capacity and check that the queue didn't resize. for (size_t i = 0; i < capacity / 2 + 3; i++) queue.emplace_back(i); ASSERT_EQ(queue.capacity(), capacity); // Check that all iterators are consistent. auto begin = queue.begin(); auto end = queue.end(); auto mid = begin + std::distance(begin, end) / 2; ASSERT_TRUE(std::is_sorted(begin, end)); ASSERT_TRUE(begin < end); ASSERT_TRUE(begin <= begin); ASSERT_TRUE(begin >= begin); ASSERT_FALSE(begin < begin); ASSERT_FALSE(begin > begin); ASSERT_TRUE(begin + 1 > begin); ASSERT_TRUE(begin + 1 >= begin); ASSERT_FALSE(begin >= begin + 1); ASSERT_TRUE(begin <= begin); ASSERT_TRUE(begin <= begin + 1); ASSERT_TRUE(end > mid); ASSERT_TRUE(mid > begin); ASSERT_TRUE(std::is_sorted(begin, mid)); ASSERT_TRUE(std::is_sorted(mid, end)); } } TEST(CircularQueueTest, ObjectLifetime) { class Checker { public: struct Stats { int num_ctors = 0; int num_dtors = 0; int num_alive = 0; }; Checker(Stats* stats, int n) : stats_(stats), n_(n) { EXPECT_GE(n, 0); stats_->num_ctors++; stats_->num_alive++; ptr_ = reinterpret_cast<uintptr_t>(this); } ~Checker() { EXPECT_EQ(ptr_, reinterpret_cast<uintptr_t>(this)); if (n_ >= 0) stats_->num_alive--; n_ = -1; stats_->num_dtors++; } Checker(Checker&& other) noexcept { n_ = other.n_; other.n_ = -1; stats_ = other.stats_; ptr_ = reinterpret_cast<uintptr_t>(this); } Checker& operator=(Checker&& other) { new (this) Checker(std::move(other)); return *this; } Checker(const Checker&) = delete; Checker& operator=(const Checker&) = delete; Stats* stats_ = nullptr; uintptr_t ptr_ = 0; int n_ = -1; }; // Check that dtors are invoked on old elements when growing capacity. Checker::Stats stats; { CircularQueue<Checker> queue(/*initial_capacity=*/2); for (int i = 0; i < 2; i++) queue.emplace_back(&stats, i); ASSERT_EQ(stats.num_ctors, 2); ASSERT_EQ(stats.num_dtors, 0); // This further insertion will grow the queue, causing two std::move()s // for the previous elements and one emplace. queue.emplace_back(&stats, 2); ASSERT_EQ(stats.num_ctors, 3); // The two old elements that have std::move()d should be destroyed upon // expansion. ASSERT_EQ(stats.num_dtors, 2); } ASSERT_EQ(stats.num_dtors, 2 + 3); stats = Checker::Stats(); { CircularQueue<Checker> queue(/*initial_capacity=*/1); for (int i = 0; i < 5; i++) queue.emplace_back(&stats, i); ASSERT_EQ(stats.num_ctors, 5); Checker c5(&stats, 5); queue.emplace_back(std::move(c5)); ASSERT_EQ(stats.num_alive, 5 + 1); queue.erase_front(2); ASSERT_EQ(stats.num_alive, 5 + 1 - 2); for (int i = 0; i < 4; i++) queue.emplace_back(&stats, 10 + i); ASSERT_EQ(stats.num_alive, 5 + 1 - 2 + 4); } ASSERT_EQ(stats.num_ctors, 5 + 1 + 4); ASSERT_EQ(stats.num_alive, 0); stats = Checker::Stats(); { CircularQueue<Checker> q1(1); CircularQueue<Checker> q2(64); for (int i = 0; i < 100; i++) { q1.emplace_back(&stats, 1000 + i * 2); q2.emplace_back(&stats, 1001 + i * 2); } ASSERT_EQ(stats.num_alive, 200); for (int i = 0; i < 100; i += 2) { auto it1 = q1.begin() + i; auto it2 = q2.begin() + i; std::swap(*it1, *it2); } auto comparer = [](const Checker& lhs, const Checker& rhs) { return lhs.n_ < rhs.n_; }; ASSERT_TRUE(std::is_sorted(q1.begin(), q1.end(), comparer)); ASSERT_TRUE(std::is_sorted(q2.begin(), q2.end(), comparer)); ASSERT_EQ(stats.num_alive, 200); } } } // namespace } // namespace base } // namespace perfetto