// Copyright 2020 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/main/viz_main_impl.h"

#include <memory>
#include <utility>

#include "base/memory/raw_ptr.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_monitor_source.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "components/viz/service/performance_hint/hint_session.h"
#include "gpu/config/gpu_info.h"
#include "gpu/ipc/service/gpu_init.h"
#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_entry_builder.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace viz {

// This mock lets us listen for delegate messages generated by a |VizMainImpl|.
// We use this to check that Viz's task runner is the task runner specified by
// dependency injection.
class MockDelegate : public VizMainImpl::Delegate {
 public:
  MOCK_METHOD1(PostCompositorThreadCreated,
               void(base::SingleThreadTaskRunner*));
  MOCK_METHOD1(PostDisplayCompositorGpuThreadCreated,
               void(base::SingleThreadTaskRunner*));
  MOCK_METHOD0(OnInitializationFailed, void());
  MOCK_METHOD1(OnGpuServiceConnection, void(GpuServiceImpl*));
  MOCK_METHOD0(QuitMainMessageLoop, void());
};

// This mock lets us listen for UKM events to be added. We use this to verify
// that the dependency-injected UKM recorder actually gets used.
class MockUkmRecorder : public ukm::MojoUkmRecorder {
 public:
  explicit MockUkmRecorder(ukm::mojom::UkmRecorderFactory& factory)
      : MojoUkmRecorder(factory) {}

  MOCK_METHOD1(AddEntry, void(ukm::mojom::UkmEntryPtr));
};

class MockVizCompositorThreadRunner : public VizCompositorThreadRunner {
 public:
  explicit MockVizCompositorThreadRunner(
      base::SingleThreadTaskRunner* task_runner)
      : VizCompositorThreadRunner(), task_runner_(task_runner) {}

  base::SingleThreadTaskRunner* task_runner() override { return task_runner_; }
  bool CreateHintSessionFactory(
      base::flat_set<base::PlatformThreadId> thread_ids,
      base::RepeatingClosure* wake_up_closure) override {
    return false;
  }
  void SetIOThreadId(base::PlatformThreadId io_thread_id) override {}
  void SetGpuMainThreadId(base::PlatformThreadId gpu_main_thread_id) override {}
  void NotifyWorkloadIncrease() override {}
  MOCK_METHOD2(CreateFrameSinkManager,
               void(mojom::FrameSinkManagerParamsPtr, GpuServiceImpl*));

 private:
  const raw_ptr<base::SingleThreadTaskRunner> task_runner_;
};

class MockPowerMonitorSource : public base::PowerMonitorSource {
 public:
  explicit MockPowerMonitorSource(bool* leak_guard)
      : base::PowerMonitorSource(), leak_guard_(leak_guard) {
    *leak_guard_ = true;
  }

  ~MockPowerMonitorSource() override { *leak_guard_ = false; }

  base::PowerStateObserver::BatteryPowerStatus GetBatteryPowerStatus()
      const override {
    return base::PowerStateObserver::BatteryPowerStatus::kUnknown;
  }

 private:
  // An external flag to signal as to whether or not this object is still
  // alive.
  raw_ptr<bool> leak_guard_;
};

TEST(VizMainImplTest, OopVizDependencyInjection) {
  VizMainImpl::ExternalDependencies external_deps;
  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
      base::SingleThreadTaskRunner::GetCurrentDefault();

  // |VizMainImpl| is supposed to use the |UkmRecorder| injected through
  // |ExternalDependencies|.

  mojo::Remote<ukm::mojom::UkmRecorderFactory> factory;
  std::ignore = factory.BindNewPipeAndPassReceiver();
  std::unique_ptr<MockUkmRecorder> mock_ukm_recorder =
      std::make_unique<MockUkmRecorder>(*factory);

  EXPECT_CALL(*mock_ukm_recorder, AddEntry);
  external_deps.ukm_recorder = std::move(mock_ukm_recorder);

  MockDelegate mock_delegate;
#if BUILDFLAG(IS_ANDROID)
  // |VizMainImpl| is supposed to use the task runner injected through
  // |ExternalDependencies|. We can check which task runner |VizMainImpl| will
  // use by looking for the task runner reported to the delegate.
  EXPECT_CALL(mock_delegate, PostCompositorThreadCreated(task_runner.get()));
  MockVizCompositorThreadRunner mock_runner(task_runner.get());
  external_deps.viz_compositor_thread_runner = &mock_runner;
#endif

  bool mock_source_is_alive = false;
  external_deps.power_monitor_source =
      std::make_unique<MockPowerMonitorSource>(&mock_source_is_alive);
  ASSERT_TRUE(mock_source_is_alive);

  auto gpu_init = std::make_unique<gpu::GpuInit>();
  // Need to force GpuInit to request an OOP viz; if |GpuInit| stops owning the
  // |GPUInfo|, this const_cast may break.
  const_cast<gpu::GPUInfo&>(gpu_init->gpu_info()).in_process_gpu = false;

  VizMainImpl impl(&mock_delegate, std::move(external_deps),
                   std::move(gpu_init));

  // Generate a UKM event so that the MockRecorder sees it. We use the global
  // recorder from |ukm::UkmRecorder::Get()| because that's the recorder that
  // client code is expected to use. That the correct recorder sees the entry
  // is handled with the EXPECT_CALL of |mock_ukm_recorder|.
  ukm::UkmRecorder* recorder = ukm::UkmRecorder::Get();
  ukm::UkmEntryBuilder builder(ukm::SourceId(42), "Event.ScrollUpdate.Touch");
  builder.SetMetric("TimeToScrollUpdateSwapBegin", 17);
  builder.Record(recorder);

  // Need to shutdown the |PowerMonitor| infrastructure.
  auto* power_monitor = base::PowerMonitor::GetInstance();
  EXPECT_TRUE(power_monitor->IsInitialized());
  power_monitor->ShutdownForTesting();
  // Double-check that we're not leaking the MockPowerMonitorSource
  // instance.
  ASSERT_FALSE(mock_source_is_alive);
}

}  // namespace viz
