blob: dfdd4ef0dbbb492c13424b96d5dbf2a13d45c46d [file] [log] [blame]
#include "libbroadcastring/broadcast_ring.h"
#include <stdlib.h>
#include <memory>
#include <thread> // NOLINT
#include <sys/mman.h>
#include <gtest/gtest.h>
namespace android {
namespace dvr {
namespace {
template <uint32_t N>
struct alignas(8) Aligned {
char v[N];
};
template <uint32_t N>
struct alignas(8) Sized {
Sized() { Clear(); }
explicit Sized(char c) { Fill(c); }
char v[sizeof(Aligned<N>)];
void Clear() { memset(v, 0, sizeof(v)); }
void Fill(char c) { memset(v, c, sizeof(v)); }
static Sized Pattern(uint8_t c) {
Sized sized;
for (size_t i = 0; i < sizeof(v); ++i) {
sized.v[i] = static_cast<char>(c + i);
}
return sized;
}
bool operator==(const Sized& right) const {
static_assert(sizeof(*this) == sizeof(v), "Size mismatch");
return !memcmp(v, right.v, sizeof(v));
}
template <typename SmallerSized>
SmallerSized Truncate() const {
SmallerSized val;
static_assert(sizeof(val.v) <= sizeof(v), "Cannot truncate to larger size");
memcpy(val.v, v, sizeof(val.v));
return val;
}
};
char FillChar(int val) { return static_cast<char>(val); }
struct FakeMmap {
explicit FakeMmap(size_t size) : size(size), data(new char[size]) {}
size_t size;
std::unique_ptr<char[]> data;
void* mmap() { return static_cast<void*>(data.get()); }
};
template <typename Ring>
FakeMmap CreateRing(Ring* ring, uint32_t count) {
FakeMmap mmap(Ring::MemorySize(count));
*ring = Ring::Create(mmap.mmap(), mmap.size, count);
return mmap;
}
template <typename RecordType, bool StaticSize = false,
uint32_t StaticCount = 0, uint32_t MaxReserved = 1,
uint32_t MinAvailable = 0>
struct Traits {
using Record = RecordType;
static constexpr bool kUseStaticRecordSize = StaticSize;
static constexpr uint32_t kStaticRecordCount = StaticCount;
static constexpr uint32_t kMaxReservedRecords = MaxReserved;
static constexpr uint32_t kMinAvailableRecords = MinAvailable;
static constexpr uint32_t kMinRecordCount = MaxReserved + MinAvailable;
};
template <typename Record, bool StaticSize = false, uint32_t MaxReserved = 1,
uint32_t MinAvailable = 7>
struct TraitsDynamic
: public Traits<Record, StaticSize, 0, MaxReserved, MinAvailable> {
using Ring = BroadcastRing<Record, TraitsDynamic>;
static uint32_t MinCount() { return MaxReserved + MinAvailable; }
};
template <typename Record, uint32_t StaticCount = 1, bool StaticSize = true,
uint32_t MaxReserved = 1, uint32_t MinAvailable = 0>
struct TraitsStatic
: public Traits<Record, true, StaticCount, MaxReserved, MinAvailable> {
using Ring = BroadcastRing<Record, TraitsStatic>;
static uint32_t MinCount() { return StaticCount; }
};
using Dynamic_8_NxM = TraitsDynamic<Sized<8>>;
using Dynamic_16_NxM = TraitsDynamic<Sized<16>>;
using Dynamic_32_NxM = TraitsDynamic<Sized<32>>;
using Dynamic_32_32xM = TraitsDynamic<Sized<32>, true>;
using Dynamic_16_NxM_1plus0 = TraitsDynamic<Sized<16>, false, 1, 0>;
using Dynamic_16_NxM_1plus1 = TraitsDynamic<Sized<16>, false, 1, 1>;
using Dynamic_16_NxM_5plus11 = TraitsDynamic<Sized<16>, false, 5, 11>;
using Dynamic_256_NxM_1plus0 = TraitsDynamic<Sized<256>, false, 1, 0>;
using Static_8_8x1 = TraitsStatic<Sized<8>, 1>;
using Static_8_8x16 = TraitsStatic<Sized<8>, 16>;
using Static_16_16x8 = TraitsStatic<Sized<16>, 8>;
using Static_16_16x16 = TraitsStatic<Sized<16>, 16>;
using Static_16_16x32 = TraitsStatic<Sized<16>, 32>;
using Static_32_Nx8 = TraitsStatic<Sized<32>, 8, false>;
using TraitsList = ::testing::Types<Dynamic_8_NxM, //
Dynamic_16_NxM, //
Dynamic_32_NxM, //
Dynamic_32_32xM, //
Dynamic_16_NxM_1plus0, //
Dynamic_16_NxM_1plus1, //
Dynamic_16_NxM_5plus11, //
Dynamic_256_NxM_1plus0, //
Static_8_8x1, //
Static_8_8x16, //
Static_16_16x8, //
Static_16_16x16, //
Static_16_16x32, //
Static_32_Nx8>;
} // namespace
template <typename T>
class BroadcastRingTest : public ::testing::Test {};
TYPED_TEST_CASE(BroadcastRingTest, TraitsList);
TYPED_TEST(BroadcastRingTest, Geometry) {
using Record = typename TypeParam::Record;
using Ring = typename TypeParam::Ring;
Ring ring;
auto mmap = CreateRing(&ring, Ring::Traits::MinCount());
EXPECT_EQ(Ring::Traits::MinCount(), ring.record_count());
EXPECT_EQ(sizeof(Record), ring.record_size());
}
TYPED_TEST(BroadcastRingTest, PutGet) {
using Record = typename TypeParam::Record;
using Ring = typename TypeParam::Ring;
Ring ring;
auto mmap = CreateRing(&ring, Ring::Traits::MinCount());
const uint32_t oldest_sequence_at_start = ring.GetOldestSequence();
const uint32_t next_sequence_at_start = ring.GetNextSequence();
{
uint32_t sequence = oldest_sequence_at_start;
Record record;
EXPECT_FALSE(ring.Get(&sequence, &record));
EXPECT_EQ(oldest_sequence_at_start, sequence);
EXPECT_EQ(Record(), record);
}
const Record original_record(0x1a);
ring.Put(original_record);
{
uint32_t sequence = next_sequence_at_start;
Record record;
EXPECT_TRUE(ring.Get(&sequence, &record));
EXPECT_EQ(next_sequence_at_start, sequence);
EXPECT_EQ(original_record, record);
}
{
uint32_t sequence = next_sequence_at_start + 1;
Record record;
EXPECT_FALSE(ring.Get(&sequence, &record));
EXPECT_EQ(next_sequence_at_start + 1, sequence);
EXPECT_EQ(Record(), record);
}
}
TYPED_TEST(BroadcastRingTest, FillOnce) {
using Record = typename TypeParam::Record;
using Ring = typename TypeParam::Ring;
Ring ring;
auto mmap = CreateRing(&ring, Ring::Traits::MinCount());
const uint32_t next_sequence_at_start = ring.GetNextSequence();
for (uint32_t i = 0; i < ring.record_count(); ++i)
ring.Put(Record(FillChar(i)));
for (uint32_t i = 0; i < ring.record_count(); ++i) {
const uint32_t expected_sequence = next_sequence_at_start + i;
const Record expected_record(FillChar(i));
{
uint32_t sequence = ring.GetOldestSequence() + i;
Record record;
EXPECT_TRUE(ring.Get(&sequence, &record));
EXPECT_EQ(expected_sequence, sequence);
EXPECT_EQ(expected_record, record);
}
}
{
uint32_t sequence = ring.GetOldestSequence() + ring.record_count();
Record record;
EXPECT_FALSE(ring.Get(&sequence, &record));
}
}
TYPED_TEST(BroadcastRingTest, FillTwice) {
using Record = typename TypeParam::Record;
using Ring = typename TypeParam::Ring;
Ring ring;
auto mmap = CreateRing(&ring, Ring::Traits::MinCount());
const uint32_t next_sequence_at_start = ring.GetNextSequence();
for (uint32_t i = 0; i < 2 * ring.record_count(); ++i) {
const Record newest_record(FillChar(i));
ring.Put(newest_record);
const uint32_t newest_sequence = next_sequence_at_start + i;
const uint32_t records_available = std::min(i + 1, ring.record_count());
const uint32_t oldest_sequence = newest_sequence - records_available + 1;
EXPECT_EQ(newest_sequence, ring.GetNewestSequence());
EXPECT_EQ(oldest_sequence, ring.GetOldestSequence());
EXPECT_EQ(newest_sequence + 1, ring.GetNextSequence());
for (uint32_t j = 0; j < records_available; ++j) {
const uint32_t sequence_jth_newest = newest_sequence - j;
const Record record_jth_newest(FillChar(i - j));
{
uint32_t sequence = sequence_jth_newest;
Record record;
EXPECT_TRUE(ring.Get(&sequence, &record));
EXPECT_EQ(sequence_jth_newest, sequence);
EXPECT_EQ(record_jth_newest, record);
}
{
uint32_t sequence = sequence_jth_newest;
Record record;
EXPECT_TRUE(ring.GetNewest(&sequence, &record));
EXPECT_EQ(newest_sequence, sequence);
EXPECT_EQ(newest_record, record);
}
}
const Record oldest_record(
FillChar(i + (oldest_sequence - newest_sequence)));
const uint32_t sequence_0th_overwritten = oldest_sequence - 1;
const uint32_t sequence_0th_future = newest_sequence + 1;
const uint32_t sequence_1st_future = newest_sequence + 2;
{
uint32_t sequence = sequence_0th_overwritten;
Record record;
EXPECT_TRUE(ring.Get(&sequence, &record));
EXPECT_EQ(oldest_sequence, sequence);
EXPECT_EQ(oldest_record, record);
}
{
uint32_t sequence = sequence_0th_overwritten;
Record record;
EXPECT_TRUE(ring.GetNewest(&sequence, &record));
EXPECT_EQ(newest_sequence, sequence);
EXPECT_EQ(newest_record, record);
}
{
uint32_t sequence = sequence_0th_future;
Record record;
EXPECT_FALSE(ring.Get(&sequence, &record));
EXPECT_EQ(sequence_0th_future, sequence);
EXPECT_EQ(Record(), record);
}
{
uint32_t sequence = sequence_0th_future;
Record record;
EXPECT_FALSE(ring.GetNewest(&sequence, &record));
EXPECT_EQ(sequence_0th_future, sequence);
EXPECT_EQ(Record(), record);
}
{
uint32_t sequence = sequence_1st_future;
Record record;
EXPECT_TRUE(ring.Get(&sequence, &record));
EXPECT_EQ(oldest_sequence, sequence);
EXPECT_EQ(oldest_record, record);
}
{
uint32_t sequence = sequence_1st_future;
Record record;
EXPECT_TRUE(ring.GetNewest(&sequence, &record));
EXPECT_EQ(newest_sequence, sequence);
EXPECT_EQ(newest_record, record);
}
}
}
TYPED_TEST(BroadcastRingTest, Import) {
using Record = typename TypeParam::Record;
using Ring = typename TypeParam::Ring;
Ring ring;
auto mmap = CreateRing(&ring, Ring::Traits::MinCount());
const uint32_t sequence_0 = ring.GetNextSequence();
const uint32_t sequence_1 = ring.GetNextSequence() + 1;
const Record record_0 = Record::Pattern(0x00);
const Record record_1 = Record::Pattern(0x80);
ring.Put(record_0);
ring.Put(record_1);
{
Ring imported_ring;
bool import_ok;
std::tie(imported_ring, import_ok) = Ring::Import(mmap.mmap(), mmap.size);
EXPECT_TRUE(import_ok);
EXPECT_EQ(ring.record_size(), imported_ring.record_size());
EXPECT_EQ(ring.record_count(), imported_ring.record_count());
if (ring.record_count() != 1) {
uint32_t sequence = sequence_0;
Record imported_record;
EXPECT_TRUE(imported_ring.Get(&sequence, &imported_record));
EXPECT_EQ(sequence_0, sequence);
EXPECT_EQ(record_0, imported_record);
}
{
uint32_t sequence = sequence_1;
Record imported_record;
EXPECT_TRUE(imported_ring.Get(&sequence, &imported_record));
EXPECT_EQ(sequence_1, sequence);
EXPECT_EQ(record_1, imported_record);
}
}
}
TEST(BroadcastRingTest, ShouldFailImportIfStaticSizeMismatch) {
using OriginalRing = typename Static_16_16x16::Ring;
using RecordSizeMismatchRing = typename Static_8_8x16::Ring;
using RecordCountMismatchRing = typename Static_16_16x8::Ring;
OriginalRing original_ring;
auto mmap = CreateRing(&original_ring, OriginalRing::Traits::MinCount());
{
using ImportedRing = RecordSizeMismatchRing;
ImportedRing imported_ring;
bool import_ok;
std::tie(imported_ring, import_ok) =
ImportedRing::Import(mmap.mmap(), mmap.size);
EXPECT_FALSE(import_ok);
auto mmap_imported =
CreateRing(&imported_ring, ImportedRing::Traits::MinCount());
EXPECT_NE(original_ring.record_size(), imported_ring.record_size());
EXPECT_EQ(original_ring.record_count(), imported_ring.record_count());
}
{
using ImportedRing = RecordCountMismatchRing;
ImportedRing imported_ring;
bool import_ok;
std::tie(imported_ring, import_ok) =
ImportedRing::Import(mmap.mmap(), mmap.size);
EXPECT_FALSE(import_ok);
auto mmap_imported =
CreateRing(&imported_ring, ImportedRing::Traits::MinCount());
EXPECT_EQ(original_ring.record_size(), imported_ring.record_size());
EXPECT_NE(original_ring.record_count(), imported_ring.record_count());
}
}
TEST(BroadcastRingTest, ShouldFailImportIfDynamicSizeGrows) {
using OriginalRing = typename Dynamic_8_NxM::Ring;
using RecordSizeGrowsRing = typename Dynamic_16_NxM::Ring;
OriginalRing original_ring;
auto mmap = CreateRing(&original_ring, OriginalRing::Traits::MinCount());
{
using ImportedRing = RecordSizeGrowsRing;
ImportedRing imported_ring;
bool import_ok;
std::tie(imported_ring, import_ok) =
ImportedRing::Import(mmap.mmap(), mmap.size);
EXPECT_FALSE(import_ok);
auto mmap_imported =
CreateRing(&imported_ring, ImportedRing::Traits::MinCount());
EXPECT_LT(original_ring.record_size(), imported_ring.record_size());
EXPECT_EQ(original_ring.record_count(), imported_ring.record_count());
}
}
TEST(BroadcastRingTest, ShouldFailImportIfCountTooSmall) {
using OriginalRing = typename Dynamic_16_NxM_1plus0::Ring;
using MinCountRing = typename Dynamic_16_NxM_1plus1::Ring;
OriginalRing original_ring;
auto mmap = CreateRing(&original_ring, OriginalRing::Traits::MinCount());
{
using ImportedRing = MinCountRing;
ImportedRing imported_ring;
bool import_ok;
std::tie(imported_ring, import_ok) =
ImportedRing::Import(mmap.mmap(), mmap.size);
EXPECT_FALSE(import_ok);
auto mmap_imported =
CreateRing(&imported_ring, ImportedRing::Traits::MinCount());
EXPECT_EQ(original_ring.record_size(), imported_ring.record_size());
EXPECT_LT(original_ring.record_count(), imported_ring.record_count());
}
}
TEST(BroadcastRingTest, ShouldFailImportIfMmapTooSmall) {
using OriginalRing = typename Dynamic_16_NxM::Ring;
OriginalRing original_ring;
auto mmap = CreateRing(&original_ring, OriginalRing::Traits::MinCount());
{
using ImportedRing = OriginalRing;
ImportedRing imported_ring;
bool import_ok;
const size_t kMinSize =
ImportedRing::MemorySize(original_ring.record_count());
std::tie(imported_ring, import_ok) = ImportedRing::Import(mmap.mmap(), 0);
EXPECT_FALSE(import_ok);
std::tie(imported_ring, import_ok) =
ImportedRing::Import(mmap.mmap(), kMinSize - 1);
EXPECT_FALSE(import_ok);
std::tie(imported_ring, import_ok) =
ImportedRing::Import(mmap.mmap(), kMinSize);
EXPECT_TRUE(import_ok);
EXPECT_EQ(original_ring.record_size(), imported_ring.record_size());
EXPECT_EQ(original_ring.record_count(), imported_ring.record_count());
}
}
TEST(BroadcastRingTest, ShouldImportIfDynamicSizeShrinks) {
using OriginalRing = typename Dynamic_16_NxM::Ring;
using RecordSizeShrinksRing = typename Dynamic_8_NxM::Ring;
OriginalRing original_ring;
auto mmap = CreateRing(&original_ring, OriginalRing::Traits::MinCount());
using OriginalRecord = typename OriginalRing::Record;
const uint32_t original_sequence_0 = original_ring.GetNextSequence();
const uint32_t original_sequence_1 = original_ring.GetNextSequence() + 1;
const OriginalRecord original_record_0 = OriginalRecord::Pattern(0x00);
const OriginalRecord original_record_1 = OriginalRecord::Pattern(0x80);
original_ring.Put(original_record_0);
original_ring.Put(original_record_1);
{
using ImportedRing = RecordSizeShrinksRing;
using ImportedRecord = typename ImportedRing::Record;
ImportedRing imported_ring;
bool import_ok;
std::tie(imported_ring, import_ok) =
ImportedRing::Import(mmap.mmap(), mmap.size);
EXPECT_TRUE(import_ok);
EXPECT_EQ(original_ring.record_size(), imported_ring.record_size());
EXPECT_EQ(original_ring.record_count(), imported_ring.record_count());
EXPECT_GT(sizeof(OriginalRecord), sizeof(ImportedRecord));
{
uint32_t sequence = original_sequence_0;
ImportedRecord shrunk_record;
EXPECT_TRUE(imported_ring.Get(&sequence, &shrunk_record));
EXPECT_EQ(original_sequence_0, sequence);
EXPECT_EQ(original_record_0.Truncate<ImportedRecord>(), shrunk_record);
}
{
uint32_t sequence = original_sequence_1;
ImportedRecord shrunk_record;
EXPECT_TRUE(imported_ring.Get(&sequence, &shrunk_record));
EXPECT_EQ(original_sequence_1, sequence);
EXPECT_EQ(original_record_1.Truncate<ImportedRecord>(), shrunk_record);
}
}
}
TEST(BroadcastRingTest, ShouldImportIfCompatibleDynamicToStatic) {
using OriginalRing = typename Dynamic_16_NxM::Ring;
using ImportedRing = typename Static_16_16x16::Ring;
using OriginalRecord = typename OriginalRing::Record;
using ImportedRecord = typename ImportedRing::Record;
using StaticRing = ImportedRing;
OriginalRing original_ring;
auto mmap = CreateRing(&original_ring, StaticRing::Traits::MinCount());
const uint32_t original_sequence_0 = original_ring.GetNextSequence();
const uint32_t original_sequence_1 = original_ring.GetNextSequence() + 1;
const OriginalRecord original_record_0 = OriginalRecord::Pattern(0x00);
const OriginalRecord original_record_1 = OriginalRecord::Pattern(0x80);
original_ring.Put(original_record_0);
original_ring.Put(original_record_1);
{
ImportedRing imported_ring;
bool import_ok;
std::tie(imported_ring, import_ok) =
ImportedRing::Import(mmap.mmap(), mmap.size);
EXPECT_TRUE(import_ok);
EXPECT_EQ(original_ring.record_size(), imported_ring.record_size());
EXPECT_EQ(original_ring.record_count(), imported_ring.record_count());
{
uint32_t sequence = original_sequence_0;
ImportedRecord imported_record;
EXPECT_TRUE(imported_ring.Get(&sequence, &imported_record));
EXPECT_EQ(original_sequence_0, sequence);
EXPECT_EQ(original_record_0, imported_record);
}
{
uint32_t sequence = original_sequence_1;
ImportedRecord imported_record;
EXPECT_TRUE(imported_ring.Get(&sequence, &imported_record));
EXPECT_EQ(original_sequence_1, sequence);
EXPECT_EQ(original_record_1, imported_record);
}
}
}
TEST(BroadcastRingTest, ShouldImportIfCompatibleStaticToDynamic) {
using OriginalRing = typename Static_16_16x16::Ring;
using ImportedRing = typename Dynamic_16_NxM::Ring;
using OriginalRecord = typename OriginalRing::Record;
using ImportedRecord = typename ImportedRing::Record;
using StaticRing = OriginalRing;
OriginalRing original_ring;
auto mmap = CreateRing(&original_ring, StaticRing::Traits::MinCount());
const uint32_t original_sequence_0 = original_ring.GetNextSequence();
const uint32_t original_sequence_1 = original_ring.GetNextSequence() + 1;
const OriginalRecord original_record_0 = OriginalRecord::Pattern(0x00);
const OriginalRecord original_record_1 = OriginalRecord::Pattern(0x80);
original_ring.Put(original_record_0);
original_ring.Put(original_record_1);
{
ImportedRing imported_ring;
bool import_ok;
std::tie(imported_ring, import_ok) =
ImportedRing::Import(mmap.mmap(), mmap.size);
EXPECT_TRUE(import_ok);
EXPECT_EQ(original_ring.record_size(), imported_ring.record_size());
EXPECT_EQ(original_ring.record_count(), imported_ring.record_count());
{
uint32_t sequence = original_sequence_0;
ImportedRecord imported_record;
EXPECT_TRUE(imported_ring.Get(&sequence, &imported_record));
EXPECT_EQ(original_sequence_0, sequence);
EXPECT_EQ(original_record_0, imported_record);
}
{
uint32_t sequence = original_sequence_1;
ImportedRecord imported_record;
EXPECT_TRUE(imported_ring.Get(&sequence, &imported_record));
EXPECT_EQ(original_sequence_1, sequence);
EXPECT_EQ(original_record_1, imported_record);
}
}
}
TEST(BroadcastRingTest, ShouldImportIfReadonlyMmap) {
using Ring = Dynamic_32_NxM::Ring;
using Record = Ring::Record;
uint32_t record_count = Ring::Traits::MinCount();
size_t ring_size = Ring::MemorySize(record_count);
size_t page_size = sysconf(_SC_PAGESIZE);
size_t mmap_size = (ring_size + (page_size - 1)) & ~(page_size - 1);
ASSERT_GE(mmap_size, ring_size);
void* mmap_base = mmap(nullptr, mmap_size, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
ASSERT_NE(MAP_FAILED, mmap_base);
Ring ring = Ring::Create(mmap_base, mmap_size, record_count);
for (uint32_t i = 0; i < record_count; ++i) ring.Put(Record(FillChar(i)));
ASSERT_EQ(0, mprotect(mmap_base, mmap_size, PROT_READ));
{
Ring imported_ring;
bool import_ok;
std::tie(imported_ring, import_ok) = Ring::Import(mmap_base, mmap_size);
EXPECT_TRUE(import_ok);
EXPECT_EQ(ring.record_size(), imported_ring.record_size());
EXPECT_EQ(ring.record_count(), imported_ring.record_count());
uint32_t oldest_sequence = imported_ring.GetOldestSequence();
for (uint32_t i = 0; i < record_count; ++i) {
uint32_t sequence = oldest_sequence + i;
Record record;
EXPECT_TRUE(imported_ring.Get(&sequence, &record));
EXPECT_EQ(Record(FillChar(i)), record);
}
}
ASSERT_EQ(0, munmap(mmap_base, mmap_size));
}
TEST(BroadcastRingTest, ShouldDieIfPutReadonlyMmap) {
using Ring = Dynamic_32_NxM::Ring;
using Record = Ring::Record;
uint32_t record_count = Ring::Traits::MinCount();
size_t ring_size = Ring::MemorySize(record_count);
size_t page_size = sysconf(_SC_PAGESIZE);
size_t mmap_size = (ring_size + (page_size - 1)) & ~(page_size - 1);
ASSERT_GE(mmap_size, ring_size);
void* mmap_base = mmap(nullptr, mmap_size, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
ASSERT_NE(MAP_FAILED, mmap_base);
Ring ring = Ring::Create(mmap_base, mmap_size, record_count);
for (uint32_t i = 0; i < record_count; ++i) ring.Put(Record(FillChar(i)));
ASSERT_EQ(0, mprotect(mmap_base, mmap_size, PROT_READ));
EXPECT_DEATH_IF_SUPPORTED({ ring.Put(Record(7)); }, "");
ASSERT_EQ(0, munmap(mmap_base, mmap_size));
}
TEST(BroadcastRingTest, ShouldDieIfCreationMmapTooSmall) {
using Ring = Dynamic_32_NxM::Ring;
using Record = Ring::Record;
uint32_t record_count = Ring::Traits::MinCount();
size_t ring_size = Ring::MemorySize(record_count);
FakeMmap mmap(ring_size);
EXPECT_DEATH_IF_SUPPORTED({
Ring ring = Ring::Create(mmap.mmap(), ring_size - 1, record_count);
}, "");
Ring ring = Ring::Create(mmap.mmap(), ring_size, record_count);
ring.Put(Record(3));
{
uint32_t sequence = ring.GetNewestSequence();
Record record;
EXPECT_TRUE(ring.Get(&sequence, &record));
EXPECT_EQ(Record(3), record);
}
}
TEST(BroadcastRingTest, ShouldDieIfCreationMmapMisaligned) {
using Ring = Static_8_8x1::Ring;
using Record = Ring::Record;
constexpr int kAlign = Ring::mmap_alignment();
constexpr int kMisalign = kAlign / 2;
size_t ring_size = Ring::MemorySize();
std::unique_ptr<char[]> buf(new char[ring_size + kMisalign]);
EXPECT_DEATH_IF_SUPPORTED(
{ Ring ring = Ring::Create(buf.get() + kMisalign, ring_size); }, "");
Ring ring = Ring::Create(buf.get(), ring_size);
ring.Put(Record(3));
{
uint32_t sequence = ring.GetNewestSequence();
Record record;
EXPECT_TRUE(ring.Get(&sequence, &record));
EXPECT_EQ(Record(3), record);
}
}
template <typename Ring>
std::unique_ptr<std::thread> CopyTask(std::atomic<bool>* quit, void* in_base,
size_t in_size, void* out_base,
size_t out_size) {
return std::unique_ptr<std::thread>(
new std::thread([quit, in_base, in_size, out_base, out_size]() {
using Record = typename Ring::Record;
bool import_ok;
Ring in_ring;
Ring out_ring;
std::tie(in_ring, import_ok) = Ring::Import(in_base, in_size);
ASSERT_TRUE(import_ok);
std::tie(out_ring, import_ok) = Ring::Import(out_base, out_size);
ASSERT_TRUE(import_ok);
uint32_t sequence = in_ring.GetOldestSequence();
while (!std::atomic_load_explicit(quit, std::memory_order_relaxed)) {
Record record;
if (in_ring.Get(&sequence, &record)) {
out_ring.Put(record);
sequence++;
}
}
}));
}
TEST(BroadcastRingTest, ThreadedCopySingle) {
using Ring = Dynamic_32_NxM::Ring;
using Record = Ring::Record;
Ring in_ring;
auto in_mmap = CreateRing(&in_ring, Ring::Traits::MinCount());
Ring out_ring;
auto out_mmap = CreateRing(&out_ring, Ring::Traits::MinCount());
std::atomic<bool> quit(false);
std::unique_ptr<std::thread> copy_task = CopyTask<Ring>(
&quit, out_mmap.mmap(), out_mmap.size, in_mmap.mmap(), in_mmap.size);
const Record out_record(0x1c);
out_ring.Put(out_record);
uint32_t in_sequence = in_ring.GetOldestSequence();
Record in_record;
while (!in_ring.Get(&in_sequence, &in_record)) {
// Do nothing.
}
EXPECT_EQ(out_record, in_record);
std::atomic_store_explicit(&quit, true, std::memory_order_relaxed);
copy_task->join();
}
TEST(BroadcastRingTest, ThreadedCopyLossless) {
using Ring = Dynamic_32_NxM::Ring;
using Record = Ring::Record;
Ring in_ring;
auto in_mmap = CreateRing(&in_ring, Ring::Traits::MinCount());
Ring out_ring;
auto out_mmap = CreateRing(&out_ring, Ring::Traits::MinCount());
std::atomic<bool> quit(false);
std::unique_ptr<std::thread> copy_task = CopyTask<Ring>(
&quit, out_mmap.mmap(), out_mmap.size, in_mmap.mmap(), in_mmap.size);
constexpr uint32_t kRecordsToProcess = 10000;
uint32_t out_records = 0;
uint32_t in_records = 0;
uint32_t in_sequence = in_ring.GetNextSequence();
while (out_records < kRecordsToProcess || in_records < kRecordsToProcess) {
if (out_records < kRecordsToProcess &&
out_records - in_records < out_ring.record_count()) {
const Record out_record(FillChar(out_records));
out_ring.Put(out_record);
out_records++;
}
Record in_record;
while (in_ring.Get(&in_sequence, &in_record)) {
EXPECT_EQ(Record(FillChar(in_records)), in_record);
in_records++;
in_sequence++;
}
}
EXPECT_EQ(kRecordsToProcess, out_records);
EXPECT_EQ(kRecordsToProcess, in_records);
std::atomic_store_explicit(&quit, true, std::memory_order_relaxed);
copy_task->join();
}
TEST(BroadcastRingTest, ThreadedCopyLossy) {
using Ring = Dynamic_32_NxM::Ring;
using Record = Ring::Record;
Ring in_ring;
auto in_mmap = CreateRing(&in_ring, Ring::Traits::MinCount());
Ring out_ring;
auto out_mmap = CreateRing(&out_ring, Ring::Traits::MinCount());
std::atomic<bool> quit(false);
std::unique_ptr<std::thread> copy_task = CopyTask<Ring>(
&quit, out_mmap.mmap(), out_mmap.size, in_mmap.mmap(), in_mmap.size);
constexpr uint32_t kRecordsToProcess = 100000;
uint32_t out_records = 0;
uint32_t in_records = 0;
uint32_t in_sequence = in_ring.GetNextSequence();
while (out_records < kRecordsToProcess) {
const Record out_record(FillChar(out_records));
out_ring.Put(out_record);
out_records++;
Record in_record;
if (in_ring.GetNewest(&in_sequence, &in_record)) {
EXPECT_EQ(Record(in_record.v[0]), in_record);
in_records++;
in_sequence++;
}
}
EXPECT_EQ(kRecordsToProcess, out_records);
EXPECT_GE(kRecordsToProcess, in_records);
std::atomic_store_explicit(&quit, true, std::memory_order_relaxed);
copy_task->join();
}
template <typename Ring>
std::unique_ptr<std::thread> CheckFillTask(std::atomic<bool>* quit,
void* in_base, size_t in_size) {
return std::unique_ptr<std::thread>(
new std::thread([quit, in_base, in_size]() {
using Record = typename Ring::Record;
bool import_ok;
Ring in_ring;
std::tie(in_ring, import_ok) = Ring::Import(in_base, in_size);
ASSERT_TRUE(import_ok);
uint32_t sequence = in_ring.GetOldestSequence();
while (!std::atomic_load_explicit(quit, std::memory_order_relaxed)) {
Record record;
if (in_ring.Get(&sequence, &record)) {
ASSERT_EQ(Record(record.v[0]), record);
sequence++;
}
}
}));
}
template <typename Ring>
void ThreadedOverwriteTorture() {
using Record = typename Ring::Record;
// Maximize overwrites by having few records.
const int kMinRecordCount = 1;
const int kMaxRecordCount = 4;
for (int count = kMinRecordCount; count <= kMaxRecordCount; count *= 2) {
Ring out_ring;
auto out_mmap = CreateRing(&out_ring, count);
std::atomic<bool> quit(false);
std::unique_ptr<std::thread> check_task =
CheckFillTask<Ring>(&quit, out_mmap.mmap(), out_mmap.size);
constexpr int kIterations = 10000;
for (int i = 0; i < kIterations; ++i) {
const Record record(FillChar(i));
out_ring.Put(record);
}
std::atomic_store_explicit(&quit, true, std::memory_order_relaxed);
check_task->join();
}
}
TEST(BroadcastRingTest, ThreadedOverwriteTortureSmall) {
ThreadedOverwriteTorture<Dynamic_16_NxM_1plus0::Ring>();
}
TEST(BroadcastRingTest, ThreadedOverwriteTortureLarge) {
ThreadedOverwriteTorture<Dynamic_256_NxM_1plus0::Ring>();
}
} // namespace dvr
} // namespace android