blob: 3ea88e929f834e8130e534b6687e929c7795b3ec [file] [log] [blame]
// Copyright 2016 The Fuchsia 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 "garnet/bin/mediaplayer/demux/sparse_byte_buffer.h"
#include "gtest/gtest.h"
namespace media_player {
namespace {
static const size_t kSize = 1000u;
void ExpectNullRegion(SparseByteBuffer* under_test,
SparseByteBuffer::Region region) {
EXPECT_EQ(under_test->null_region(), region);
}
uint8_t ByteForPosition(size_t position) {
return static_cast<uint8_t>(position ^ (position >> 8) ^ (position >> 16) ^
(position >> 24));
}
void ExpectRegion(SparseByteBuffer* under_test, size_t position, size_t size,
SparseByteBuffer::Region region) {
EXPECT_NE(under_test->null_region(), region);
EXPECT_EQ(position, region.position());
EXPECT_EQ(size, region.size());
uint8_t* data = region.data();
EXPECT_NE(nullptr, data);
for (size_t i = 0; i < size; i++) {
EXPECT_EQ(data[i], ByteForPosition(position + i));
}
}
void ExpectNullHole(SparseByteBuffer* under_test, SparseByteBuffer::Hole hole) {
EXPECT_EQ(under_test->null_hole(), hole);
}
void ExpectHole(SparseByteBuffer* under_test, size_t position, size_t size,
SparseByteBuffer::Hole hole) {
EXPECT_NE(SparseByteBuffer::Hole(), hole);
EXPECT_NE(under_test->null_hole(), hole);
EXPECT_EQ(position, hole.position());
EXPECT_EQ(size, hole.size());
}
std::vector<uint8_t> CreateBuffer(size_t position, size_t size) {
std::vector<uint8_t> buffer(size);
for (size_t i = 0; i < size; i++) {
buffer[i] = ByteForPosition(i + position);
}
return buffer;
}
void FillRegion(SparseByteBuffer* under_test, size_t start, size_t size) {
SparseByteBuffer::Hole hole_to_fill =
under_test->FindOrCreateHole(start, under_test->null_hole());
if (hole_to_fill == under_test->null_hole()) {
return;
}
under_test->Fill(hole_to_fill, CreateBuffer(start, size));
}
SparseByteBuffer BufferWithRegions(
std::vector<std::pair<size_t, size_t>> regions) {
SparseByteBuffer under_test;
under_test.Initialize(kSize);
for (const auto& region_spec : regions) {
FillRegion(&under_test, region_spec.first, region_spec.second);
}
return under_test;
}
// See HoleHints test.
void VerifyHoleHint(size_t hole_count, size_t hint_position) {
SparseByteBuffer under_test;
under_test.Initialize(kSize);
size_t hole_size = kSize / hole_count;
// Create the holes.
for (size_t position = 0; position < kSize; position += hole_size) {
SparseByteBuffer::Hole hole =
under_test.FindOrCreateHole(position, under_test.null_hole());
ExpectHole(&under_test, position, kSize - position, hole);
}
// Use the indicated hole as a hint.
SparseByteBuffer::Hole hint = under_test.FindHoleContaining(hint_position);
// Run FindOrCreateHole against every position for each hint.
for (size_t position = 0; position < kSize; position++) {
SparseByteBuffer::Hole hole = under_test.FindOrCreateHole(position, hint);
size_t expected_size = hole_size - position % hole_size;
if (position + expected_size > kSize) {
expected_size = kSize - position;
}
ExpectHole(&under_test, position, expected_size, hole);
}
}
// Tests that the buffer behaves as expected immediately after initialization.
TEST(SparseByteBufferTest, InitialState) {
SparseByteBuffer under_test;
under_test.Initialize(kSize);
// Null comparison works for regions.
ExpectNullRegion(&under_test, under_test.null_region());
// No regions anywhere.
for (size_t position = 0; position < kSize; position++) {
ExpectNullRegion(&under_test, under_test.FindRegionContaining(
position, under_test.null_region()));
}
// Null comparison works for holes.
ExpectNullHole(&under_test, under_test.null_hole());
// One hole everywhere.
for (size_t position = 0; position < kSize; position++) {
ExpectHole(&under_test, 0, kSize, under_test.FindHoleContaining(position));
}
// FindOrCreateHole finds the hole.
ExpectHole(&under_test, 0, kSize,
under_test.FindOrCreateHole(0, under_test.null_hole()));
}
TEST(SparseByteBufferTest, BytesStored) {
{
SparseByteBuffer under_test =
BufferWithRegions({{0, 10}, {30, 2}, {780, 2}});
EXPECT_EQ(under_test.BytesStored(), 14u);
}
{
SparseByteBuffer under_test = BufferWithRegions({});
EXPECT_EQ(under_test.BytesStored(), 0u);
}
}
TEST(SparseByteBufferTest, NextMissingByte) {
{
SparseByteBuffer under_test =
BufferWithRegions({{0, 10}, {30, 2}, {780, 2}});
ASSERT_TRUE(under_test.NextMissingByte(30).has_value());
EXPECT_EQ(*under_test.NextMissingByte(30), 32u);
ASSERT_TRUE(under_test.NextMissingByte(0).has_value());
EXPECT_EQ(*under_test.NextMissingByte(0), 10u);
ASSERT_TRUE(under_test.NextMissingByte(100).has_value());
EXPECT_EQ(*under_test.NextMissingByte(100), 100u);
}
{
SparseByteBuffer under_test = BufferWithRegions({});
EXPECT_EQ(under_test.NextMissingByte(0), std::make_optional(0u));
}
{
SparseByteBuffer under_test = BufferWithRegions({{0, kSize}});
EXPECT_EQ(under_test.NextMissingByte(0), std::nullopt);
}
}
TEST(SparseByteBufferTest, ReadRange) {
{
// Read range filled with regions.
SparseByteBuffer under_test = BufferWithRegions({{0, 100}, {100, 200}});
std::vector<uint8_t> dest_buffer(200, 0);
size_t copied = under_test.ReadRange(0, 200, dest_buffer.data());
EXPECT_EQ(copied, 200u);
EXPECT_EQ(dest_buffer, CreateBuffer(0, 200));
}
{
// Read range from region stretching beyond it.
SparseByteBuffer under_test = BufferWithRegions({{0, 1000}});
std::vector<uint8_t> dest_buffer(50, 0);
size_t copied = under_test.ReadRange(100, 50, dest_buffer.data());
EXPECT_EQ(copied, 50u);
EXPECT_EQ(dest_buffer, CreateBuffer(100, 50));
}
{
// Read range with only partial coverage.
SparseByteBuffer under_test = BufferWithRegions({{0, 50}});
std::vector<uint8_t> dest_buffer(25);
size_t copied = under_test.ReadRange(25, 50, dest_buffer.data());
EXPECT_EQ(copied, 25u);
EXPECT_EQ(dest_buffer, CreateBuffer(25, 25));
}
{
// Read range with only partial coverage that should bail early.
SparseByteBuffer under_test = BufferWithRegions({{0, 50}, {100, 50}});
std::vector<uint8_t> dest_buffer(25);
size_t copied = under_test.ReadRange(25, 500, dest_buffer.data());
EXPECT_EQ(copied, 25u);
EXPECT_EQ(dest_buffer, CreateBuffer(25, 25));
}
}
// Creates a second hole.
TEST(SparseByteBufferTest, TwoHoles) {
SparseByteBuffer under_test;
under_test.Initialize(kSize);
// Create a hole from kSize/2 to the end.
SparseByteBuffer::Hole created_hole =
under_test.FindOrCreateHole(kSize / 2, under_test.null_hole());
// One hole covers the first half.
for (size_t position = 0; position < kSize / 2; position++) {
ExpectHole(&under_test, 0, kSize / 2,
under_test.FindHoleContaining(position));
}
// Created hole covers the second half.
for (size_t position = kSize / 2; position < kSize; position++) {
EXPECT_EQ(created_hole, under_test.FindHoleContaining(position));
}
// No regions anywhere.
for (size_t position = 0; position < kSize; position++) {
ExpectNullRegion(&under_test, under_test.FindRegionContaining(
position, under_test.null_region()));
}
}
// Creates a single region that covers the entire buffer.
TEST(SparseByteBufferTest, BigRegion) {
SparseByteBuffer under_test;
under_test.Initialize(kSize);
// Fill the whole buffer. No hole should be returned, because there are no
// holes anymore.
ExpectNullHole(&under_test, under_test.Fill(under_test.FindHoleContaining(0),
CreateBuffer(0, kSize)));
// Find the new region.
SparseByteBuffer::Region big_region =
under_test.FindRegionContaining(0, under_test.null_region());
ExpectRegion(&under_test, 0, kSize, big_region);
// Same region everywhere.
for (size_t position = 0; position < kSize; position++) {
EXPECT_EQ(big_region, under_test.FindRegionContaining(
position, under_test.null_region()));
}
// No holes anywhere.
for (size_t position = 0; position < kSize; position++) {
ExpectNullHole(&under_test, under_test.FindHoleContaining(position));
}
}
// Creates a region for every other byte, then fills in the holes.
TEST(SparseByteBufferTest, TinyRegions) {
SparseByteBuffer under_test;
under_test.Initialize(kSize);
// Create the regions.
for (size_t position = 0; position < kSize; position += 2) {
SparseByteBuffer::Hole hole =
under_test.FindOrCreateHole(position, under_test.null_hole());
ExpectHole(&under_test, position, kSize - position, hole);
ExpectHole(&under_test, position + 1, kSize - position - 1,
under_test.Fill(hole, CreateBuffer(position, 1)));
}
// Find them again.
for (size_t position = 0; position < kSize; position += 2) {
ExpectRegion(
&under_test, position, 1,
under_test.FindRegionContaining(position, under_test.null_region()));
}
// Find the holes.
for (size_t position = 1; position < kSize; position += 2) {
ExpectHole(&under_test, position, 1,
under_test.FindHoleContaining(position));
}
// Fill in the holes.
for (size_t position = 1; position < kSize; position += 2) {
SparseByteBuffer::Hole hole = under_test.FindHoleContaining(position);
ExpectHole(&under_test, position, 1, hole);
hole = under_test.Fill(hole, CreateBuffer(position, 1));
if (position + 2 < kSize) {
ExpectHole(&under_test, position + 2, 1, hole);
} else {
ExpectNullHole(&under_test, hole);
}
}
// Tiny regions everywhere.
for (size_t position = 0; position < kSize; position++) {
ExpectRegion(
&under_test, position, 1,
under_test.FindRegionContaining(position, under_test.null_region()));
}
// No holes anywhere.
for (size_t position = 0; position < kSize; position++) {
ExpectNullHole(&under_test, under_test.FindHoleContaining(position));
}
}
// Verifies that FindRegionContaining works regardless of the hints it's given.
TEST(SparseByteBufferTest, RegionHints) {
SparseByteBuffer under_test;
under_test.Initialize(kSize);
static const size_t region_count = 11u;
size_t region_size = kSize / region_count;
// Create the regions.
for (size_t position = 0; position < kSize; position += region_size) {
SparseByteBuffer::Hole hole =
under_test.FindOrCreateHole(position, under_test.null_hole());
ExpectHole(&under_test, position, kSize - position, hole);
if (position + region_size >= kSize) {
ExpectNullHole(
&under_test,
under_test.Fill(hole, CreateBuffer(position, kSize - position)));
} else {
ExpectHole(&under_test, position + region_size,
kSize - position - region_size,
under_test.Fill(hole, CreateBuffer(position, region_size)));
}
}
// Use each region as a hint.
for (size_t hint_position = 0; hint_position < kSize;
hint_position += region_size) {
SparseByteBuffer::Region hint = under_test.FindRegionContaining(
hint_position, under_test.null_region());
// Run FindRegionContaining against every position for each hint.
for (size_t position = 0; position < kSize; position++) {
SparseByteBuffer::Region region =
under_test.FindRegionContaining(position, hint);
size_t region_position = position - (position % region_size);
ExpectRegion(&under_test, region_position,
region_position + region_size > kSize
? kSize - region_position
: region_size,
region);
}
}
// Make sure null_region works as a hint.
for (size_t position = 0; position < kSize; position++) {
SparseByteBuffer::Region region =
under_test.FindRegionContaining(position, under_test.null_region());
size_t region_position = position - (position % region_size);
ExpectRegion(&under_test, region_position,
region_position + region_size > kSize ? kSize - region_position
: region_size,
region);
}
}
// Verifies that FindOrCreateHole works regardless of the hints it's given.
TEST(SparseByteBufferTest, HoleHints) {
static const size_t hole_count = 11u;
size_t hole_size = kSize / hole_count;
for (size_t hint_position = 0; hint_position < kSize;
hint_position += hole_size) {
VerifyHoleHint(hole_count, hint_position);
}
}
TEST(SparseByteBufferTest, FindOrCreateHolesInRange) {
{
// Test buffer diagram:
// | Selected Range |
// [ = ==== === ==== ....]
// Regions (corresponding to diagram):
SparseByteBuffer under_test =
BufferWithRegions({{3, 1}, {7, 4}, {12, 3}, {21, 4}});
std::vector<SparseByteBuffer::Hole> holes =
under_test.FindOrCreateHolesInRange(5, 17);
// Expected holes in range (corresponding to diagram):
EXPECT_EQ(holes.size(), 3u) << "Number of holes vs Expected";
ExpectHole(&under_test, 5, 2, holes[0]);
ExpectHole(&under_test, 11, 1, holes[1]);
ExpectHole(&under_test, 15, 6, holes[2]);
// Expected holes outside of range (corresponding to diagram):
ExpectHole(&under_test, 0, 3, under_test.FindHoleContaining(0));
ExpectHole(&under_test, 4, 1, under_test.FindHoleContaining(4));
ExpectHole(&under_test, 25, kSize - 25, under_test.FindHoleContaining(25));
}
{
// Find hole on end of range.
SparseByteBuffer under_test = BufferWithRegions({{0, 100}});
std::vector<SparseByteBuffer::Hole> holes =
under_test.FindOrCreateHolesInRange(50, 100);
EXPECT_EQ(holes.size(), 1u);
ExpectHole(&under_test, 100, 50, holes[0]);
ExpectHole(&under_test, 150, kSize - 150,
under_test.FindHoleContaining(150));
}
{
// Find hole in empty buffer.
SparseByteBuffer under_test = BufferWithRegions({});
std::vector<SparseByteBuffer::Hole> holes =
under_test.FindOrCreateHolesInRange(100, 100);
EXPECT_EQ(holes.size(), 1u);
ExpectHole(&under_test, 100, 100, holes[0]);
ExpectHole(&under_test, 0, 100, under_test.FindHoleContaining(0));
ExpectHole(&under_test, 200, kSize - 200,
under_test.FindHoleContaining(200));
}
}
TEST(SparseByteBufferTest, BytesMissingInRange) {
{
// Test buffer diagram:
// | Selected Range |
// [ = ==== === ==== ....]
// Regions (corresponding to diagram):
SparseByteBuffer under_test =
BufferWithRegions({{3, 1}, {7, 4}, {12, 3}, {21, 4}});
size_t bytes_missing = under_test.BytesMissingInRange(5, 17);
// Expected bytes missing in range (corresponding to diagram):
EXPECT_EQ(bytes_missing, 9u) << "Number of missing bytes vs Expected";
}
{
// Find missing bytes in end of range.
SparseByteBuffer under_test = BufferWithRegions({{0, 100}});
size_t bytes_missing = under_test.BytesMissingInRange(50, 100);
EXPECT_EQ(bytes_missing, 50u);
}
{
// Find missing bytes in empty buffer.
SparseByteBuffer under_test = BufferWithRegions({});
size_t bytes_missing = under_test.BytesMissingInRange(100, 100);
EXPECT_EQ(bytes_missing, 100u);
}
}
TEST(SparseByteBufferTest, FreeRegion) {
{
// Free a region with a hole before and after it.
SparseByteBuffer under_test = BufferWithRegions({{40, 50}});
SparseByteBuffer::Region region_to_free =
under_test.FindRegionContaining(40, under_test.null_region());
SparseByteBuffer::Hole enclosing_hole = under_test.Free(region_to_free);
ExpectHole(&under_test, 0, kSize, enclosing_hole);
}
{
// Free a region at the buffer beginning.
SparseByteBuffer under_test = BufferWithRegions({{0, 20}});
SparseByteBuffer::Region region_to_free =
under_test.FindRegionContaining(0, under_test.null_region());
SparseByteBuffer::Hole enclosing_hole = under_test.Free(region_to_free);
ExpectHole(&under_test, 0, kSize, enclosing_hole);
}
{
// Free a sandwiched Region.
SparseByteBuffer under_test =
BufferWithRegions({{10, 10}, {20, 10}, {30, 10}});
SparseByteBuffer::Region region_to_free =
under_test.FindRegionContaining(20, under_test.null_region());
SparseByteBuffer::Hole enclosing_hole = under_test.Free(region_to_free);
ExpectHole(&under_test, 20, 10, enclosing_hole);
}
{
// Free region with holes not adjacent.
SparseByteBuffer under_test =
BufferWithRegions({{10, 10}, {20, 10}, {30, 10}, {50, 10}, {90, 10}});
SparseByteBuffer::Region region_to_free =
under_test.FindRegionContaining(20, under_test.null_region());
SparseByteBuffer::Hole enclosing_hole = under_test.Free(region_to_free);
ExpectHole(&under_test, 20, 10, enclosing_hole);
}
}
TEST(SparseByteBufferTest, CleanUpExcept) {
{
// Flagship usecase.
// Clean up except a space with regions on the border.
// Visually:
// | Protected Range |
// [ ==== ==== ======== ==== ...]
//
// Regions (corresponding to diagram):
SparseByteBuffer under_test =
BufferWithRegions({{2, 4}, {9, 4}, {17, 8}, {27, 4}});
// Protected range:
size_t protected_start = 4;
size_t protected_size = 19;
size_t freed = under_test.CleanUpExcept(
/* >= the buffer size so it cleans the most it can */ kSize,
protected_start, protected_size);
// Full expectation layout corresponding to diagram.
EXPECT_EQ(freed, 8u);
ExpectHole(&under_test, 0, 4, under_test.FindHoleContaining(0));
ExpectRegion(&under_test, 4, 2,
under_test.FindRegionContaining(4, under_test.null_region()));
ExpectHole(&under_test, 6, 3, under_test.FindHoleContaining(6));
ExpectRegion(&under_test, 9, 4,
under_test.FindRegionContaining(9, under_test.null_region()));
ExpectHole(&under_test, 13, 4, under_test.FindHoleContaining(13));
ExpectRegion(&under_test, 17, 6,
under_test.FindRegionContaining(17, under_test.null_region()));
ExpectHole(&under_test, 23, kSize - 23, under_test.FindHoleContaining(23));
}
{
// Clean up less than available excess; should truncate excess region after
// protected range.
SparseByteBuffer under_test = BufferWithRegions({{0, 100}, {900, 100}});
size_t freed = under_test.CleanUpExcept(50, 0, 100);
EXPECT_EQ(freed, 50u);
ExpectRegion(
&under_test, 900, 50,
under_test.FindRegionContaining(900, under_test.null_region()));
ExpectHole(&under_test, 950, 50, under_test.FindHoleContaining(950));
}
{
// Clean up a buffer with no regions; ensure graceful return.
SparseByteBuffer under_test;
under_test.Initialize(100);
EXPECT_EQ(under_test.CleanUpExcept(100, 0, 10), 0u);
}
}
TEST(SparseByteBufferTest, ShrinkRegionFront) {
{
// Shrink a region with a hole before it.
SparseByteBuffer under_test = BufferWithRegions({{1, 4}});
SparseByteBuffer::Region result = under_test.ShrinkRegionFront(
under_test.FindRegionContaining(1, under_test.null_region()), 1);
ExpectRegion(&under_test, 2, 3, result);
ExpectHole(&under_test, 0, 2, under_test.FindHoleContaining(0));
}
{
// Shrink a region with a region before it.
SparseByteBuffer under_test = BufferWithRegions({{2, 4}, {6, 4}});
SparseByteBuffer::Region result = under_test.ShrinkRegionFront(
under_test.FindRegionContaining(6, under_test.null_region()), 2);
ExpectRegion(&under_test, 8, 2, result);
ExpectHole(&under_test, 6, 2, under_test.FindHoleContaining(6));
}
{
// Shrink a region totally (free it).
SparseByteBuffer under_test = BufferWithRegions({{2, 4}, {6, 4}, {10, 2}});
under_test.ShrinkRegionFront(
under_test.FindRegionContaining(6, under_test.null_region()), 4);
ExpectHole(&under_test, 6, 4, under_test.FindHoleContaining(6));
}
}
TEST(SparseByteBufferTest, ShrinkRegionBack) {
{
// Shrink a region with a hole after it.
SparseByteBuffer under_test = BufferWithRegions({{0, 4}});
SparseByteBuffer::Region result = under_test.ShrinkRegionBack(
under_test.FindRegionContaining(0, under_test.null_region()), 1);
ExpectRegion(&under_test, 0, 3, result);
ExpectHole(&under_test, 3, kSize - 3, under_test.FindHoleContaining(3));
}
{
// Shrink a region with a region after it.
SparseByteBuffer under_test = BufferWithRegions({{2, 4}, {6, 4}});
SparseByteBuffer::Region result = under_test.ShrinkRegionBack(
under_test.FindRegionContaining(2, under_test.null_region()), 2);
ExpectRegion(&under_test, 2, 2, result);
ExpectHole(&under_test, 4, 2, under_test.FindHoleContaining(4));
}
{
// Shrink a region totally (free it).
SparseByteBuffer under_test = BufferWithRegions({{2, 4}, {6, 4}, {10, 2}});
under_test.ShrinkRegionBack(
under_test.FindRegionContaining(6, under_test.null_region()), 4);
ExpectHole(&under_test, 6, 4, under_test.FindHoleContaining(6));
}
}
} // namespace
} // namespace media_player