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

#include "components/viz/service/gl/gpu_service_impl.h"

#include <memory>
#include <tuple>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "components/viz/common/resources/peak_gpu_memory_tracker_util.h"
#include "components/viz/service/gl/mock_gpu_service_impl.h"
#include "components/viz/service/input/peak_gpu_memory_tracker_impl.h"
#include "gpu/config/gpu_info.h"
#include "gpu/ipc/service/gpu_watchdog_thread.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/viz/public/mojom/gpu.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/init/gl_factory.h"

namespace viz {

namespace {

const uint64_t kPeakMemoryMB = 42u;
const uint64_t kPeakMemory = kPeakMemoryMB * 1048576u;

}  // namespace

class GpuServiceTest : public testing::Test {
 public:
  GpuServiceTest()
      : io_thread_("TestIOThread"),
        wait_(base::WaitableEvent::ResetPolicy::MANUAL,
              base::WaitableEvent::InitialState::NOT_SIGNALED) {}

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

  ~GpuServiceTest() override = default;

  MockGpuServiceImpl* mock_gpu_service() {
    return static_cast<MockGpuServiceImpl*>(gpu_service_.get());
  }
  GpuServiceImpl* gpu_service() { return gpu_service_.get(); }

  void DestroyService() { gpu_service_ = nullptr; }

  void BlockIOThread() {
    wait_.Reset();
    io_runner()->PostTask(FROM_HERE, wait_.GetWaitCallbackForTesting());
  }

  void UnblockIOThread() {
    DCHECK(!wait_.IsSignaled());
    wait_.Signal();
  }

  scoped_refptr<base::SingleThreadTaskRunner> io_runner() {
    return io_thread_.task_runner();
  }

  // testing::Test
  void SetUp() override {
    ASSERT_TRUE(io_thread_.Start());
    gpu::GPUInfo gpu_info;
    gpu_info.in_process_gpu = false;
    GpuServiceImpl::InitParams init_params;
    init_params.io_runner = io_thread_.task_runner();
    gpu_service_ = std::make_unique<testing::NiceMock<MockGpuServiceImpl>>(
        gpu::GpuPreferences(), gpu_info, gpu::GpuFeatureInfo(), gpu::GPUInfo(),
        gpu::GpuFeatureInfo(), gfx::GpuExtraInfo(), std::move(init_params));
  }

  void TearDown() override {
    DestroyService();
    base::RunLoop runloop;
    runloop.RunUntilIdle();
    io_thread_.Stop();
  }

  std::optional<base::Process::Priority> priority_;

 private:
  base::Thread io_thread_;
  std::unique_ptr<GpuServiceImpl> gpu_service_;
  base::WaitableEvent wait_;
};

// Tests that GpuServiceImpl can be destroyed before Bind() succeeds on the IO
// thread.
TEST_F(GpuServiceTest, ServiceDestroyedBeforeBind) {
  // Block the IO thread to make sure that the GpuServiceImpl is destroyed
  // before the binding happens on the IO thread.
  mojo::Remote<mojom::GpuService> gpu_service_remote;
  BlockIOThread();
  gpu_service()->Bind(gpu_service_remote.BindNewPipeAndPassReceiver());
  UnblockIOThread();
  DestroyService();
}

// Tests that GpuServiceImpl can be destroyed after Bind() succeeds on the IO
// thread.
TEST_F(GpuServiceTest, ServiceDestroyedAfterBind) {
  mojo::Remote<mojom::GpuService> gpu_service_remote;
  gpu_service()->Bind(gpu_service_remote.BindNewPipeAndPassReceiver());
  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  io_runner()->PostTask(FROM_HERE, base::BindOnce(&base::WaitableEvent::Signal,
                                                  base::Unretained(&wait)));
  wait.Wait();
  DestroyService();
}

// Tests that the visibility callback gets called when visibility changes.
TEST_F(GpuServiceTest, VisibilityCallbackCalled) {
  mojo::Remote<mojom::GpuService> gpu_service_remote;
  gpu_service()->Bind(gpu_service_remote.BindNewPipeAndPassReceiver());

  mojo::PendingRemote<mojom::GpuHost> gpu_host_proxy;
  std::ignore = gpu_host_proxy.InitWithNewPipeAndPassReceiver();
  gpu_service()->InitializeWithHost(
      std::move(gpu_host_proxy), gpu::GpuProcessShmCount(),
      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size()),
      mojom::GpuServiceCreationParams::New());
  gpu_service_remote.FlushForTesting();

  gpu_service()->SetPriorityChangedCallback(base::BindRepeating(
      [](GpuServiceTest* test, base::Process::Priority priority) {
        test->priority_ = priority;
      },
      base::Unretained(this)));
  EXPECT_FALSE(priority_.has_value());

  gpu_service_remote->OnForegrounded();
  gpu_service_remote.FlushForTesting();

  EXPECT_TRUE(priority_.has_value());
  EXPECT_EQ(*priority_, base::Process::Priority::kUserBlocking);

  gpu_service_remote->OnBackgrounded();
  gpu_service_remote.FlushForTesting();

  EXPECT_TRUE(priority_.has_value());
  EXPECT_EQ(*priority_, base::Process::Priority::kBestEffort);
}

// Tests that when a PeakGpuMemoryTracker is destroyed, GpuService properly
// updates the histograms.
TEST_F(GpuServiceTest, PeakGpuMemoryCallback) {
  mojo::Remote<mojom::GpuService> gpu_service_remote;
  gpu_service()->Bind(gpu_service_remote.BindNewPipeAndPassReceiver());

  mojo::PendingRemote<mojom::GpuHost> gpu_host_proxy;
  std::ignore = gpu_host_proxy.InitWithNewPipeAndPassReceiver();
  gpu_service()->InitializeWithHost(
      std::move(gpu_host_proxy), gpu::GpuProcessShmCount(),
      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size()),
      mojom::GpuServiceCreationParams::New());
  gpu_service_remote.FlushForTesting();

  ON_CALL(*mock_gpu_service(), GetPeakMemoryUsageOnMainThread)
      .WillByDefault(
          [](uint32_t sequence_num,
             GpuServiceImpl::GetPeakMemoryUsageCallback callback) {
            ASSERT_EQ(GetPeakMemoryUsageRequestLocation(sequence_num),
                      SequenceLocation::kGpuProcess);
            base::flat_map<gpu::GpuPeakMemoryAllocationSource, uint64_t>
                allocation_per_source;
            allocation_per_source[gpu::GpuPeakMemoryAllocationSource::UNKNOWN] =
                kPeakMemory;
            std::move(callback).Run(kPeakMemory, allocation_per_source);
          });

  base::HistogramTester histogram;
  auto tracker = std::make_unique<PeakGpuMemoryTrackerImpl>(
      PeakGpuMemoryTracker::Usage::PAGE_LOAD, gpu_service());

  // No report in response to creation.
  histogram.ExpectTotalCount("Memory.GPU.PeakMemoryUsage2.PageLoad", 0);
  histogram.ExpectTotalCount(
      "Memory.GPU.PeakMemoryAllocationSource.PageLoad.Unknown", 0);

  // Deleting the tracker should start a request for peak Gpu memory usage,
  // with the callback being a posted task.
  tracker.reset();
  gpu_service_remote.FlushForTesting();

  histogram.ExpectUniqueSample("Memory.GPU.PeakMemoryUsage2.PageLoad",
                               kPeakMemoryMB, 1);
  histogram.ExpectUniqueSample(
      "Memory.GPU.PeakMemoryAllocationSource2.PageLoad.Unknown", kPeakMemoryMB,
      1);
}

}  // namespace viz
