Index: webrtc/base/numerics/cyclic_buffer_unittest.cc |
diff --git a/webrtc/base/numerics/cyclic_buffer_unittest.cc b/webrtc/base/numerics/cyclic_buffer_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..064d2797443f022ae7565f97a31b0acb60e55eb7 |
--- /dev/null |
+++ b/webrtc/base/numerics/cyclic_buffer_unittest.cc |
@@ -0,0 +1,438 @@ |
+/* |
+ * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+#include <deque> |
+#include <list> |
+#include <string> |
+ |
+#include "webrtc/base/numerics/cyclic_buffer.h" |
+#include "webrtc/base/timeutils.h" |
+#include "webrtc/base/random.h" |
+#include "webrtc/test/gtest.h" |
+ |
+namespace { |
+ |
+struct Usage { |
+ size_t default_constructor = 0; |
+ size_t cast_constructor = 0; |
+ size_t copy_constructor = 0; |
+ size_t move_constructor = 0; |
+ size_t move_cast_constructor = 0; |
+ size_t destructors = 0; |
+ size_t copy_assignment = 0; |
+ size_t cast_assignment = 0; |
+ size_t move_assignment = 0; |
+ size_t move_cast_assignment = 0; |
+ size_t casts = 0; |
+}; |
+ |
+// void ResetMagicTypeUsage(Usage& usage) { |
+// usage.default_constructor = 0; |
+// usage.cast_constructor = 0; |
+// usage.copy_constructor = 0; |
+// usage.move_constructor = 0; |
+// usage.move_cast_constructor = 0; |
+// usage.destructors = 0; |
+// usage.copy_assignment = 0; |
+// usage.cast_assignment = 0; |
+// usage.move_assignment = 0; |
+// usage.move_cast_assignment = 0; |
+// usage.casts = 0; |
+// } |
+ |
+// void PrintMagicTypeUsage(const Usage& usage) { |
+// printf("%zu default constructor calls\n", usage.default_constructor); |
+// printf("%zu cast constructor calls\n", usage.cast_constructor); |
+// printf("%zu copy constructor calls\n", usage.copy_constructor); |
+// printf("%zu move cast constructor calls\n", usage.move_cast_constructor); |
+// printf("%zu move constructor calls\n", usage.move_constructor); |
+// printf("%zu destructor calls\n", usage.destructors); |
+// printf("%zu copy assignments calls\n", usage.copy_assignment); |
+// printf("%zu cast assignments calls\n", usage.cast_assignment); |
+// printf("%zu move assignments calls\n", usage.move_assignment); |
+// printf("%zu move cast assignments calls\n", usage.move_cast_assignment); |
+// printf("%zu casts\n", usage.casts); |
+// } |
+ |
+// Wrapper type to detect memory management problems, like using memcpy to copy |
+// data without running the constructor, using the data before it has been |
+// assigned, or using the data after it has been deleted. |
+template <typename T> |
+class MagicType { |
+ public: |
+ MagicType() : magic_(this), constructed_(true), assigned_(false) { |
+ usage.default_constructor++; |
+ } |
+ |
+ MagicType(const T& value) // we actually want implicit casts here |
+ : value_(value), magic_(this), constructed_(true), assigned_(true) { |
+ usage.cast_constructor++; |
+ } |
+ |
+ MagicType(T&& value) // we actually want implicit casts here |
+ : value_(std::move(value)), |
+ magic_(this), |
+ constructed_(true), |
+ assigned_(true) { |
+ usage.move_cast_constructor++; |
+ } |
+ |
+ MagicType(const MagicType<T>& m) |
+ : value_(m.value_), |
+ magic_(this), |
+ constructed_(m.constructed_), |
+ assigned_(m.assigned_) { |
+ RTC_DCHECK(m.magic_ == &m); |
+ RTC_DCHECK(m.constructed_); |
+ usage.copy_constructor++; |
+ } |
+ |
+ MagicType(MagicType<T>&& m) |
+ : value_(std::move(m.value_)), |
+ magic_(this), |
+ constructed_(m.constructed_), |
+ assigned_(m.assigned_) { |
+ RTC_DCHECK(m.magic_ == &m); |
+ RTC_DCHECK(m.constructed_); |
+ usage.move_constructor++; |
+ } |
+ |
+ ~MagicType() { |
+ RTC_DCHECK(magic_ == this); |
+ RTC_DCHECK(constructed_); |
+ magic_ = nullptr; |
+ constructed_ = false; |
+ assigned_ = false; |
+ usage.destructors++; |
+ } |
+ |
+ MagicType<T>& operator=(const T& value) { |
+ RTC_DCHECK(magic_ == this); |
+ RTC_DCHECK(constructed_); |
+ value_ = value; |
+ assigned_ = true; |
+ usage.cast_assignment++; |
+ return *this; |
+ } |
+ |
+ MagicType<T>& operator=(T&& value) { |
+ RTC_DCHECK(magic_ == this); |
+ RTC_DCHECK(constructed_); |
+ value_ = value; |
+ assigned_ = true; |
+ usage.move_cast_assignment++; |
+ return *this; |
+ } |
+ |
+ MagicType<T>& operator=(const MagicType<T>& m) { |
+ RTC_DCHECK(m.magic_ == &m); |
+ RTC_DCHECK(m.constructed_); |
+ RTC_DCHECK(magic_ == this); |
+ RTC_DCHECK(constructed_); |
+ value_ = m.value_; |
+ assigned_ = m.assigned_; |
+ usage.copy_assignment++; |
+ return *this; |
+ } |
+ |
+ MagicType<T>& operator=(MagicType<T>&& m) { |
+ RTC_DCHECK(m.magic_ == &m); |
+ RTC_DCHECK(m.constructed_); |
+ RTC_DCHECK(magic_ == this); |
+ RTC_DCHECK(constructed_); |
+ value_ = m.value_; |
+ assigned_ = m.assigned_; |
+ usage.move_assignment++; |
+ return *this; |
+ } |
+ |
+ operator T() const { |
+ RTC_DCHECK(magic_ == this); |
+ RTC_DCHECK(constructed_); |
+ RTC_DCHECK(assigned_); |
+ usage.casts++; |
+ return value_; |
+ } |
+ |
+ static Usage usage; |
+ |
+ private: |
+ T value_; |
+ MagicType<T>* magic_; |
+ bool constructed_; |
+ bool assigned_; |
+}; |
+ |
+template <typename T> |
+Usage MagicType<T>::usage; |
+} // namespace |
+ |
+namespace webrtc { |
+ |
+TEST(CyclicBufferTest, EmptyBuffer) { |
+ CyclicBuffer<int, GrowthPolicy::ASSERT> buffer(5); |
+ EXPECT_EQ(buffer.size(), 0u); |
+} |
+ |
+TEST(CyclicBufferTest, PushBack) { |
+ CyclicBuffer<int, GrowthPolicy::ASSERT> buffer(5); |
+ buffer.push_back(45); |
+ EXPECT_EQ(buffer.size(), 1u); |
+ EXPECT_EQ(buffer.back(), 45); |
+ EXPECT_EQ(buffer.front(), 45); |
+ buffer.push_back(98); |
+ EXPECT_EQ(buffer.size(), 2u); |
+ EXPECT_EQ(buffer.back(), 98); |
+ EXPECT_EQ(buffer.front(), 45); |
+ buffer.push_back(35); |
+ EXPECT_EQ(buffer.size(), 3u); |
+ EXPECT_EQ(buffer.back(), 35); |
+ EXPECT_EQ(buffer.front(), 45); |
+} |
+ |
+TEST(CyclicBufferTest, PopFront) { |
+ CyclicBuffer<int, GrowthPolicy::ASSERT> buffer(5); |
+ buffer.push_back(45); |
+ buffer.push_back(98); |
+ buffer.push_back(35); |
+ EXPECT_EQ(buffer.size(), 3u); |
+ EXPECT_EQ(buffer.back(), 35); |
+ EXPECT_EQ(buffer.front(), 45); |
+ buffer.pop_front(); |
+ EXPECT_EQ(buffer.size(), 2u); |
+ EXPECT_EQ(buffer.back(), 35); |
+ EXPECT_EQ(buffer.front(), 98); |
+ buffer.pop_front(); |
+ EXPECT_EQ(buffer.size(), 1u); |
+ EXPECT_EQ(buffer.back(), 35); |
+ EXPECT_EQ(buffer.front(), 35); |
+ buffer.pop_front(); |
+ EXPECT_EQ(buffer.size(), 0u); |
+} |
+ |
+TEST(CyclicBufferTest, PushFront) { |
+ CyclicBuffer<int, GrowthPolicy::ASSERT> buffer(5); |
+ buffer.push_front(45); |
+ EXPECT_EQ(buffer.size(), 1u); |
+ EXPECT_EQ(buffer.back(), 45); |
+ EXPECT_EQ(buffer.front(), 45); |
+ buffer.push_front(98); |
+ EXPECT_EQ(buffer.size(), 2u); |
+ EXPECT_EQ(buffer.back(), 45); |
+ EXPECT_EQ(buffer.front(), 98); |
+ buffer.push_front(35); |
+ EXPECT_EQ(buffer.size(), 3u); |
+ EXPECT_EQ(buffer.back(), 45); |
+ EXPECT_EQ(buffer.front(), 35); |
+} |
+ |
+TEST(CyclicBufferTest, PopBack) { |
+ CyclicBuffer<int, GrowthPolicy::ASSERT> buffer(5); |
+ buffer.push_front(45); |
+ buffer.push_front(98); |
+ buffer.push_front(35); |
+ EXPECT_EQ(buffer.size(), 3u); |
+ EXPECT_EQ(buffer.back(), 45); |
+ EXPECT_EQ(buffer.front(), 35); |
+ buffer.pop_back(); |
+ EXPECT_EQ(buffer.size(), 2u); |
+ EXPECT_EQ(buffer.back(), 98); |
+ EXPECT_EQ(buffer.front(), 35); |
+ buffer.pop_back(); |
+ EXPECT_EQ(buffer.size(), 1u); |
+ EXPECT_EQ(buffer.back(), 35); |
+ EXPECT_EQ(buffer.front(), 35); |
+ buffer.pop_back(); |
+ EXPECT_EQ(buffer.size(), 0u); |
+} |
+ |
+TEST(CyclicBufferTest, BufferWithCapacityOne) { |
+ CyclicBuffer<int, GrowthPolicy::ASSERT> buffer(1); |
+ // Insert and remove one element from a buffer with capacity 1. |
+ buffer.push_back(45); |
+ EXPECT_EQ(buffer.size(), 1u); |
+ EXPECT_EQ(buffer.back(), 45); |
+ EXPECT_EQ(buffer.front(), 45); |
+ buffer.pop_front(); |
+ EXPECT_EQ(buffer.size(), 0u); |
+ // Verify that we are still in a valid state by attempting a another |
+ // operation. |
+ buffer.push_back(21); |
+ EXPECT_EQ(buffer.size(), 1u); |
+ EXPECT_EQ(buffer.back(), 21); |
+ EXPECT_EQ(buffer.front(), 21); |
+ buffer.pop_front(); |
+ EXPECT_EQ(buffer.size(), 0u); |
+} |
+ |
+TEST(CyclicBufferTest, BufferMagicType) { |
+ CyclicBuffer<MagicType<int>, GrowthPolicy::ASSERT> buffer(3); |
+ buffer.push_back(45); |
+ EXPECT_EQ(buffer.size(), 1u); |
+ EXPECT_EQ(buffer.back(), 45); |
+ EXPECT_EQ(buffer.front(), 45); |
+ buffer.push_back(98); |
+ EXPECT_EQ(buffer.size(), 2u); |
+ EXPECT_EQ(buffer.back(), 98); |
+ EXPECT_EQ(buffer.front(), 45); |
+ buffer.push_back(35); |
+ EXPECT_EQ(buffer.size(), 3u); |
+ EXPECT_EQ(buffer.back(), 35); |
+ EXPECT_EQ(buffer.front(), 45); |
+ buffer.pop_front(); |
+ EXPECT_EQ(buffer.size(), 2u); |
+ EXPECT_EQ(buffer.back(), 35); |
+ EXPECT_EQ(buffer.front(), 98); |
+ buffer.pop_front(); |
+ EXPECT_EQ(buffer.size(), 1u); |
+ EXPECT_EQ(buffer.back(), 35); |
+ EXPECT_EQ(buffer.front(), 35); |
+ buffer.pop_front(); |
+ EXPECT_EQ(buffer.size(), 0u); |
+} |
+ |
+template <typename Buffer> |
+int64_t TestIntBufferPerformance(Buffer* buffer, |
+ size_t elements, |
+ size_t capacity) { |
+ int64_t start = rtc::TimeMicros(); |
+ for (size_t i = 0; i < elements; i++) { |
+ if (buffer->size() == capacity) |
+ buffer->pop_front(); |
+ buffer->push_back(i); |
+ } |
+ int64_t stop = rtc::TimeMicros(); |
+ return stop - start; |
+} |
+ |
+template <typename Buffer> |
+int64_t TestStringBufferPerformance(Buffer* buffer, |
+ size_t elements, |
+ size_t capacity) { |
+ int64_t start = rtc::TimeMicros(); |
+ for (size_t i = 0; i < elements; i++) { |
+ if (buffer->size() == capacity) |
+ buffer->pop_front(); |
+ buffer->push_back(std::to_string(i)); |
+ } |
+ int64_t stop = rtc::TimeMicros(); |
+ return stop - start; |
+} |
+ |
+TEST(CyclicBufferTest, DISABLED_PerformanceInt) { |
+ CyclicBuffer<int, GrowthPolicy::ASSERT> cyclic(1023u); |
+ std::deque<int> double_ended; |
+ // printf("Time used by std::list = %ld\n", TestListPerformance(1000000u, |
+ // 1023u)); |
+ |
+ int64_t deque_time = 0; |
+ int64_t cyclicbuffer_time = 0; |
+ for (int i = 0; i < 100; i++) { |
+ deque_time += TestIntBufferPerformance(&double_ended, 1000000u, 1023u); |
+ cyclicbuffer_time += TestIntBufferPerformance(&cyclic, 1000000u, 1023u); |
+ } |
+ printf("Time used by std::deque = %ld\n", deque_time / 100); |
+ printf("Time used by CyclicBuffer = %ld\n", cyclicbuffer_time / 100); |
+} |
+ |
+TEST(CyclicBufferTest, DISABLED_PerformanceDouble) { |
+ CyclicBuffer<double, GrowthPolicy::ASSERT> cyclic(1023u); |
+ std::deque<double> double_ended; |
+ |
+ int64_t deque_time = 0; |
+ int64_t cyclicbuffer_time = 0; |
+ for (int i = 0; i < 100; i++) { |
+ deque_time += TestIntBufferPerformance(&double_ended, 1000000u, 1023u); |
+ cyclicbuffer_time += TestIntBufferPerformance(&cyclic, 1000000u, 1023u); |
+ } |
+ printf("Time used by std::deque = %ld\n", deque_time / 100); |
+ printf("Time used by CyclicBuffer = %ld\n", cyclicbuffer_time / 100); |
+} |
+ |
+TEST(CyclicBufferTest, DISABLED_PerformanceString) { |
+ CyclicBuffer<std::string, GrowthPolicy::ASSERT> cyclic(1023u); |
+ std::deque<std::string> double_ended; |
+ |
+ int64_t deque_time = 0; |
+ int64_t cyclicbuffer_time = 0; |
+ for (int i = 0; i < 100; i++) { |
+ deque_time += TestStringBufferPerformance(&double_ended, 1000000u, 1023u); |
+ cyclicbuffer_time += TestStringBufferPerformance(&cyclic, 1000000u, 1023u); |
+ } |
+ printf("Time used by std::deque = %ld\n", deque_time / 100); |
+ printf("Time used by CyclicBuffer = %ld\n", cyclicbuffer_time / 100); |
+} |
+ |
+TEST(CyclicBufferTest, DISABLED_PerformanceMagicTypeString) { |
+ CyclicBuffer<MagicType<std::string>, GrowthPolicy::ASSERT> cyclic(1023u); |
+ std::deque<MagicType<std::string>> double_ended; |
+ |
+ int64_t deque_time = 0; |
+ int64_t cyclicbuffer_time = 0; |
+ for (int i = 0; i < 100; i++) { |
+ // ResetMagicTypeUsage(MagicType<std::string>::usage); |
+ deque_time += TestStringBufferPerformance(&double_ended, 1000000u, 1023u); |
+ // printf("std::deque performance\n"); |
+ // PrintMagicTypeUsage(MagicType<std::string>::usage); |
+ |
+ // ResetMagicTypeUsage(MagicType<std::string>::usage); |
+ cyclicbuffer_time += TestStringBufferPerformance(&cyclic, 1000000u, 1023u); |
+ // printf("CyclicBuffer performance\n"); |
+ // PrintMagicTypeUsage(MagicType<std::string>::usage); |
+ // printf("\n"); |
+ } |
+ printf("Time used by std::deque = %ld\n", deque_time / 100); |
+ printf("Time used by CyclicBuffer = %ld\n", cyclicbuffer_time / 100); |
+} |
+ |
+void BehavesAsDeque(size_t operations, size_t capacity, uint64_t seed) { |
+ Random random(seed); |
+ std::deque<uint32_t> q; |
+ CyclicBuffer<uint32_t, GrowthPolicy::ASSERT> cq(capacity); |
+ |
+ for (size_t i = 0; i < operations; i++) { |
+ bool insert = random.Rand<bool>(); |
+ bool front = random.Rand<bool>(); |
+ if ((insert && q.size() < capacity) || q.empty()) { |
+ uint32_t value = random.Rand<uint32_t>(); |
+ if (front) { |
+ q.push_front(value); |
+ cq.push_front(value); |
+ } else { |
+ q.push_back(value); |
+ cq.push_back(value); |
+ } |
+ } else { |
+ if (front) { |
+ q.pop_front(); |
+ cq.pop_front(); |
+ } else { |
+ q.pop_back(); |
+ cq.pop_back(); |
+ } |
+ } |
+ |
+ EXPECT_EQ(q.size(), cq.size()); |
+ if (!q.empty()) { |
+ EXPECT_EQ(q.front(), cq.front()); |
+ EXPECT_EQ(q.back(), cq.back()); |
+ } |
+ } |
+} |
+ |
+TEST(CyclicBufferTest, BehavesAsDeque) { |
+ BehavesAsDeque(1000000, 1023, 123454321); |
+} |
+ |
+TEST(CyclicBufferTest, BehavesAsDequeSmallBuffer) { |
+ BehavesAsDeque(1000000, 4, 123454321); |
+} |
+ |
+} // namespace webrtc |