chromium/media/filters/hls_data_source_provider_impl_unittest.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/filters/hls_data_source_provider_impl.h"

#include <memory>
#include <string>
#include <vector>

#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
#include "media/base/mock_media_log.h"
#include "media/filters/hls_test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

using base::test::RunOnceCallback;
using testing::_;
using testing::AtLeast;
using testing::ByMove;
using testing::DoAll;
using testing::Eq;
using testing::Invoke;
using testing::NiceMock;
using testing::NotNull;
using testing::Ref;
using testing::Return;
using testing::SaveArg;
using testing::SetArgPointee;
using testing::StrictMock;

class HlsDataSourceProviderImplUnittest : public testing::Test {
 public:
  ~HlsDataSourceProviderImplUnittest() override = default;
  HlsDataSourceProviderImplUnittest() { RecreateImpl(); }

  void RecreateImpl() {
    auto factory = std::make_unique<MockDataSourceFactory>();
    factory_ = factory.get();
    impl_ = std::make_unique<HlsDataSourceProviderImpl>(std::move(factory));
  }

 protected:
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<HlsDataSourceProviderImpl> impl_;

  raw_ptr<MockDataSourceFactory> factory_;
};

TEST_F(HlsDataSourceProviderImplUnittest, TestReadFromUrlOnce) {
  // The entire read is satisfied, so there is more to read.
  factory_->AddReadExpectation(0, 16384, 16384);
  impl_->ReadFromUrl(
      {GURL("example.com"), std::nullopt},
      base::BindOnce([](HlsDataSourceProvider::ReadResult result) {
        ASSERT_TRUE(result.has_value());
        auto stream = std::move(result).value();
        ASSERT_EQ(stream->read_position(), 16384lu);
        ASSERT_EQ(stream->buffer_size(), 16384lu);
        ASSERT_EQ(stream->max_read_position(), std::nullopt);
        ASSERT_TRUE(stream->CanReadMore());
      }));
  task_environment_.RunUntilIdle();

  // Only got 400 bytes of requested, so the stream is _probably_ ended, but
  // we'd have to read again (and get a 0) to be sure.
  factory_->AddReadExpectation(0, 16384, 400);
  impl_->ReadFromUrl(
      {GURL("example.com"), std::nullopt},
      base::BindOnce([](HlsDataSourceProvider::ReadResult result) {
        ASSERT_TRUE(result.has_value());
        auto stream = std::move(result).value();
        ASSERT_TRUE(stream->CanReadMore());
        ASSERT_EQ(stream->read_position(), 400lu);
        ASSERT_EQ(stream->buffer_size(), 16384lu);
        ASSERT_EQ(stream->max_read_position(), std::nullopt);
      }));
  task_environment_.RunUntilIdle();

  // The data source should only be limited to 4242 total bytes and should start
  // at an offset of 99. The read should be from 99, size of 4242.
  factory_->AddReadExpectation(99, 4242, 4242);
  impl_->ReadFromUrl(
      {GURL("example.com"), hls::types::ByteRange::Validate(4242, 99)},
      base::BindOnce([](HlsDataSourceProvider::ReadResult result) {
        ASSERT_TRUE(result.has_value());
        auto stream = std::move(result).value();
        ASSERT_EQ(stream->read_position(), 4341lu);
        ASSERT_EQ(stream->buffer_size(), 4242lu);
        ASSERT_EQ(stream->max_read_position().value_or(0), 4341lu);
        ASSERT_FALSE(stream->CanReadMore());
      }));
  task_environment_.RunUntilIdle();
}

