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