// Copyright 2016 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/common/gpu/context_cache_controller.h"

#include <utility>
#include <vector>

#include "base/test/test_mock_time_task_runner.h"
#include "components/viz/test/test_context_provider.h"
#include "components/viz/test/test_context_support.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h"

using ::testing::Mock;
using ::testing::StrictMock;

namespace viz {
namespace {

class MockContextSupport : public TestContextSupport {
 public:
  MockContextSupport() {}
  MOCK_METHOD1(SetAggressivelyFreeResources,
               void(bool aggressively_free_resources));
};

TEST(ContextCacheControllerTest, ScopedVisibilityBasic) {
  StrictMock<MockContextSupport> context_support;
  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
  ContextCacheController cache_controller(&context_support, task_runner);

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  std::unique_ptr<ContextCacheController::ScopedVisibility> visibility =
      cache_controller.ClientBecameVisible();
  Mock::VerifyAndClearExpectations(&context_support);

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  cache_controller.ClientBecameNotVisible(std::move(visibility));
}

TEST(ContextCacheControllerTest, ScopedVisibilityMulti) {
  StrictMock<MockContextSupport> context_support;
  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
  ContextCacheController cache_controller(&context_support, task_runner);

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  auto visibility_1 = cache_controller.ClientBecameVisible();
  Mock::VerifyAndClearExpectations(&context_support);
  auto visibility_2 = cache_controller.ClientBecameVisible();

  cache_controller.ClientBecameNotVisible(std::move(visibility_1));
  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  cache_controller.ClientBecameNotVisible(std::move(visibility_2));
}

// Check that resources aren't freed during shutdown until the
// ContextCacheController is also deleted.
TEST(ContextCacheControllerTest, ScopedVisibilityDuringShutdown) {
  StrictMock<MockContextSupport> context_support;
  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
  std::unique_ptr<ContextCacheController> cache_controller =
      std::make_unique<ContextCacheController>(&context_support, task_runner);

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  std::unique_ptr<ContextCacheController::ScopedVisibility> visibility =
      cache_controller->ClientBecameVisible();
  Mock::VerifyAndClearExpectations(&context_support);

  cache_controller->ClientBecameNotVisibleDuringShutdown(std::move(visibility));

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  cache_controller.reset();
}

// Check that multiple clients can shutdown successfully.
TEST(ContextCacheControllerTest, ScopedVisibilityDuringShutdownMulti) {
  StrictMock<MockContextSupport> context_support;
  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
  std::unique_ptr<ContextCacheController> cache_controller =
      std::make_unique<ContextCacheController>(&context_support, task_runner);

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  auto visibility_1 = cache_controller->ClientBecameVisible();
  Mock::VerifyAndClearExpectations(&context_support);
  auto visibility_2 = cache_controller->ClientBecameVisible();

  cache_controller->ClientBecameNotVisibleDuringShutdown(
      std::move(visibility_1));
  cache_controller->ClientBecameNotVisibleDuringShutdown(
      std::move(visibility_2));

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  cache_controller.reset();
}

TEST(ContextCacheControllerTest, ScopedBusyWhileVisible) {
  StrictMock<MockContextSupport> context_support;
  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
  ContextCacheController cache_controller(&context_support, task_runner);

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  auto visibility = cache_controller.ClientBecameVisible();
  Mock::VerifyAndClearExpectations(&context_support);

  // Now that we're visible, ensure that going idle triggers a delayed cleanup.
  auto busy = cache_controller.ClientBecameBusy();
  cache_controller.ClientBecameNotBusy(std::move(busy));

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  task_runner->FastForwardBy(base::Seconds(5));
  Mock::VerifyAndClearExpectations(&context_support);

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  cache_controller.ClientBecameNotVisible(std::move(visibility));
}

TEST(ContextCacheControllerTest, ScopedBusyWhileNotVisible) {
  StrictMock<MockContextSupport> context_support;
  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
  ContextCacheController cache_controller(&context_support, task_runner);

  auto busy = cache_controller.ClientBecameBusy();

  // We are not visible, so becoming busy should not trigger an idle callback.
  cache_controller.ClientBecameNotBusy(std::move(busy));
  task_runner->FastForwardBy(base::Seconds(5));
}

TEST(ContextCacheControllerTest, ScopedBusyMulitpleWhileVisible) {
  StrictMock<MockContextSupport> context_support;
  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
  ContextCacheController cache_controller(&context_support, task_runner);

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  auto visible = cache_controller.ClientBecameVisible();
  Mock::VerifyAndClearExpectations(&context_support);

  auto busy_1 = cache_controller.ClientBecameBusy();
  cache_controller.ClientBecameNotBusy(std::move(busy_1));
  auto busy_2 = cache_controller.ClientBecameBusy();
  cache_controller.ClientBecameNotBusy(std::move(busy_2));

  // When we fast forward, only one cleanup should happen.
  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  task_runner->FastForwardBy(base::Seconds(5));
  Mock::VerifyAndClearExpectations(&context_support);

  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  cache_controller.ClientBecameNotVisible(std::move(visible));
}

// Confirms that the Skia performDeferredCleanup API used by the cache
// controller behaves as expected.
TEST(ContextCacheControllerTest, CheckSkiaResourcePurgeAPI) {
  StrictMock<MockContextSupport> context_support;
  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();

  // Must outlive `cache_controller`.
  auto context_provider = TestContextProvider::Create();
  context_provider->BindToCurrentSequence();

  ContextCacheController cache_controller(&context_support, task_runner);
  auto* gr_context = context_provider->GrContext();
  cache_controller.SetGrContext(gr_context);

  // Make us visible.
  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  auto visibility = cache_controller.ClientBecameVisible();
  Mock::VerifyAndClearExpectations(&context_support);

  // Now that we're visible, become busy, create and release a skia resource.
  auto busy = cache_controller.ClientBecameBusy();
  {
    auto image_info = SkImageInfo::MakeN32Premul(200, 200);
    std::vector<uint8_t> image_data(image_info.computeMinByteSize());
    SkPixmap pixmap(image_info, image_data.data(), image_info.minRowBytes());
    auto image = SkImages::RasterFromPixmapCopy(pixmap);
    auto image_gpu = SkImages::TextureFromImage(gr_context, std::move(image));
    gr_context->flushAndSubmit();
  }

  // Ensure we see size taken up for the image (now released, but cached for
  // re-use).
  EXPECT_GT(gr_context->getResourceCachePurgeableBytes(), 0u);

  // Make the client idle and wait for the idle callback to trigger.
  cache_controller.ClientBecameNotBusy(std::move(busy));
  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  EXPECT_CALL(context_support, SetAggressivelyFreeResources(false));
  task_runner->FastForwardBy(base::Seconds(5));
  Mock::VerifyAndClearExpectations(&context_support);

  // The Skia resource cache should now be empty.
  EXPECT_EQ(gr_context->getResourceCachePurgeableBytes(), 0u);

  // Set not-visible.
  EXPECT_CALL(context_support, SetAggressivelyFreeResources(true));
  cache_controller.ClientBecameNotVisible(std::move(visibility));
}

}  // namespace
}  // namespace viz