TEST_F(HlsDataSourceProviderImplUnittest, TestReadFromUrlThenReadAgain) {
  factory_->AddReadExpectation(0, 16384, 16384);
  factory_->AddReadExpectation(16384, 16384, 16384);
  factory_->AddReadExpectation(32768, 16384, 3);
  factory_->AddReadExpectation(32771, 16384, 0);
  impl_->ReadFromUrl(
      {GURL("example.com"), std::nullopt},
      base::BindOnce(
          [](HlsDataSourceProviderImpl* impl_ptr,
             HlsDataSourceProvider::ReadResult result) {
            ASSERT_TRUE(result.has_value());
            auto stream = std::move(result).value();
            ASSERT_EQ(stream->read_position(), 16384lu);
            ASSERT_EQ(stream->buffer_size(), 16384lu);
            ASSERT_TRUE(stream->CanReadMore());

            impl_ptr->ReadFromExistingStream(
                std::move(stream),
                base::BindOnce(
                    [](HlsDataSourceProviderImpl* impl_ptr,
                       HlsDataSourceProvider::ReadResult result) {
                      ASSERT_TRUE(result.has_value());
                      auto stream = std::move(result).value();
                      ASSERT_EQ(stream->read_position(), 32768lu);
                      ASSERT_EQ(stream->buffer_size(), 32768lu);
                      ASSERT_TRUE(stream->CanReadMore());

                      impl_ptr->ReadFromExistingStream(
                          std::move(stream),
                          base::BindOnce(
                              [](HlsDataSourceProviderImpl* impl_ptr,
                                 HlsDataSourceProvider::ReadResult result) {
                                ASSERT_TRUE(result.has_value());
                                auto stream = std::move(result).value();
                                ASSERT_EQ(stream->read_position(), 32771lu);
                                ASSERT_EQ(stream->buffer_size(), 49152lu);
                                ASSERT_TRUE(stream->CanReadMore());

                                impl_ptr->ReadFromExistingStream(
                                    std::move(stream),
                                    base::BindOnce(
                                        [](HlsDataSourceProvider::ReadResult
                                               result) {
                                          ASSERT_TRUE(result.has_value());
                                          auto stream =
                                              std::move(result).value();
                                          ASSERT_EQ(stream->read_position(),
                                                    32771lu);
                                          ASSERT_EQ(stream->buffer_size(),
                                                    32771lu);
                                          ASSERT_FALSE(stream->CanReadMore());
                                        }));
                              },
                              impl_ptr));
                    },
                    impl_ptr));
          },
          impl_.get()));

  task_environment_.RunUntilIdle();
}

TEST_F(HlsDataSourceProviderImplUnittest, TestAbortMidDownload) {
  // Pregenerating the mock requires setting all our own expectations.
  auto* mock_data_source = factory_->PregenerateNextMock();
  EXPECT_CALL(*mock_data_source, Initialize).WillOnce(RunOnceCallback<0>(true));
  EXPECT_CALL(*mock_data_source, Abort()).Times(0);
  EXPECT_CALL(*mock_data_source, Stop()).Times(0);

  DataSource::ReadCB read_cb;
  EXPECT_CALL(*mock_data_source, Read(0, _, _, _))
      .WillOnce(
          [&read_cb](int64_t, int, uint8_t*, DataSource::ReadCB cb) {
            read_cb = std::move(cb);
          });

  // The Read CB is captured, and so will not execute right away.
  bool has_been_read = false;
  impl_->ReadFromUrl(
      {GURL("example.com"), std::nullopt},
      base::BindOnce(
          [](bool* read_canary, HlsDataSourceProvider::ReadResult result) {
            *read_canary = true;
          },
          &has_been_read));

  // cycle everything and check that we are blocking the read.
  task_environment_.RunUntilIdle();
  ASSERT_FALSE(has_been_read);
  ASSERT_TRUE(!!read_cb);

  // Deleting the HlsDataSourceproviderImpl will stop all existing reads.
  EXPECT_CALL(*mock_data_source, Stop());
  RecreateImpl();
  task_environment_.RunUntilIdle();

  // Run with aborted signal.
  std::move(read_cb).Run(-2);

  task_environment_.RunUntilIdle();
  ASSERT_TRUE(has_been_read);
}

TEST_F(HlsDataSourceProviderImplUnittest, AbortMidInit) {
  auto* mock_data_source = factory_->PregenerateNextMock();

  // Don't run init cb!
  EXPECT_CALL(*mock_data_source, Initialize);
  EXPECT_CALL(*mock_data_source, Stop());

  bool has_been_read = false;
  impl_->ReadFromUrl(
      {GURL("example.com"), std::nullopt},
      base::BindOnce(
          [](bool* read_canary, HlsDataSourceProvider::ReadResult result) {
            *read_canary = true;
          },
          &has_been_read));

  // Despite the init never returning, it is stored in the `data_source_map_`
  // and all entries there get stopped on teardown.
  task_environment_.RunUntilIdle();

  // Should be false, because the stream init function won't post it's callback.
  ASSERT_FALSE(has_been_read);
}

