// Copyright (c) 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "quic/core/quic_interval_deque.h" #include #include #include "quic/core/quic_interval.h" #include "quic/platform/api/quic_expect_bug.h" #include "quic/platform/api/quic_test.h" #include "quic/test_tools/quic_interval_deque_peer.h" #include "quic/test_tools/quic_test_utils.h" namespace quic { namespace test { namespace { const int32_t kSize = 100; const std::size_t kIntervalStep = 10; } // namespace struct TestIntervalItem { int32_t val; std::size_t interval_start, interval_end; QuicInterval interval() const { return QuicInterval(interval_start, interval_end); } TestIntervalItem(int32_t val, std::size_t interval_start, std::size_t interval_end) : val(val), interval_start(interval_start), interval_end(interval_end) {} }; using QID = QuicIntervalDeque; class QuicIntervalDequeTest : public QuicTest { public: QuicIntervalDequeTest() { // Add items with intervals of |kIntervalStep| size. for (int32_t i = 0; i < kSize; ++i) { const std::size_t interval_begin = kIntervalStep * i; const std::size_t interval_end = interval_begin + kIntervalStep; qid_.PushBack(TestIntervalItem(i, interval_begin, interval_end)); } } QID qid_; }; // The goal of this test is to show insertion/push_back, iteration, and and // deletion/pop_front from the container. TEST_F(QuicIntervalDequeTest, InsertRemoveSize) { QID qid; EXPECT_EQ(qid.Size(), std::size_t(0)); qid.PushBack(TestIntervalItem(0, 0, 10)); EXPECT_EQ(qid.Size(), std::size_t(1)); qid.PushBack(TestIntervalItem(1, 10, 20)); EXPECT_EQ(qid.Size(), std::size_t(2)); qid.PushBack(TestIntervalItem(2, 20, 30)); EXPECT_EQ(qid.Size(), std::size_t(3)); qid.PushBack(TestIntervalItem(3, 30, 40)); EXPECT_EQ(qid.Size(), std::size_t(4)); // Advance the index all the way... int32_t i = 0; for (auto it = qid.DataAt(0); it != qid.DataEnd(); ++it, ++i) { const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid); EXPECT_EQ(index, i); EXPECT_EQ(it->val, i); } const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid); EXPECT_EQ(index, -1); qid.PopFront(); EXPECT_EQ(qid.Size(), std::size_t(3)); qid.PopFront(); EXPECT_EQ(qid.Size(), std::size_t(2)); qid.PopFront(); EXPECT_EQ(qid.Size(), std::size_t(1)); qid.PopFront(); EXPECT_EQ(qid.Size(), std::size_t(0)); EXPECT_QUIC_BUG(qid.PopFront(), "Trying to pop from an empty container."); } // The goal of this test is to push data into the container at specific // intervals and show how the |DataAt| method can move the |cached_index| as the // iterator moves through the data. TEST_F(QuicIntervalDequeTest, InsertIterateWhole) { // The write index should point to the beginning of the container. const int32_t cached_index = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(cached_index, 0); auto it = qid_.DataBegin(); auto end = qid_.DataEnd(); for (int32_t i = 0; i < kSize; ++i, ++it) { EXPECT_EQ(it->val, i); const std::size_t current_iteraval_begin = i * kIntervalStep; // The |DataAt| method should find the correct interval. auto lookup = qid_.DataAt(current_iteraval_begin); EXPECT_EQ(i, lookup->val); // Make sure the index hasn't changed just from using |DataAt| const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index_before, i); // This increment should move the index forward. lookup++; // Check that the index has changed. const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_); const int32_t after_i = (i + 1) == kSize ? -1 : (i + 1); EXPECT_EQ(index_after, after_i); EXPECT_NE(it, end); } } // The goal of this test is to push data into the container at specific // intervals and show how the |DataAt| method can move the |cached_index| using // the off-by-one logic. TEST_F(QuicIntervalDequeTest, OffByOne) { // The write index should point to the beginning of the container. const int32_t cached_index = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(cached_index, 0); auto it = qid_.DataBegin(); auto end = qid_.DataEnd(); for (int32_t i = 0; i < kSize - 1; ++i, ++it) { EXPECT_EQ(it->val, i); const int32_t off_by_one_i = i + 1; const std::size_t current_iteraval_begin = off_by_one_i * kIntervalStep; // Make sure the index has changed just from using |DataAt| const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index_before, i); // The |DataAt| method should find the correct interval. auto lookup = qid_.DataAt(current_iteraval_begin); EXPECT_EQ(off_by_one_i, lookup->val); // Check that the index has changed. const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_); const int32_t after_i = off_by_one_i == kSize ? -1 : off_by_one_i; EXPECT_EQ(index_after, after_i); EXPECT_NE(it, end); } } // The goal of this test is to push data into the container at specific // intervals and show modify the structure with a live iterator. TEST_F(QuicIntervalDequeTest, IteratorInvalidation) { // The write index should point to the beginning of the container. const int32_t cached_index = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(cached_index, 0); const std::size_t iteraval_begin = (kSize - 1) * kIntervalStep; auto lookup = qid_.DataAt(iteraval_begin); EXPECT_EQ((*lookup).val, (kSize - 1)); qid_.PopFront(); EXPECT_QUIC_BUG(lookup++, "Iterator out of bounds."); auto lookup_end = qid_.DataAt(iteraval_begin + kIntervalStep); EXPECT_EQ(lookup_end, qid_.DataEnd()); } // The goal of this test is the same as |InsertIterateWhole| but to // skip certain intervals and show the |cached_index| is updated properly. TEST_F(QuicIntervalDequeTest, InsertIterateSkip) { // The write index should point to the beginning of the container. const int32_t cached_index = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(cached_index, 0); const std::size_t step = 4; for (int32_t i = 0; i < kSize; i += 4) { if (i != 0) { const int32_t before_i = (i - (step - 1)); EXPECT_EQ(QuicIntervalDequePeer::GetCachedIndex(&qid_), before_i); } const std::size_t current_iteraval_begin = i * kIntervalStep; // The |DataAt| method should find the correct interval. auto lookup = qid_.DataAt(current_iteraval_begin); EXPECT_EQ(i, lookup->val); // Make sure the index _has_ changed just from using |DataAt| since we're // skipping data. const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index_before, i); // This increment should move the index forward. lookup++; // Check that the index has changed. const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_); const int32_t after_i = (i + 1) == kSize ? -1 : (i + 1); EXPECT_EQ(index_after, after_i); } } // The goal of this test is the same as |InsertIterateWhole| but it has // |PopFront| calls interleaved to show the |cached_index| updates correctly. TEST_F(QuicIntervalDequeTest, InsertDeleteIterate) { // The write index should point to the beginning of the container. const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index, 0); std::size_t limit = 0; for (int32_t i = 0; limit < qid_.Size(); ++i, ++limit) { // Always point to the beginning of the container. auto it = qid_.DataBegin(); EXPECT_EQ(it->val, i); // Get an iterator. const std::size_t current_iteraval_begin = i * kIntervalStep; auto lookup = qid_.DataAt(current_iteraval_begin); const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_); // The index should always point to 0. EXPECT_EQ(index_before, 0); // This iterator increment should effect the index. lookup++; const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index_after, 1); // Decrement the |temp_size| and pop from the front. qid_.PopFront(); // Show the index has been updated to point to 0 again (from 1). const int32_t index_after_pop = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index_after_pop, 0); } } // The goal of this test is to move the index to the end and then add more data // to show it can be reset to a valid index. TEST_F(QuicIntervalDequeTest, InsertIterateInsert) { // The write index should point to the beginning of the container. const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index, 0); int32_t iterated_elements = 0; for (int32_t i = 0; i < kSize; ++i, ++iterated_elements) { // Get an iterator. const std::size_t current_iteraval_begin = i * kIntervalStep; auto lookup = qid_.DataAt(current_iteraval_begin); const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_); // The index should always point to i. EXPECT_EQ(index_before, i); // This iterator increment should effect the index. lookup++; // Show the index has been updated to point to i + 1 or -1 if at the end. const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_); const int32_t after_i = (i + 1) == kSize ? -1 : (i + 1); EXPECT_EQ(index_after, after_i); } const int32_t invalid_index = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(invalid_index, -1); // Add more data to the container, making the index valid. const std::size_t offset = qid_.Size(); for (int32_t i = 0; i < kSize; ++i) { const std::size_t interval_begin = offset + (kIntervalStep * i); const std::size_t interval_end = offset + interval_begin + kIntervalStep; qid_.PushBack(TestIntervalItem(i + offset, interval_begin, interval_end)); const int32_t index_current = QuicIntervalDequePeer::GetCachedIndex(&qid_); // Index should now be valid and equal to the size of the container before // adding more items to it. EXPECT_EQ(index_current, iterated_elements); } // Show the index is still valid and hasn't changed since the first iteration // of the loop. const int32_t index_after_add = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index_after_add, iterated_elements); // Iterate over all the data in the container and eventually reset the index // as we did before. for (int32_t i = 0; i < kSize; ++i, ++iterated_elements) { const std::size_t interval_begin = offset + (kIntervalStep * i); const int32_t index_current = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index_current, iterated_elements); auto lookup = qid_.DataAt(interval_begin); const int32_t expected_value = i + offset; EXPECT_EQ(lookup->val, expected_value); lookup++; const int32_t after_inc = (iterated_elements + 1) == (kSize * 2) ? -1 : (iterated_elements + 1); const int32_t after_index = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(after_index, after_inc); } // Show the index is now invalid. const int32_t invalid_index_again = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(invalid_index_again, -1); } // The goal of this test is to push data into the container at specific // intervals and show how the |DataAt| can iterate over already scanned data. TEST_F(QuicIntervalDequeTest, RescanData) { // The write index should point to the beginning of the container. const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index, 0); auto it = qid_.DataBegin(); auto end = qid_.DataEnd(); for (int32_t i = 0; i < kSize - 1; ++i, ++it) { EXPECT_EQ(it->val, i); const std::size_t current_iteraval_begin = i * kIntervalStep; // The |DataAt| method should find the correct interval. auto lookup = qid_.DataAt(current_iteraval_begin); EXPECT_EQ(i, lookup->val); // Make sure the index has changed just from using |DataAt| const int32_t cached_index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(cached_index_before, i); // Ensure the real index has changed just from using |DataAt| and the // off-by-one logic const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_); const int32_t before_i = i; EXPECT_EQ(index_before, before_i); // This increment should move the cached index forward. lookup++; // Check that the cached index has moved foward. const int32_t cached_index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_); const int32_t after_i = (i + 1); EXPECT_EQ(cached_index_after, after_i); EXPECT_NE(it, end); } // Iterate over items which have been consumed before. int32_t expected_index = static_cast(kSize - 1); for (int32_t i = 0; i < kSize - 1; ++i) { const std::size_t current_iteraval_begin = i * kIntervalStep; // The |DataAt| method should find the correct interval. auto lookup = qid_.DataAt(current_iteraval_begin); EXPECT_EQ(i, lookup->val); // This increment shouldn't move the index forward as the index is currently // ahead. lookup++; // Check that the index hasn't moved foward. const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_); EXPECT_EQ(index_after, expected_index); EXPECT_NE(it, end); } } // The goal of this test is to show that popping from an empty container is a // bug. TEST_F(QuicIntervalDequeTest, PopEmpty) { QID qid; EXPECT_TRUE(qid.Empty()); EXPECT_QUIC_BUG(qid.PopFront(), "Trying to pop from an empty container."); } // The goal of this test is to show that adding a zero-sized interval is a bug. TEST_F(QuicIntervalDequeTest, ZeroSizedInterval) { QID qid; EXPECT_QUIC_BUG(qid.PushBack(TestIntervalItem(0, 0, 0)), "Trying to save empty interval to ."); } // The goal of this test is to show that an iterator to an empty container // returns |DataEnd|. TEST_F(QuicIntervalDequeTest, IteratorEmpty) { QID qid; auto it = qid.DataAt(0); EXPECT_EQ(it, qid.DataEnd()); } } // namespace test } // namespace quic