#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