chromium/chrome/browser/thumbnail/cc/jpeg_thumbnail_helper_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 "chrome/browser/thumbnail/cc/jpeg_thumbnail_helper.h"

#include <cstring>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/task_environment.h"
#include "chrome/browser/thumbnail/cc/thumbnail.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
#include "ui/gfx/codec/jpeg_codec.h"

namespace thumbnail {
namespace {

constexpr int kDimension = 16;
constexpr int kKiB = 1024;

SkPaint SetupPaint() {
  SkColor colors[] = {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE};
  SkScalar pos[] = {0, SK_Scalar1 / 2, SK_Scalar1};
  SkPaint paint;
  paint.setShader(SkGradientShader::MakeSweep(256, 256, colors, pos, 3));
  return paint;
}

}  // anonymous namespace

class JpegThumbnailHelperTest : public ::testing::Test {
 protected:
  JpegThumbnailHelperTest()
      : task_environment_(
            base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) {}

  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    interface_ = std::make_unique<thumbnail::JpegThumbnailHelper>(
        temp_dir_.GetPath(),
        base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}));
  }

  void TearDown() override {}

  thumbnail::JpegThumbnailHelper& GetInterface() { return *interface_; }
  base::FilePath GetFile(int tab_id) {
    return interface_->GetJpegFilePath(tab_id);
  }

  base::test::TaskEnvironment task_environment_;

 private:
  std::unique_ptr<thumbnail::JpegThumbnailHelper> interface_;
  base::ScopedTempDir temp_dir_;
};

TEST_F(JpegThumbnailHelperTest, CompressThumbnail) {
  // Create a bitmap
  SkBitmap image;
  ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension));
  SkCanvas canvas(image);
  canvas.drawPaint(SetupPaint());
  image.setImmutable();

  // Compress the bitmap
  base::RunLoop loop1;
  base::OnceCallback<void(std::vector<uint8_t>)> once =
      base::BindOnce([](std::vector<uint8_t> jpeg_data) {
        EXPECT_FALSE(jpeg_data.empty());
        auto bitmap =
            gfx::JPEGCodec::Decode(jpeg_data.data(), jpeg_data.size());
        EXPECT_TRUE(bitmap);
        EXPECT_GT(bitmap->width(), 0);
        EXPECT_GT(bitmap->height(), 0);
      }).Then(loop1.QuitClosure());

  GetInterface().Compress(image, std::move(once));
  task_environment_.RunUntilIdle();
  loop1.Run();
}

TEST_F(JpegThumbnailHelperTest, WriteThumbnail) {
  int tab_id = 0;

  // Create a bitmap
  SkBitmap image;
  ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension));
  SkCanvas canvas(image);
  canvas.drawPaint(SetupPaint());
  image.setImmutable();

  constexpr int kCompressionQuality = 97;
  std::vector<uint8_t> data;
  gfx::JPEGCodec::Encode(image, kCompressionQuality, &data);

  // Write the image
  base::RunLoop loop1;
  GetInterface().Write(tab_id, data,
                       base::BindOnce(
                           [](base::OnceClosure quit, bool success) {
                             EXPECT_TRUE(success);
                             std::move(quit).Run();
                           },
                           loop1.QuitClosure()));
  task_environment_.RunUntilIdle();
  loop1.Run();

  base::FilePath file_path = GetFile(tab_id);
  EXPECT_TRUE(base::PathExists(file_path));

  // Compare original data with written data
  std::optional<std::vector<uint8_t>> read_data =
      base::ReadFileToBytes(file_path);
  ASSERT_EQ(data.size(), read_data->size());
  EXPECT_EQ(0, memcmp(data.data(), read_data->data(), data.size()));
}

TEST_F(JpegThumbnailHelperTest, ReadThumbnail) {
  int tab_id = 0;

  // Create a bitmap
  SkBitmap image;
  ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension));
  SkCanvas canvas(image);
  canvas.drawPaint(SetupPaint());
  image.setImmutable();

  constexpr int kCompressionQuality = 97;
  std::vector<uint8_t> data;
  gfx::JPEGCodec::Encode(image, kCompressionQuality, &data);

  // Write the image
  base::FilePath file_path = GetFile(tab_id);
  base::File file(file_path,
                  base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
  file.Write(0, reinterpret_cast<const char*>(data.data()), data.size());

  // Read the image
  base::RunLoop loop1;
  base::OnceCallback<void(std::optional<std::vector<uint8_t>>)> once =
      base::BindOnce([](std::optional<std::vector<uint8_t>> compressed_data) {
        EXPECT_TRUE(compressed_data.has_value());
        EXPECT_FALSE(compressed_data->empty());
        auto bitmap = gfx::JPEGCodec::Decode(compressed_data->data(),
                                             compressed_data->size());
        EXPECT_TRUE(bitmap);
        EXPECT_GT(bitmap->width(), 0);
        EXPECT_GT(bitmap->height(), 0);
      }).Then(loop1.QuitClosure());

  GetInterface().Read(tab_id, std::move(once));
  task_environment_.RunUntilIdle();
  loop1.Run();
}

TEST_F(JpegThumbnailHelperTest, DeleteThumbnail) {
  int tab_id = 0;

  // Create a bitmap
  SkBitmap image;
  ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension));
  SkCanvas canvas(image);
  canvas.drawPaint(SetupPaint());
  image.setImmutable();

  constexpr int kCompressionQuality = 97;
  std::vector<uint8_t> data;
  gfx::JPEGCodec::Encode(image, kCompressionQuality, &data);

  // Write the image
  base::FilePath file_path = GetFile(tab_id);
  base::File file(file_path,
                  base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
  file.Write(0, reinterpret_cast<const char*>(data.data()), data.size());

  // Delete the image
  GetInterface().Delete(tab_id);
  task_environment_.RunUntilIdle();

  // Check deletion occurred
  base::FilePath post_delete_file_path = GetFile(tab_id);
  EXPECT_FALSE(base::PathExists(post_delete_file_path));
}

}  // namespace thumbnail