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

#include "services/video_capture/broadcasting_receiver.h"

#include "base/memory/unsafe_shared_memory_region.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::base::test::RunOnceClosure;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::InvokeWithoutArgs;

namespace video_capture {

static const size_t kArbitraryDummyBufferSize = 8u;
static const int kArbitraryBufferId = 123;
static const int kArbitraryFrameFeedbackId = 456;

class StubReadWritePermission final
    : public media::VideoCaptureDevice::Client::Buffer::ScopedAccessPermission {
 public:
  StubReadWritePermission() = default;

  StubReadWritePermission(const StubReadWritePermission&) = delete;
  StubReadWritePermission& operator=(const StubReadWritePermission&) = delete;

  ~StubReadWritePermission() override = default;
};

class BroadcastingReceiverTest : public ::testing::Test {
 public:
  void SetUp() override {
    mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_1;
    mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_2;
    mock_video_frame_handler_1_ = std::make_unique<MockVideoFrameHandler>(
        video_frame_handler_1.InitWithNewPipeAndPassReceiver());
    mock_video_frame_handler_2_ = std::make_unique<MockVideoFrameHandler>(
        video_frame_handler_2.InitWithNewPipeAndPassReceiver());
    client_id_1_ =
        broadcaster_.AddClient(std::move(video_frame_handler_1),
                               media::VideoCaptureBufferType::kSharedMemory);
    client_id_2_ =
        broadcaster_.AddClient(std::move(video_frame_handler_2),
                               media::VideoCaptureBufferType::kSharedMemory);

    shm_region_ =
        base::UnsafeSharedMemoryRegion::Create(kArbitraryDummyBufferSize);
    ASSERT_TRUE(shm_region_.IsValid());
    media::mojom::VideoBufferHandlePtr buffer_handle =
        media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
            std::move(shm_region_));
    broadcaster_.OnNewBuffer(kArbitraryBufferId, std::move(buffer_handle));
  }

  size_t HoldBufferContextSize() {
    return broadcaster_.scoped_access_permissions_by_buffer_context_id_.size();
  }

 protected:
  BroadcastingReceiver broadcaster_;
  std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_1_;
  std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_2_;
  int32_t client_id_1_;
  int32_t client_id_2_;
  base::UnsafeSharedMemoryRegion shm_region_;
  base::test::TaskEnvironment task_environment_;
};

TEST_F(
    BroadcastingReceiverTest,
    HoldsOnToAccessPermissionForRetiredBufferUntilLastClientFinishedConsuming) {
  base::RunLoop frame_arrived_at_video_frame_handler_1;
  base::RunLoop frame_arrived_at_video_frame_handler_2;
  EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _))
      .WillOnce(InvokeWithoutArgs([&frame_arrived_at_video_frame_handler_1]() {
        frame_arrived_at_video_frame_handler_1.Quit();
      }));
  EXPECT_CALL(*mock_video_frame_handler_2_, DoOnFrameReadyInBuffer(_, _, _))
      .WillOnce(InvokeWithoutArgs([&frame_arrived_at_video_frame_handler_2]() {
        frame_arrived_at_video_frame_handler_2.Quit();
      }));
  mock_video_frame_handler_1_->HoldAccessPermissions();
  mock_video_frame_handler_2_->HoldAccessPermissions();

  broadcaster_.OnFrameReadyInBuffer(
      media::ReadyFrameInBuffer(kArbitraryBufferId, kArbitraryFrameFeedbackId,
                                std::make_unique<StubReadWritePermission>(),
                                media::mojom::VideoFrameInfo::New()));

  // mock_video_frame_handler_1_ finishes consuming immediately.
  // mock_video_frame_handler_2_ continues consuming.
  frame_arrived_at_video_frame_handler_1.Run();
  frame_arrived_at_video_frame_handler_2.Run();

  base::RunLoop buffer_retired_arrived_at_video_frame_handler_1;
  base::RunLoop buffer_retired_arrived_at_video_frame_handler_2;
  EXPECT_CALL(*mock_video_frame_handler_1_, DoOnBufferRetired(_))
      .WillOnce(InvokeWithoutArgs(
          [&buffer_retired_arrived_at_video_frame_handler_1]() {
            buffer_retired_arrived_at_video_frame_handler_1.Quit();
          }));
  EXPECT_CALL(*mock_video_frame_handler_2_, DoOnBufferRetired(_))
      .WillOnce(InvokeWithoutArgs(
          [&buffer_retired_arrived_at_video_frame_handler_2]() {
            buffer_retired_arrived_at_video_frame_handler_2.Quit();
          }));

  // Retiring the buffer results in both receivers getting the retired event.
  broadcaster_.OnBufferRetired(kArbitraryBufferId);
  buffer_retired_arrived_at_video_frame_handler_1.Run();
  buffer_retired_arrived_at_video_frame_handler_2.Run();
  base::RunLoop().RunUntilIdle();

  // Despite retiring, the access to the buffer is not released because it is
  // still in use by both handlers.
  DCHECK_EQ(HoldBufferContextSize(), 1u);

  // mock_video_frame_handler_2_ finishes consuming. Access is still not
  // released because of mock_video_frame_handler_1_.
  mock_video_frame_handler_2_->ReleaseAccessedFrames();
  base::RunLoop().RunUntilIdle();
  DCHECK_EQ(HoldBufferContextSize(), 1u);

  // mock_video_frame_handler_1_ finishes consuming. Now the buffer is finally
  // released.
  mock_video_frame_handler_1_->ReleaseAccessedFrames();
  base::RunLoop().RunUntilIdle();
  DCHECK_EQ(HoldBufferContextSize(), 0u);
}

