blob: c3fbe49452b40d94462d3b65d9e00b8108cd02da [file] [log] [blame]
/*
* 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);
}