| /* |
| * Copyright (C) 2016 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 "common/libs/utils/simulated_buffer.h" |
| #include <gtest/gtest.h> |
| |
| using cvd::time::MonotonicTimePoint; |
| using cvd::time::MonotonicTimePointFactory; |
| using cvd::time::Seconds; |
| using cvd::time::Milliseconds; |
| using cvd::time::Nanoseconds; |
| using cvd::time::kNanosecondsPerSecond; |
| |
| class MockTimepointFactory : public MonotonicTimePointFactory { |
| public: |
| virtual void FetchCurrentTime(MonotonicTimePoint* dest) const override { |
| *dest = system_time_; |
| } |
| |
| void SetTime(const MonotonicTimePoint& in) { |
| system_time_ = in; |
| } |
| |
| protected: |
| MonotonicTimePoint system_time_; |
| }; |
| |
| template <typename T> class MockSimulatedBuffer : public T { |
| public: |
| MockSimulatedBuffer( |
| int64_t sample_rate, |
| int64_t capacity, |
| MockTimepointFactory* factory) : |
| T(sample_rate, capacity, factory), |
| factory_(factory) { } |
| |
| void FetchCurrentTime(MonotonicTimePoint* dest) const { |
| return factory_->FetchCurrentTime(dest); |
| } |
| |
| void SleepUntilTime(const MonotonicTimePoint& tick) { |
| factory_->SetTime(tick); |
| } |
| |
| protected: |
| // Save a redundant pointer to avoid downcasting |
| MockTimepointFactory* factory_; |
| }; |
| |
| static const int64_t kItemRate = 48000; |
| static const int64_t kBufferCapacity = 4800; |
| |
| class SimulatedBufferTest : public ::testing::Test { |
| public: |
| MockTimepointFactory clock; |
| MockSimulatedBuffer<SimulatedBufferBase> buffer; |
| |
| SimulatedBufferTest() : buffer(kItemRate, kBufferCapacity, &clock) { } |
| }; |
| |
| TEST_F(SimulatedBufferTest, TimeMocking) { |
| // Ensure that the mocked clock starts at the epoch. |
| MonotonicTimePoint epoch_time; |
| MonotonicTimePoint actual_time; |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(epoch_time, actual_time); |
| |
| // Ensure that sleeping works |
| MonotonicTimePoint test_time = actual_time + Seconds(10); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| |
| // Try one more sleep to make sure that time moves forward |
| test_time += Seconds(5); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| } |
| |
| TEST_F(SimulatedBufferTest, ItemScaling) { |
| // Make certain that we start at item 0 |
| EXPECT_EQ(0, buffer.GetCurrentItemNum()); |
| |
| // Make certain that the expected number of items appear in 1 second |
| MonotonicTimePoint actual_time; |
| buffer.FetchCurrentTime(&actual_time); |
| MonotonicTimePoint test_time = actual_time + Seconds(1); |
| buffer.SleepUntilTime(test_time); |
| EXPECT_EQ(kItemRate, buffer.GetCurrentItemNum()); |
| |
| // Sleep an additional 10 seconds to make certain that the item numbers |
| // increment |
| test_time += Seconds(10); |
| buffer.SleepUntilTime(test_time); |
| EXPECT_EQ(11 * kItemRate, buffer.GetCurrentItemNum()); |
| |
| // Make certain that partial seconds work |
| test_time += Milliseconds(1500); |
| buffer.SleepUntilTime(test_time); |
| EXPECT_EQ(12.5 * kItemRate, buffer.GetCurrentItemNum()); |
| |
| // Make certain that we don't get new items when paused |
| buffer.SetPaused(true); |
| test_time += Seconds(10); |
| buffer.SleepUntilTime(test_time); |
| EXPECT_EQ(12.5 * kItemRate, buffer.GetCurrentItemNum()); |
| |
| // Make certain that we start getting items when pausing stops |
| buffer.SetPaused(false); |
| test_time += Milliseconds(500); |
| buffer.SleepUntilTime(test_time); |
| EXPECT_EQ(13 * kItemRate, buffer.GetCurrentItemNum()); |
| } |
| |
| TEST_F(SimulatedBufferTest, ItemSleeping) { |
| // See if sleeping on an time causes the right amount of time to pass |
| EXPECT_EQ(0, buffer.GetCurrentItemNum()); |
| MonotonicTimePoint base_time; |
| buffer.FetchCurrentTime(&base_time); |
| |
| // Wait for 1500ms worth of samples |
| buffer.SleepUntilItem(kItemRate * 1500 / 1000); |
| EXPECT_EQ(kItemRate * 1500 / 1000, buffer.GetCurrentItemNum()); |
| MonotonicTimePoint actual_time; |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(1500, Milliseconds(actual_time - base_time).count()); |
| |
| // Now wait again for more samples |
| buffer.SleepUntilItem(kItemRate * 2500 / 1000); |
| EXPECT_EQ(kItemRate * 2500 / 1000, buffer.GetCurrentItemNum()); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(2500, Milliseconds(actual_time - base_time).count()); |
| } |
| |
| class OutputBufferTest : public ::testing::Test { |
| public: |
| MockTimepointFactory clock; |
| MockSimulatedBuffer<SimulatedOutputBuffer> buffer; |
| |
| OutputBufferTest() : buffer(kItemRate, kBufferCapacity, &clock) { } |
| }; |
| |
| TEST_F(OutputBufferTest, NonBlockingQueueing) { |
| int64_t half_buffer = kBufferCapacity / 2; |
| EXPECT_EQ(0, buffer.GetCurrentItemNum()); |
| |
| // Filling half of the buffer should not block |
| MonotonicTimePoint test_time; |
| buffer.FetchCurrentTime(&test_time); |
| EXPECT_EQ(half_buffer, buffer.AddToOutputBuffer(half_buffer, false)); |
| MonotonicTimePoint actual_time; |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(half_buffer, buffer.GetOutputBufferSize()); |
| |
| // Filling all but one entry of the buffer should not block |
| EXPECT_EQ(half_buffer - 1, |
| buffer.AddToOutputBuffer(half_buffer - 1, false)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity - 1, buffer.GetOutputBufferSize()); |
| |
| // Filling the entire buffer should not block |
| EXPECT_EQ(1, buffer.AddToOutputBuffer(half_buffer, false)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(actual_time, test_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // The buffer should reject additional data but not block |
| EXPECT_EQ(0, buffer.AddToOutputBuffer(half_buffer, false)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // One quarter of the buffer should drain in the expected time |
| Nanoseconds quarter_drain_time( |
| kBufferCapacity / 4 * kNanosecondsPerSecond / kItemRate); |
| test_time += quarter_drain_time; |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(actual_time, test_time); |
| EXPECT_EQ(kBufferCapacity * 3 / 4, buffer.GetOutputBufferSize()); |
| |
| // The buffer should now accept new data without blocking |
| EXPECT_EQ(kBufferCapacity / 4, |
| buffer.AddToOutputBuffer(half_buffer, false)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // Now that the buffer is full it should reject additional data but |
| // not block |
| EXPECT_EQ(0, buffer.AddToOutputBuffer(half_buffer, false)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // Wait for 3/4 of the buffer to drain |
| test_time += Nanoseconds(3 * quarter_drain_time.count()); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity / 4, buffer.GetOutputBufferSize()); |
| |
| // The entire buffer should drain on schedule |
| test_time += Nanoseconds(quarter_drain_time.count() - 1); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(1, buffer.GetOutputBufferSize()); |
| test_time += Nanoseconds(1); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(0, buffer.GetOutputBufferSize()); |
| |
| // It should be possible to fill the buffer in a single shot |
| EXPECT_EQ(kBufferCapacity, |
| buffer.AddToOutputBuffer(kBufferCapacity, false)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // The buffer shouldn't accept additional data but shouldn't block |
| EXPECT_EQ(0, buffer.AddToOutputBuffer(1, false)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // The buffer should underflow sanely |
| test_time += Nanoseconds(6 * quarter_drain_time.count()); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(0, buffer.GetOutputBufferSize()); |
| |
| // The underflow shouldn't increase the buffer's capacity |
| EXPECT_EQ(kBufferCapacity, |
| buffer.AddToOutputBuffer(kBufferCapacity + 1, false)); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| } |
| |
| TEST_F(OutputBufferTest, BlockingQueueing) { |
| int64_t half_buffer = kBufferCapacity / 2; |
| |
| // Check the initial setup |
| EXPECT_EQ(0, buffer.GetCurrentItemNum()); |
| MonotonicTimePoint test_time; |
| buffer.FetchCurrentTime(&test_time); |
| |
| // Filling half the buffer works without blocking |
| EXPECT_EQ(half_buffer, buffer.AddToOutputBuffer(half_buffer, true)); |
| MonotonicTimePoint actual_time; |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(half_buffer, buffer.GetOutputBufferSize()); |
| |
| // Filling all but one entry of the buffer also works without blocking |
| EXPECT_EQ(half_buffer - 1, |
| buffer.AddToOutputBuffer(half_buffer - 1, true)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity - 1, buffer.GetOutputBufferSize()); |
| |
| // Putting the last sample into the buffer doesn't block |
| EXPECT_EQ(1, buffer.AddToOutputBuffer(1, true)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // Putting more data into the buffer causes blocking |
| EXPECT_EQ(half_buffer, buffer.AddToOutputBuffer(half_buffer, true)); |
| Nanoseconds half_drain_time( |
| ((kBufferCapacity / 2) * kNanosecondsPerSecond + kItemRate - 1) / |
| kItemRate); |
| Nanoseconds quarter_drain_time(half_drain_time.count() / 2); |
| test_time += half_drain_time; |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // The buffer drains as expected |
| test_time += quarter_drain_time; |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity * 3 / 4, buffer.GetOutputBufferSize()); |
| |
| // Overfilling the drained buffer also causes blocking |
| EXPECT_EQ(half_buffer, buffer.AddToOutputBuffer(half_buffer, true)); |
| test_time += quarter_drain_time; |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // The buffer drains on schedule |
| test_time += Nanoseconds(half_drain_time.count() * 2 - 1); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(1, buffer.GetOutputBufferSize()); |
| test_time += Nanoseconds(1); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(0, buffer.GetOutputBufferSize()); |
| |
| // It's possible to fill the entire output buffer in 1 shot without blocking |
| EXPECT_EQ(kBufferCapacity, |
| buffer.AddToOutputBuffer(kBufferCapacity, true)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| |
| // Adding a single extra sample causes some blocking |
| EXPECT_EQ(1, buffer.AddToOutputBuffer(1, true)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_LT(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity, buffer.GetOutputBufferSize()); |
| } |
| |
| class InputBufferTest : public ::testing::Test { |
| public: |
| MockTimepointFactory clock; |
| MockSimulatedBuffer<SimulatedInputBuffer> buffer; |
| |
| InputBufferTest() : buffer(kItemRate, kBufferCapacity, &clock) { } |
| }; |
| |
| TEST_F(InputBufferTest, NonBlockingInput) { |
| Nanoseconds quarter_fill_time(kBufferCapacity / 4 * kNanosecondsPerSecond / |
| kItemRate); |
| // Verify that the buffer starts empty |
| EXPECT_EQ(0, buffer.GetCurrentItemNum()); |
| MonotonicTimePoint actual_time; |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(0, buffer.RemoveFromInputBuffer(kBufferCapacity, false)); |
| EXPECT_EQ(0, buffer.GetLostInputItems()); |
| |
| // Wait for 1/4 of the buffer to fill |
| MonotonicTimePoint test_time = actual_time + quarter_fill_time; |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(0, buffer.GetLostInputItems()); |
| |
| // Verify that we can read the samples in two groups |
| EXPECT_EQ(kBufferCapacity / 8, |
| buffer.RemoveFromInputBuffer(kBufferCapacity / 8, false)); |
| EXPECT_EQ(kBufferCapacity / 8, |
| buffer.RemoveFromInputBuffer(kBufferCapacity, false)); |
| |
| // Verify that there are no samples left and that we did not block |
| EXPECT_EQ(0, buffer.RemoveFromInputBuffer(kBufferCapacity, false)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| |
| // Verify that the buffer fills on schedule |
| test_time += Nanoseconds(4 * quarter_fill_time.count() - 1); |
| buffer.SleepUntilTime(test_time); |
| EXPECT_EQ(kBufferCapacity - 1, |
| buffer.RemoveFromInputBuffer(kBufferCapacity, false)); |
| test_time += Nanoseconds(1); |
| buffer.SleepUntilTime(test_time); |
| EXPECT_EQ(1, buffer.RemoveFromInputBuffer(kBufferCapacity, false)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(0, buffer.GetLostInputItems()); |
| |
| // Verify that the buffer overflows as expected |
| test_time += Nanoseconds(5 * quarter_fill_time.count()); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity / 4, buffer.GetLostInputItems()); |
| EXPECT_EQ(0, buffer.GetLostInputItems()); |
| |
| EXPECT_EQ(kBufferCapacity, |
| buffer.RemoveFromInputBuffer(2 * kBufferCapacity, false)); |
| EXPECT_EQ(0, buffer.RemoveFromInputBuffer(kBufferCapacity, false)); |
| } |
| |
| TEST_F(InputBufferTest, BlockingInput) { |
| Nanoseconds quarter_fill_time(kBufferCapacity / 4 * kNanosecondsPerSecond / |
| kItemRate); |
| // Verify that the buffer starts empty |
| EXPECT_EQ(0, buffer.GetCurrentItemNum()); |
| MonotonicTimePoint actual_time; |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(0, buffer.GetLostInputItems()); |
| |
| // Wait for 1/4 of the buffer to fill |
| MonotonicTimePoint test_time = actual_time + quarter_fill_time; |
| EXPECT_EQ(kBufferCapacity / 4, |
| buffer.RemoveFromInputBuffer(kBufferCapacity / 4, true)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(0, buffer.GetLostInputItems()); |
| |
| // Verify that the buffer fills on schedule |
| test_time += Nanoseconds(4 * quarter_fill_time.count()); |
| EXPECT_EQ(kBufferCapacity, |
| buffer.RemoveFromInputBuffer(kBufferCapacity, true)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(0, buffer.GetLostInputItems()); |
| |
| // Verify that the buffer overflows as expected |
| test_time += Nanoseconds(5 * quarter_fill_time.count()); |
| buffer.SleepUntilTime(test_time); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| EXPECT_EQ(kBufferCapacity / 4, buffer.GetLostInputItems()); |
| EXPECT_EQ(0, buffer.GetLostInputItems()); |
| EXPECT_EQ(kBufferCapacity, |
| buffer.RemoveFromInputBuffer(kBufferCapacity, true)); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| |
| // Verify that reads bigger than the buffer work as expected |
| test_time += Nanoseconds(8 * quarter_fill_time.count()); |
| EXPECT_EQ(kBufferCapacity * 2, |
| buffer.RemoveFromInputBuffer(kBufferCapacity * 2, true)); |
| EXPECT_EQ(0, buffer.GetLostInputItems()); |
| buffer.FetchCurrentTime(&actual_time); |
| EXPECT_EQ(test_time, actual_time); |
| } |