TEST_F(HlsDataSourceProviderImplUnittest, ReadMultipleSegments) {
  HlsDataSourceProvider::SegmentQueue segments;
  segments.emplace(GURL("example.com"), std::nullopt);
  segments.emplace(GURL("foo.com"), std::nullopt);

  // Request 16k, but only 4k is read. Another read then happens and the 0 byte
  // EOS read happens.
  factory_->AddReadExpectation(0, 16384, 4096);
  factory_->AddReadExpectation(4096, 16384, 0);

  std::unique_ptr<HlsDataSourceStream> read_result;
  impl_->ReadFromCombinedUrlQueue(
      std::move(segments), base::BindOnce(
                               [](std::unique_ptr<HlsDataSourceStream>* extract,
                                  HlsDataSourceProvider::ReadResult result) {
                                 *extract = std::move(result).value();
                               },
                               &read_result));
  task_environment_.RunUntilIdle();
  ASSERT_NE(read_result, nullptr);
  ASSERT_TRUE(read_result->CanReadMore());
  ASSERT_FALSE(read_result->RequiresNextDataSource());

  impl_->ReadFromExistingStream(
      std::move(read_result),
      base::BindOnce(
          [](std::unique_ptr<HlsDataSourceStream>* extract,
             HlsDataSourceProvider::ReadResult result) {
            *extract = std::move(result).value();
          },
          &read_result));
  task_environment_.RunUntilIdle();
  ASSERT_NE(read_result, nullptr);
  ASSERT_TRUE(read_result->CanReadMore());
  ASSERT_TRUE(read_result->RequiresNextDataSource());

  factory_->AddReadExpectation(0, 16384, 4096);
  factory_->AddReadExpectation(4096, 16384, 0);
  impl_->ReadFromExistingStream(
      std::move(read_result),
      base::BindOnce(
          [](std::unique_ptr<HlsDataSourceStream>* extract,
             HlsDataSourceProvider::ReadResult result) {
            *extract = std::move(result).value();
          },
          &read_result));

  task_environment_.RunUntilIdle();
  ASSERT_NE(read_result, nullptr);
  ASSERT_TRUE(read_result->CanReadMore());
  ASSERT_FALSE(read_result->RequiresNextDataSource());

  impl_->ReadFromExistingStream(
      std::move(read_result),
      base::BindOnce(
          [](std::unique_ptr<HlsDataSourceStream>* extract,
             HlsDataSourceProvider::ReadResult result) {
            *extract = std::move(result).value();
          },
          &read_result));

  task_environment_.RunUntilIdle();
  ASSERT_NE(read_result, nullptr);
  ASSERT_FALSE(read_result->CanReadMore());
  ASSERT_FALSE(read_result->RequiresNextDataSource());

  read_result = nullptr;
  task_environment_.RunUntilIdle();
}

TEST_F(HlsDataSourceProviderImplUnittest, ReadMultipleRangedSegments) {
  HlsDataSourceProvider::SegmentQueue segments;
  // Read 10 bytes from offset 0.
  segments.emplace(GURL("example.com"), hls::types::ByteRange::Validate(10, 0));

  // Read 100 bytes from offset 100
  segments.emplace(GURL("foo.com"), hls::types::ByteRange::Validate(100, 100));

  // Request 10 bytes from the 0 offset. this is fairly common for EXT-X-MAP.
  factory_->AddReadExpectation(0, 10, 10);

  std::unique_ptr<HlsDataSourceStream> read_result;
  impl_->ReadFromCombinedUrlQueue(
      std::move(segments), base::BindOnce(
                               [](std::unique_ptr<HlsDataSourceStream>* extract,
                                  HlsDataSourceProvider::ReadResult result) {
                                 *extract = std::move(result).value();
                               },
                               &read_result));

  task_environment_.RunUntilIdle();
  ASSERT_NE(read_result, nullptr);
  ASSERT_TRUE(read_result->CanReadMore());
  ASSERT_TRUE(read_result->RequiresNextDataSource());

  // The second segment will request another 100 bytes, and again, because it is
  // a range request, more should be returned.
  factory_->AddReadExpectation(100, 100, 100);
  impl_->ReadFromExistingStream(
      std::move(read_result),
      base::BindOnce(
          [](std::unique_ptr<HlsDataSourceStream>* extract,
             HlsDataSourceProvider::ReadResult result) {
            *extract = std::move(result).value();
          },
          &read_result));

  task_environment_.RunUntilIdle();
  ASSERT_NE(read_result, nullptr);
  ASSERT_FALSE(read_result->CanReadMore());
  ASSERT_FALSE(read_result->RequiresNextDataSource());

  read_result = nullptr;
  task_environment_.RunUntilIdle();
}

}  // namespace blink