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

#include "content/browser/indexed_db/instance/leveldb/active_blob_registry.h"

#include <stdint.h>

#include <memory>
#include <set>

#include "base/functional/bind.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content::indexed_db::level_db {
namespace {

struct ReportOutstandingState {
  int true_calls = 0;
  int false_calls = 0;

  bool no_calls() { return true_calls == 0 && false_calls == 0; }
};

void ReportOutstandingBlobs(ReportOutstandingState* state,
                            bool blobs_outstanding) {
  if (blobs_outstanding) {
    ++state->true_calls;
  } else {
    ++state->false_calls;
  }
}

struct UnusedBlob {
  int64_t database_id;
  int64_t blob_number;

  bool operator<(const UnusedBlob& other) const {
    if (database_id == other.database_id) {
      return blob_number < other.blob_number;
    }
    return database_id < other.database_id;
  }
};

void ReportUnusedBlob(std::set<UnusedBlob>* unused_blob_records,
                      int64_t database_id,
                      int64_t blob_number) {
  unused_blob_records->insert({database_id, blob_number});
}

// Base class for our test fixtures.
class ActiveBlobRegistryTest : public testing::Test {
 public:
  static const int64_t kDatabaseId0 = 7;
  static const int64_t kDatabaseId1 = 12;
  static const int64_t kBlobNumber0 = 77;
  static const int64_t kBlobNumber1 = 14;

  ActiveBlobRegistryTest()
      : registry_(std::make_unique<ActiveBlobRegistry>(
            base::BindRepeating(ReportOutstandingBlobs,
                                &report_outstanding_state_),
            base::BindRepeating(&ReportUnusedBlob, &unused_blobs_))) {}

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

  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
  ActiveBlobRegistry* registry() const { return registry_.get(); }

 protected:
  ReportOutstandingState report_outstanding_state_;
  std::set<UnusedBlob> unused_blobs_;

 private:
  base::test::TaskEnvironment task_environment_;

  std::unique_ptr<ActiveBlobRegistry> registry_;
};

TEST_F(ActiveBlobRegistryTest, DeleteUnused) {
  EXPECT_TRUE(report_outstanding_state_.no_calls());
  EXPECT_TRUE(unused_blobs_.empty());

  EXPECT_FALSE(registry()->MarkBlobInfoDeletedAndCheckIfReferenced(
      kDatabaseId0, kBlobNumber0));
  RunUntilIdle();

  EXPECT_TRUE(report_outstanding_state_.no_calls());
  EXPECT_TRUE(unused_blobs_.empty());
}

TEST_F(ActiveBlobRegistryTest, SimpleUse) {
  EXPECT_TRUE(report_outstanding_state_.no_calls());
  EXPECT_TRUE(unused_blobs_.empty());

  auto add_ref =
      registry()->GetMarkBlobActiveCallback(kDatabaseId0, kBlobNumber0);
  auto release =
      registry()->GetFinalReleaseCallback(kDatabaseId0, kBlobNumber0);
  std::move(add_ref).Run();
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());

  std::move(release).Run();
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(1, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());
}

TEST_F(ActiveBlobRegistryTest, DeleteWhileInUse) {
  EXPECT_TRUE(report_outstanding_state_.no_calls());
  EXPECT_TRUE(unused_blobs_.empty());

  auto add_ref =
      registry()->GetMarkBlobActiveCallback(kDatabaseId0, kBlobNumber0);
  auto release =
      registry()->GetFinalReleaseCallback(kDatabaseId0, kBlobNumber0);

  std::move(add_ref).Run();
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());

  EXPECT_TRUE(registry()->MarkBlobInfoDeletedAndCheckIfReferenced(
      kDatabaseId0, kBlobNumber0));
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());

  std::move(release).Run();
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(1, report_outstanding_state_.false_calls);
  UnusedBlob unused_blob = {kDatabaseId0, kBlobNumber0};
  EXPECT_EQ(1u, unused_blobs_.size());
  EXPECT_TRUE(unused_blobs_.contains(unused_blob));
}

TEST_F(ActiveBlobRegistryTest, MultipleBlobs) {
  EXPECT_TRUE(report_outstanding_state_.no_calls());
  EXPECT_TRUE(unused_blobs_.empty());

  auto add_ref_00 =
      registry()->GetMarkBlobActiveCallback(kDatabaseId0, kBlobNumber0);
  auto release_00 =
      registry()->GetFinalReleaseCallback(kDatabaseId0, kBlobNumber0);
  auto add_ref_01 =
      registry()->GetMarkBlobActiveCallback(kDatabaseId0, kBlobNumber1);
  auto release_01 =
      registry()->GetFinalReleaseCallback(kDatabaseId0, kBlobNumber1);
  auto add_ref_10 =
      registry()->GetMarkBlobActiveCallback(kDatabaseId1, kBlobNumber0);
  auto release_10 =
      registry()->GetFinalReleaseCallback(kDatabaseId1, kBlobNumber0);
  auto add_ref_11 =
      registry()->GetMarkBlobActiveCallback(kDatabaseId1, kBlobNumber1);
  auto release_11 =
      registry()->GetFinalReleaseCallback(kDatabaseId1, kBlobNumber1);

  std::move(add_ref_00).Run();
  std::move(add_ref_01).Run();
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());

  std::move(release_00).Run();
  std::move(add_ref_10).Run();
  std::move(add_ref_11).Run();
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());

  EXPECT_TRUE(registry()->MarkBlobInfoDeletedAndCheckIfReferenced(
      kDatabaseId0, kBlobNumber1));
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());

  std::move(release_01).Run();
  std::move(release_11).Run();
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  UnusedBlob unused_blob = {kDatabaseId0, kBlobNumber1};
  EXPECT_TRUE(unused_blobs_.contains(unused_blob));
  EXPECT_EQ(1u, unused_blobs_.size());

  std::move(release_10).Run();
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(1, report_outstanding_state_.false_calls);
  unused_blob = {kDatabaseId0, kBlobNumber1};
  EXPECT_TRUE(unused_blobs_.contains(unused_blob));
  EXPECT_EQ(1u, unused_blobs_.size());
}

TEST_F(ActiveBlobRegistryTest, ForceShutdown) {
  EXPECT_TRUE(report_outstanding_state_.no_calls());
  EXPECT_TRUE(unused_blobs_.empty());

  auto add_ref_0 =
      registry()->GetMarkBlobActiveCallback(kDatabaseId0, kBlobNumber0);
  auto release_0 =
      registry()->GetFinalReleaseCallback(kDatabaseId0, kBlobNumber0);
  auto add_ref_1 =
      registry()->GetMarkBlobActiveCallback(kDatabaseId0, kBlobNumber1);
  auto release_1 =
      registry()->GetFinalReleaseCallback(kDatabaseId0, kBlobNumber1);

  std::move(add_ref_0).Run();
  RunUntilIdle();

  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());

  registry()->ForceShutdown();

  std::move(add_ref_1).Run();
  RunUntilIdle();

  // Nothing changes.
  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());

  std::move(release_0).Run();
  std::move(release_1).Run();
  RunUntilIdle();

  // Nothing changes.
  EXPECT_EQ(1, report_outstanding_state_.true_calls);
  EXPECT_EQ(0, report_outstanding_state_.false_calls);
  EXPECT_TRUE(unused_blobs_.empty());
}

}  // namespace
}  // namespace content::indexed_db::level_db