TEST_F(BroadcastingReceiverTest,
       DoesNotHoldOnToAccessPermissionWhenAllClientsAreSuspended) {
  EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _))
      .Times(0);
  EXPECT_CALL(*mock_video_frame_handler_2_, DoOnFrameReadyInBuffer(_, _, _))
      .Times(0);
  mock_video_frame_handler_1_->HoldAccessPermissions();
  mock_video_frame_handler_2_->HoldAccessPermissions();

  broadcaster_.SuspendClient(client_id_1_);
  broadcaster_.SuspendClient(client_id_2_);

  broadcaster_.OnFrameReadyInBuffer(
      media::ReadyFrameInBuffer(kArbitraryBufferId, kArbitraryFrameFeedbackId,
                                std::make_unique<StubReadWritePermission>(),
                                media::mojom::VideoFrameInfo::New()));

  // Because the clients are suspended, frames are automatically released.
  base::RunLoop().RunUntilIdle();
  DCHECK_EQ(HoldBufferContextSize(), 0u);

  // Resume one of the clients and pass another frame.
  broadcaster_.ResumeClient(client_id_2_);
  EXPECT_CALL(*mock_video_frame_handler_2_, DoOnFrameReadyInBuffer(_, _, _))
      .Times(1);
  broadcaster_.OnFrameReadyInBuffer(
      media::ReadyFrameInBuffer(kArbitraryBufferId, kArbitraryFrameFeedbackId,
                                std::make_unique<StubReadWritePermission>(),
                                media::mojom::VideoFrameInfo::New()));

  // This time the buffer is not released automatically.
  base::RunLoop().RunUntilIdle();
  DCHECK_EQ(HoldBufferContextSize(), 1u);

  // Releasing mock_video_frame_handler_2_'s frame is sufficient for the buffer
  // to be released since the frame was never delivered to
  // mock_video_frame_handler_1_.
  mock_video_frame_handler_2_->ReleaseAccessedFrames();
  base::RunLoop().RunUntilIdle();
  DCHECK_EQ(HoldBufferContextSize(), 0u);
}

TEST_F(BroadcastingReceiverTest, AccessPermissionsSurviveStop) {
  // For simplicitly, we only care about the first client in this test.
  broadcaster_.SuspendClient(client_id_2_);
  broadcaster_.OnStarted();

  // In this test, two frame handlers are used. In order to inspect all frame
  // IDs that have released after the first handler is destroyed.
  mock_video_frame_handler_1_->HoldAccessPermissions();

  EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _))
      .Times(1);
  broadcaster_.OnFrameReadyInBuffer(
      media::ReadyFrameInBuffer(kArbitraryBufferId, kArbitraryFrameFeedbackId,
                                std::make_unique<StubReadWritePermission>(),
                                media::mojom::VideoFrameInfo::New()));
  base::RunLoop().RunUntilIdle();

  // The first frame has not been released yet.
  DCHECK_EQ(HoldBufferContextSize(), 1u);

  // Simulate a device restart.
  broadcaster_.OnStopped();
  broadcaster_.OnStarted();
  base::RunLoop().RunUntilIdle();

  // The first frame has still not been released yet.
  DCHECK_EQ(HoldBufferContextSize(), 1u);

  // Receive another frame after device restart.
  base::UnsafeSharedMemoryRegion shm_region2 =
      base::UnsafeSharedMemoryRegion::Create(kArbitraryDummyBufferSize);
  ASSERT_TRUE(shm_region2.IsValid());
  media::mojom::VideoBufferHandlePtr buffer_handle2 =
      media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
          std::move(shm_region2));
  broadcaster_.OnNewBuffer(kArbitraryBufferId + 1, std::move(buffer_handle2));
  EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _))
      .Times(1);
  broadcaster_.OnFrameReadyInBuffer(media::ReadyFrameInBuffer(
      kArbitraryBufferId + 1, kArbitraryFrameFeedbackId + 1,
      std::make_unique<StubReadWritePermission>(),
      media::mojom::VideoFrameInfo::New()));
  base::RunLoop().RunUntilIdle();

  // Neither frame has been released.
  DCHECK_EQ(HoldBufferContextSize(), 2u);

  // Release all frames. This should inform both the old and the new handler.
  mock_video_frame_handler_1_->ReleaseAccessedFrames();
  base::RunLoop().RunUntilIdle();

  // Both buffers should now be released.
  DCHECK_EQ(HoldBufferContextSize(), 0u);
}

TEST_F(BroadcastingReceiverTest, OnNewCaptureVersion) {
  base::RunLoop run_loop_1;
  base::RunLoop run_loop_2;

  // Ignore uninteresting calls.
  EXPECT_CALL(*mock_video_frame_handler_1_, DoOnNewBuffer(_, _))
      .Times(AnyNumber());
  EXPECT_CALL(*mock_video_frame_handler_2_, DoOnNewBuffer(_, _))
      .Times(AnyNumber());

  const media::CaptureVersion capture_version(/*source=*/321,
                                              /*sub_capture=*/456);
  EXPECT_CALL(*mock_video_frame_handler_1_,
              OnNewCaptureVersion(capture_version))
      .WillOnce(RunOnceClosure(run_loop_1.QuitClosure()));
  EXPECT_CALL(*mock_video_frame_handler_2_,
              OnNewCaptureVersion(capture_version))
      .WillOnce(RunOnceClosure(run_loop_2.QuitClosure()));
  broadcaster_.OnNewCaptureVersion(capture_version);

  run_loop_1.Run();
  run_loop_2.Run();
}

}  // namespace video_capture
