// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/prefs/json_pref_store.h"

#include <stdint.h>

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_samples.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/simple_test_clock.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/values.h"
#include "components/prefs/persistent_pref_store_unittest.h"
#include "components/prefs/pref_filter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace {

const char kHomePage[] = "homepage";

const char kReadJson[] =
    "{\n"
    "  \"homepage\": \"http://www.cnn.com\",\n"
    "  \"some_directory\": \"/usr/local/\",\n"
    "  \"tabs\": {\n"
    "    \"new_windows_in_tabs\": true,\n"
    "    \"max_tabs\": 20\n"
    "  }\n"
    "}";

const char kInvalidJson[] = "!@#$%^&";

// Expected output for tests using RunBasicJsonPrefStoreTest().
const char kWriteGolden[] =
    "{\"homepage\":\"http://www.cnn.com\","
     "\"long_int\":{\"pref\":\"214748364842\"},"
     "\"some_directory\":\"/usr/sbin/\","
     "\"tabs\":{\"max_tabs\":10,\"new_windows_in_tabs\":false}}";

// Set the time on the given SimpleTestClock to the given time in minutes.
void SetCurrentTimeInMinutes(double minutes, base::SimpleTestClock* clock) {
  const int32_t kBaseTimeMins = 100;
  clock->SetNow(base::Time::FromDoubleT((kBaseTimeMins + minutes) * 60));
}

// A PrefFilter that will intercept all calls to FilterOnLoad() and hold on
// to the |prefs| until explicitly asked to release them.
class InterceptingPrefFilter : public PrefFilter {
 public:
  InterceptingPrefFilter();
  InterceptingPrefFilter(OnWriteCallbackPair callback_pair);
  ~InterceptingPrefFilter() override;

  // PrefFilter implementation:
  void FilterOnLoad(
      const PostFilterOnLoadCallback& post_filter_on_load_callback,
      std::unique_ptr<base::DictionaryValue> pref_store_contents) override;
  void FilterUpdate(const std::string& path) override {}
  OnWriteCallbackPair FilterSerializeData(
      base::DictionaryValue* pref_store_contents) override {
    return on_write_callback_pair_;
  }
  void OnStoreDeletionFromDisk() override {}

  bool has_intercepted_prefs() const { return intercepted_prefs_ != nullptr; }

  // Finalize an intercepted read, handing |intercepted_prefs_| back to its
  // JsonPrefStore.
  void ReleasePrefs();

 private:
  PostFilterOnLoadCallback post_filter_on_load_callback_;
  std::unique_ptr<base::DictionaryValue> intercepted_prefs_;
  OnWriteCallbackPair on_write_callback_pair_;

  DISALLOW_COPY_AND_ASSIGN(InterceptingPrefFilter);
};

InterceptingPrefFilter::InterceptingPrefFilter() {}

InterceptingPrefFilter::InterceptingPrefFilter(
    OnWriteCallbackPair callback_pair) {
  on_write_callback_pair_ = callback_pair;
}

InterceptingPrefFilter::~InterceptingPrefFilter() {}

void InterceptingPrefFilter::FilterOnLoad(
    const PostFilterOnLoadCallback& post_filter_on_load_callback,
    std::unique_ptr<base::DictionaryValue> pref_store_contents) {
  post_filter_on_load_callback_ = post_filter_on_load_callback;
  intercepted_prefs_ = std::move(pref_store_contents);
}

void InterceptingPrefFilter::ReleasePrefs() {
  EXPECT_FALSE(post_filter_on_load_callback_.is_null());
  post_filter_on_load_callback_.Run(std::move(intercepted_prefs_), false);
  post_filter_on_load_callback_.Reset();
}

class MockPrefStoreObserver : public PrefStore::Observer {
 public:
  MOCK_METHOD1(OnPrefValueChanged, void (const std::string&));
  MOCK_METHOD1(OnInitializationCompleted, void (bool));
};

class MockReadErrorDelegate : public PersistentPrefStore::ReadErrorDelegate {
 public:
  MOCK_METHOD1(OnError, void(PersistentPrefStore::PrefReadError));
};

enum class CommitPendingWriteMode {
  WITHOUT_CALLBACK,
  WITH_CALLBACK,
};

void CommitPendingWrite(
    JsonPrefStore* pref_store,
    CommitPendingWriteMode commit_pending_write_mode,
    base::test::ScopedTaskEnvironment* scoped_task_environment) {
  if (commit_pending_write_mode == CommitPendingWriteMode::WITHOUT_CALLBACK) {
    pref_store->CommitPendingWrite(OnceClosure());
    scoped_task_environment->RunUntilIdle();
  } else {
    TestCommitPendingWriteWithCallback(pref_store, scoped_task_environment);
  }
}

class JsonPrefStoreTest
    : public testing::TestWithParam<CommitPendingWriteMode> {
 public:
  JsonPrefStoreTest()
      : scoped_task_environment_(
            base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
            base::test::ScopedTaskEnvironment::ExecutionMode::QUEUED) {}

 protected:
  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
  }

  base::test::ScopedTaskEnvironment scoped_task_environment_;

  // The path to temporary directory used to contain the test operations.
  base::ScopedTempDir temp_dir_;

  DISALLOW_COPY_AND_ASSIGN(JsonPrefStoreTest);
};

}  // namespace

// Test fallback behavior for a nonexistent file.
TEST_F(JsonPrefStoreTest, NonExistentFile) {
  base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
  ASSERT_FALSE(PathExists(bogus_input_file));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);
  EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
            pref_store->ReadPrefs());
  EXPECT_FALSE(pref_store->ReadOnly());
}

// Test fallback behavior for an invalid file.
TEST_F(JsonPrefStoreTest, InvalidFile) {
  base::FilePath invalid_file = temp_dir_.GetPath().AppendASCII("invalid.json");
  ASSERT_LT(0, base::WriteFile(invalid_file,
                               kInvalidJson, arraysize(kInvalidJson) - 1));

  auto pref_store = base::MakeRefCounted<JsonPrefStore>(invalid_file);
  EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE,
            pref_store->ReadPrefs());
  EXPECT_FALSE(pref_store->ReadOnly());

  // The file should have been moved aside.
  EXPECT_FALSE(PathExists(invalid_file));
  base::FilePath moved_aside = temp_dir_.GetPath().AppendASCII("invalid.bad");
  EXPECT_TRUE(PathExists(moved_aside));

  std::string moved_aside_contents;
  ASSERT_TRUE(base::ReadFileToString(moved_aside, &moved_aside_contents));
  EXPECT_EQ(kInvalidJson, moved_aside_contents);
}

// This function is used to avoid code duplication while testing synchronous
// and asynchronous version of the JsonPrefStore loading. It validates that the
// given output file's contents matches kWriteGolden.
void RunBasicJsonPrefStoreTest(
    JsonPrefStore* pref_store,
    const base::FilePath& output_file,
    CommitPendingWriteMode commit_pending_write_mode,
    base::test::ScopedTaskEnvironment* scoped_task_environment) {
  const char kNewWindowsInTabs[] = "tabs.new_windows_in_tabs";
  const char kMaxTabs[] = "tabs.max_tabs";
  const char kLongIntPref[] = "long_int.pref";

  std::string cnn("http://www.cnn.com");

  const Value* actual;
  EXPECT_TRUE(pref_store->GetValue(kHomePage, &actual));
  std::string string_value;
  EXPECT_TRUE(actual->GetAsString(&string_value));
  EXPECT_EQ(cnn, string_value);

  const char kSomeDirectory[] = "some_directory";

  EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual));
  base::FilePath::StringType path;
  EXPECT_TRUE(actual->GetAsString(&path));
  EXPECT_EQ(base::FilePath::StringType(FILE_PATH_LITERAL("/usr/local/")), path);
  base::FilePath some_path(FILE_PATH_LITERAL("/usr/sbin/"));

  pref_store->SetValue(kSomeDirectory,
                       std::make_unique<Value>(some_path.value()),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual));
  EXPECT_TRUE(actual->GetAsString(&path));
  EXPECT_EQ(some_path.value(), path);

  // Test reading some other data types from sub-dictionaries.
  EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual));
  bool boolean = false;
  EXPECT_TRUE(actual->GetAsBoolean(&boolean));
  EXPECT_TRUE(boolean);

  pref_store->SetValue(kNewWindowsInTabs, std::make_unique<Value>(false),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual));
  EXPECT_TRUE(actual->GetAsBoolean(&boolean));
  EXPECT_FALSE(boolean);

  EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual));
  int integer = 0;
  EXPECT_TRUE(actual->GetAsInteger(&integer));
  EXPECT_EQ(20, integer);
  pref_store->SetValue(kMaxTabs, std::make_unique<Value>(10),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual));
  EXPECT_TRUE(actual->GetAsInteger(&integer));
  EXPECT_EQ(10, integer);

  pref_store->SetValue(
      kLongIntPref,
      std::make_unique<Value>(base::Int64ToString(214748364842LL)),
      WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  EXPECT_TRUE(pref_store->GetValue(kLongIntPref, &actual));
  EXPECT_TRUE(actual->GetAsString(&string_value));
  int64_t value;
  base::StringToInt64(string_value, &value);
  EXPECT_EQ(214748364842LL, value);

  // Serialize and compare to expected output.
  CommitPendingWrite(pref_store, commit_pending_write_mode,
                     scoped_task_environment);

  std::string output_contents;
  ASSERT_TRUE(base::ReadFileToString(output_file, &output_contents));
  EXPECT_EQ(kWriteGolden, output_contents);
  ASSERT_TRUE(base::DeleteFile(output_file, false));
}

TEST_P(JsonPrefStoreTest, Basic) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_LT(0, base::WriteFile(input_file,
                               kReadJson, arraysize(kReadJson) - 1));

  // Test that the persistent value can be loaded.
  ASSERT_TRUE(PathExists(input_file));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(input_file);
  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
  EXPECT_FALSE(pref_store->ReadOnly());
  EXPECT_TRUE(pref_store->IsInitializationComplete());

  // The JSON file looks like this:
  // {
  //   "homepage": "http://www.cnn.com",
  //   "some_directory": "/usr/local/",
  //   "tabs": {
  //     "new_windows_in_tabs": true,
  //     "max_tabs": 20
  //   }
  // }

  RunBasicJsonPrefStoreTest(pref_store.get(), input_file, GetParam(),
                            &scoped_task_environment_);
}

TEST_P(JsonPrefStoreTest, BasicAsync) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_LT(0, base::WriteFile(input_file,
                               kReadJson, arraysize(kReadJson) - 1));

  // Test that the persistent value can be loaded.
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(input_file);

  {
    MockPrefStoreObserver mock_observer;
    pref_store->AddObserver(&mock_observer);

    MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate;
    pref_store->ReadPrefsAsync(mock_error_delegate);

    EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
    EXPECT_CALL(*mock_error_delegate,
                OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);
    scoped_task_environment_.RunUntilIdle();
    pref_store->RemoveObserver(&mock_observer);

    EXPECT_FALSE(pref_store->ReadOnly());
    EXPECT_TRUE(pref_store->IsInitializationComplete());
  }

  // The JSON file looks like this:
  // {
  //   "homepage": "http://www.cnn.com",
  //   "some_directory": "/usr/local/",
  //   "tabs": {
  //     "new_windows_in_tabs": true,
  //     "max_tabs": 20
  //   }
  // }

  RunBasicJsonPrefStoreTest(pref_store.get(), input_file, GetParam(),
                            &scoped_task_environment_);
}

TEST_P(JsonPrefStoreTest, PreserveEmptyValues) {
  FilePath pref_file = temp_dir_.GetPath().AppendASCII("empty_values.json");

  auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);

  // Set some keys with empty values.
  pref_store->SetValue("list", std::make_unique<base::ListValue>(),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  pref_store->SetValue("dict", std::make_unique<base::DictionaryValue>(),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);

  // Write to file.
  CommitPendingWrite(pref_store.get(), GetParam(), &scoped_task_environment_);

  // Reload.
  pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);
  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
  ASSERT_FALSE(pref_store->ReadOnly());

  // Check values.
  const Value* result = nullptr;
  EXPECT_TRUE(pref_store->GetValue("list", &result));
  EXPECT_TRUE(ListValue().Equals(result));
  EXPECT_TRUE(pref_store->GetValue("dict", &result));
  EXPECT_TRUE(DictionaryValue().Equals(result));
}

// This test is just documenting some potentially non-obvious behavior. It
// shouldn't be taken as normative.
TEST_F(JsonPrefStoreTest, RemoveClearsEmptyParent) {
  FilePath pref_file = temp_dir_.GetPath().AppendASCII("empty_values.json");

  auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);

  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
  dict->SetString("key", "value");
  pref_store->SetValue("dict", std::move(dict),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);

  pref_store->RemoveValue("dict.key",
                          WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);

  const base::Value* retrieved_dict = nullptr;
  bool has_dict = pref_store->GetValue("dict", &retrieved_dict);
  EXPECT_FALSE(has_dict);
}

// Tests asynchronous reading of the file when there is no file.
TEST_F(JsonPrefStoreTest, AsyncNonExistingFile) {
  base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
  ASSERT_FALSE(PathExists(bogus_input_file));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);
  MockPrefStoreObserver mock_observer;
  pref_store->AddObserver(&mock_observer);

  MockReadErrorDelegate *mock_error_delegate = new MockReadErrorDelegate;
  pref_store->ReadPrefsAsync(mock_error_delegate);

  EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
  EXPECT_CALL(*mock_error_delegate,
              OnError(PersistentPrefStore::PREF_READ_ERROR_NO_FILE)).Times(1);
  scoped_task_environment_.RunUntilIdle();
  pref_store->RemoveObserver(&mock_observer);

  EXPECT_FALSE(pref_store->ReadOnly());
}

TEST_P(JsonPrefStoreTest, ReadWithInterceptor) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_LT(0, base::WriteFile(input_file,
                               kReadJson, arraysize(kReadJson) - 1));

  std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
      new InterceptingPrefFilter());
  InterceptingPrefFilter* raw_intercepting_pref_filter_ =
      intercepting_pref_filter.get();
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(
      input_file, base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}),
      std::move(intercepting_pref_filter));

  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE,
            pref_store->ReadPrefs());
  EXPECT_FALSE(pref_store->ReadOnly());

  // The store shouldn't be considered initialized until the interceptor
  // returns.
  EXPECT_TRUE(raw_intercepting_pref_filter_->has_intercepted_prefs());
  EXPECT_FALSE(pref_store->IsInitializationComplete());
  EXPECT_FALSE(pref_store->GetValue(kHomePage, nullptr));

  raw_intercepting_pref_filter_->ReleasePrefs();

  EXPECT_FALSE(raw_intercepting_pref_filter_->has_intercepted_prefs());
  EXPECT_TRUE(pref_store->IsInitializationComplete());
  EXPECT_TRUE(pref_store->GetValue(kHomePage, nullptr));

  // The JSON file looks like this:
  // {
  //   "homepage": "http://www.cnn.com",
  //   "some_directory": "/usr/local/",
  //   "tabs": {
  //     "new_windows_in_tabs": true,
  //     "max_tabs": 20
  //   }
  // }

  RunBasicJsonPrefStoreTest(pref_store.get(), input_file, GetParam(),
                            &scoped_task_environment_);
}

TEST_P(JsonPrefStoreTest, ReadAsyncWithInterceptor) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_LT(0, base::WriteFile(input_file,
                               kReadJson, arraysize(kReadJson) - 1));

  std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
      new InterceptingPrefFilter());
  InterceptingPrefFilter* raw_intercepting_pref_filter_ =
      intercepting_pref_filter.get();
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(
      input_file, base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}),
      std::move(intercepting_pref_filter));

  MockPrefStoreObserver mock_observer;
  pref_store->AddObserver(&mock_observer);

  // Ownership of the |mock_error_delegate| is handed to the |pref_store| below.
  MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate;

  {
    pref_store->ReadPrefsAsync(mock_error_delegate);

    EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(0);
    // EXPECT_CALL(*mock_error_delegate,
    //             OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);
    scoped_task_environment_.RunUntilIdle();

    EXPECT_FALSE(pref_store->ReadOnly());
    EXPECT_TRUE(raw_intercepting_pref_filter_->has_intercepted_prefs());
    EXPECT_FALSE(pref_store->IsInitializationComplete());
    EXPECT_FALSE(pref_store->GetValue(kHomePage, nullptr));
  }

  {
    EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
    // EXPECT_CALL(*mock_error_delegate,
    //             OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);

    raw_intercepting_pref_filter_->ReleasePrefs();

    EXPECT_FALSE(pref_store->ReadOnly());
    EXPECT_FALSE(raw_intercepting_pref_filter_->has_intercepted_prefs());
    EXPECT_TRUE(pref_store->IsInitializationComplete());
    EXPECT_TRUE(pref_store->GetValue(kHomePage, nullptr));
  }

  pref_store->RemoveObserver(&mock_observer);

  // The JSON file looks like this:
  // {
  //   "homepage": "http://www.cnn.com",
  //   "some_directory": "/usr/local/",
  //   "tabs": {
  //     "new_windows_in_tabs": true,
  //     "max_tabs": 20
  //   }
  // }

  RunBasicJsonPrefStoreTest(pref_store.get(), input_file, GetParam(),
                            &scoped_task_environment_);
}

TEST_F(JsonPrefStoreTest, WriteCountHistogramTestBasic) {
  base::HistogramTester histogram_tester;

  SimpleTestClock* test_clock = new SimpleTestClock;
  SetCurrentTimeInMinutes(0, test_clock);
  JsonPrefStore::WriteCountHistogram histogram(
      base::TimeDelta::FromSeconds(10),
      base::FilePath(FILE_PATH_LITERAL("/tmp/Local State")),
      std::unique_ptr<base::Clock>(test_clock));
  int32_t report_interval =
      JsonPrefStore::WriteCountHistogram::kHistogramWriteReportIntervalMins;

  histogram.RecordWriteOccured();

  SetCurrentTimeInMinutes(1.5 * report_interval, test_clock);
  histogram.ReportOutstandingWrites();
  std::unique_ptr<HistogramSamples> samples =
      histogram.GetHistogram()->SnapshotSamples();

  std::string histogram_name = histogram.GetHistogram()->histogram_name();
  histogram_tester.ExpectBucketCount(histogram_name, 1, 1);
  histogram_tester.ExpectTotalCount(histogram_name, 1);

  ASSERT_EQ("Settings.JsonDataWriteCount.Local_State",
            std::string(histogram.GetHistogram()->histogram_name()));
  ASSERT_TRUE(histogram.GetHistogram()->HasConstructionArguments(1, 30, 31));
}

TEST_F(JsonPrefStoreTest, WriteCountHistogramTestSinglePeriod) {
  base::HistogramTester histogram_tester;

  SimpleTestClock* test_clock = new SimpleTestClock;
  SetCurrentTimeInMinutes(0, test_clock);
  JsonPrefStore::WriteCountHistogram histogram(
      base::TimeDelta::FromSeconds(10),
      base::FilePath(FILE_PATH_LITERAL("/tmp/Local State")),
      std::unique_ptr<base::Clock>(test_clock));
  int32_t report_interval =
      JsonPrefStore::WriteCountHistogram::kHistogramWriteReportIntervalMins;

  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(0.5 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(0.7 * report_interval, test_clock);
  histogram.RecordWriteOccured();

  // Nothing should be recorded until the report period has elapsed.
  std::string histogram_name = histogram.GetHistogram()->histogram_name();
  histogram_tester.ExpectTotalCount(histogram_name, 0);

  SetCurrentTimeInMinutes(1.3 * report_interval, test_clock);
  histogram.RecordWriteOccured();

  // Now the report period has elapsed.
  histogram_tester.ExpectBucketCount(histogram_name, 3, 1);
  histogram_tester.ExpectTotalCount(histogram_name, 1);

  // The last write won't be recorded because the second count period hasn't
  // fully elapsed.
  SetCurrentTimeInMinutes(1.5 * report_interval, test_clock);
  histogram.ReportOutstandingWrites();

  histogram_tester.ExpectBucketCount(histogram_name, 3, 1);
  histogram_tester.ExpectTotalCount(histogram_name, 1);
}

TEST_F(JsonPrefStoreTest, WriteCountHistogramTestMultiplePeriods) {
  base::HistogramTester histogram_tester;

  SimpleTestClock* test_clock = new SimpleTestClock;
  SetCurrentTimeInMinutes(0, test_clock);
  JsonPrefStore::WriteCountHistogram histogram(
      base::TimeDelta::FromSeconds(10),
      base::FilePath(FILE_PATH_LITERAL("/tmp/Local State")),
      std::unique_ptr<base::Clock>(test_clock));
  int32_t report_interval =
      JsonPrefStore::WriteCountHistogram::kHistogramWriteReportIntervalMins;

  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(0.5 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(0.7 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(1.3 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(1.5 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(2.1 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(2.5 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(2.7 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(3.3 * report_interval, test_clock);
  histogram.RecordWriteOccured();

  // The last write won't be recorded because the second count period hasn't
  // fully elapsed
  SetCurrentTimeInMinutes(3.5 * report_interval, test_clock);
  histogram.ReportOutstandingWrites();
  std::string histogram_name = histogram.GetHistogram()->histogram_name();
  histogram_tester.ExpectBucketCount(histogram_name, 3, 2);
  histogram_tester.ExpectBucketCount(histogram_name, 2, 1);
  histogram_tester.ExpectTotalCount(histogram_name, 3);
}

TEST_F(JsonPrefStoreTest, WriteCountHistogramTestPeriodWithGaps) {
  base::HistogramTester histogram_tester;

  SimpleTestClock* test_clock = new SimpleTestClock;
  SetCurrentTimeInMinutes(0, test_clock);
  JsonPrefStore::WriteCountHistogram histogram(
      base::TimeDelta::FromSeconds(10),
      base::FilePath(FILE_PATH_LITERAL("/tmp/Local State")),
      std::unique_ptr<base::Clock>(test_clock));
  int32_t report_interval =
      JsonPrefStore::WriteCountHistogram::kHistogramWriteReportIntervalMins;

  // 1 write in the first period.
  histogram.RecordWriteOccured();

  // No writes in the second and third periods.

  // 2 writes in the fourth period.
  SetCurrentTimeInMinutes(3.1 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(3.3 * report_interval, test_clock);
  histogram.RecordWriteOccured();

  // No writes in the fifth period.

  // 3 writes in the sixth period.
  SetCurrentTimeInMinutes(5.1 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(5.3 * report_interval, test_clock);
  histogram.RecordWriteOccured();
  SetCurrentTimeInMinutes(5.5 * report_interval, test_clock);
  histogram.RecordWriteOccured();

  SetCurrentTimeInMinutes(6.1 * report_interval, test_clock);
  histogram.ReportOutstandingWrites();
  std::string histogram_name = histogram.GetHistogram()->histogram_name();
  histogram_tester.ExpectBucketCount(histogram_name, 0, 3);
  histogram_tester.ExpectBucketCount(histogram_name, 1, 1);
  histogram_tester.ExpectBucketCount(histogram_name, 2, 1);
  histogram_tester.ExpectBucketCount(histogram_name, 3, 1);
  histogram_tester.ExpectTotalCount(histogram_name, 6);
}

INSTANTIATE_TEST_CASE_P(
    WithoutCallback,
    JsonPrefStoreTest,
    ::testing::Values(CommitPendingWriteMode::WITHOUT_CALLBACK));
INSTANTIATE_TEST_CASE_P(
    WithCallback,
    JsonPrefStoreTest,
    ::testing::Values(CommitPendingWriteMode::WITH_CALLBACK));

class JsonPrefStoreLossyWriteTest : public JsonPrefStoreTest {
 public:
  JsonPrefStoreLossyWriteTest() = default;

 protected:
  void SetUp() override {
    JsonPrefStoreTest::SetUp();
    test_file_ = temp_dir_.GetPath().AppendASCII("test.json");
  }

  scoped_refptr<JsonPrefStore> CreatePrefStore() {
    return base::MakeRefCounted<JsonPrefStore>(test_file_);
  }

  // Return the ImportantFileWriter for a given JsonPrefStore.
  ImportantFileWriter* GetImportantFileWriter(JsonPrefStore* pref_store) {
    return &(pref_store->writer_);
  }

  // Get the contents of kTestFile. Pumps the message loop before returning the
  // result.
  std::string GetTestFileContents() {
    scoped_task_environment_.RunUntilIdle();
    std::string file_contents;
    ReadFileToString(test_file_, &file_contents);
    return file_contents;
  }

 private:
  base::FilePath test_file_;

  DISALLOW_COPY_AND_ASSIGN(JsonPrefStoreLossyWriteTest);
};

TEST_F(JsonPrefStoreLossyWriteTest, LossyWriteBasic) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Set a normal pref and check that it gets scheduled to be written.
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->SetValue("normal", std::make_unique<base::Value>("normal"),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  ASSERT_TRUE(file_writer->HasPendingWrite());
  file_writer->DoScheduledWrite();
  ASSERT_EQ("{\"normal\":\"normal\"}", GetTestFileContents());
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // Set a lossy pref and check that it is not scheduled to be written.
  // SetValue/RemoveValue.
  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->RemoveValue("lossy", WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // SetValueSilently/RemoveValueSilently.
  pref_store->SetValueSilently("lossy", std::make_unique<base::Value>("lossy"),
                               WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->RemoveValueSilently("lossy",
                                  WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // ReportValueChanged.
  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->ReportValueChanged("lossy",
                                 WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // Call CommitPendingWrite and check that the lossy pref and the normal pref
  // are there with the last values set above.
  pref_store->CommitPendingWrite(base::OnceClosure());
  ASSERT_FALSE(file_writer->HasPendingWrite());
  ASSERT_EQ("{\"lossy\":\"lossy\",\"normal\":\"normal\"}",
            GetTestFileContents());
}

TEST_F(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossyFirst) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Set a lossy pref and check that it is not scheduled to be written.
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // Set a normal pref and check that it is scheduled to be written.
  pref_store->SetValue("normal", std::make_unique<base::Value>("normal"),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  ASSERT_TRUE(file_writer->HasPendingWrite());

  // Call DoScheduledWrite and check both prefs get written.
  file_writer->DoScheduledWrite();
  ASSERT_EQ("{\"lossy\":\"lossy\",\"normal\":\"normal\"}",
            GetTestFileContents());
  ASSERT_FALSE(file_writer->HasPendingWrite());
}

TEST_F(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossySecond) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Set a normal pref and check that it is scheduled to be written.
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->SetValue("normal", std::make_unique<base::Value>("normal"),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  ASSERT_TRUE(file_writer->HasPendingWrite());

  // Set a lossy pref and check that the write is still scheduled.
  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_TRUE(file_writer->HasPendingWrite());

  // Call DoScheduledWrite and check both prefs get written.
  file_writer->DoScheduledWrite();
  ASSERT_EQ("{\"lossy\":\"lossy\",\"normal\":\"normal\"}",
            GetTestFileContents());
  ASSERT_FALSE(file_writer->HasPendingWrite());
}

TEST_F(JsonPrefStoreLossyWriteTest, ScheduleLossyWrite) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Set a lossy pref and check that it is not scheduled to be written.
  pref_store->SetValue("lossy", std::make_unique<base::Value>("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // Schedule pending lossy writes and check that it is scheduled.
  pref_store->SchedulePendingLossyWrites();
  ASSERT_TRUE(file_writer->HasPendingWrite());

  // Call CommitPendingWrite and check that the lossy pref is there with the
  // last value set above.
  pref_store->CommitPendingWrite(base::OnceClosure());
  ASSERT_FALSE(file_writer->HasPendingWrite());
  ASSERT_EQ("{\"lossy\":\"lossy\"}", GetTestFileContents());
}

class SuccessfulWriteReplyObserver {
 public:
  SuccessfulWriteReplyObserver() = default;

  // Returns true if a successful write was observed via on_successful_write()
  // and resets the observation state to false regardless.
  bool GetAndResetObservationState() {
    bool was_successful_write_observed = successful_write_reply_observed_;
    successful_write_reply_observed_ = false;
    return was_successful_write_observed;
  }

  // Register OnWrite() to be called on the next write of |json_pref_store|.
  void ObserveNextWriteCallback(JsonPrefStore* json_pref_store);

  void OnSuccessfulWrite() {
    EXPECT_FALSE(successful_write_reply_observed_);
    successful_write_reply_observed_ = true;
  }

 private:
  bool successful_write_reply_observed_ = false;

  DISALLOW_COPY_AND_ASSIGN(SuccessfulWriteReplyObserver);
};

void SuccessfulWriteReplyObserver::ObserveNextWriteCallback(
    JsonPrefStore* json_pref_store) {
  json_pref_store->RegisterOnNextSuccessfulWriteReply(
      base::Bind(&SuccessfulWriteReplyObserver::OnSuccessfulWrite,
                 base::Unretained(this)));
}

enum WriteCallbackObservationState {
  NOT_CALLED,
  CALLED_WITH_ERROR,
  CALLED_WITH_SUCCESS,
};

class WriteCallbacksObserver {
 public:
  WriteCallbacksObserver() = default;

  // Register OnWrite() to be called on the next write of |json_pref_store|.
  void ObserveNextWriteCallback(JsonPrefStore* json_pref_store);

  // Returns whether OnPreWrite() was called, and resets the observation state
  // to false.
  bool GetAndResetPreWriteObservationState();

  // Returns the |WriteCallbackObservationState| which was observed, then resets
  // it to |NOT_CALLED|.
  WriteCallbackObservationState GetAndResetPostWriteObservationState();

  JsonPrefStore::OnWriteCallbackPair GetCallbackPair() {
    return std::make_pair(
        base::Bind(&WriteCallbacksObserver::OnPreWrite, base::Unretained(this)),
        base::Bind(&WriteCallbacksObserver::OnPostWrite,
                   base::Unretained(this)));
  }

  void OnPreWrite() {
    EXPECT_FALSE(pre_write_called_);
    pre_write_called_ = true;
  }

  void OnPostWrite(bool success) {
    EXPECT_EQ(NOT_CALLED, post_write_observation_state_);
    post_write_observation_state_ =
        success ? CALLED_WITH_SUCCESS : CALLED_WITH_ERROR;
  }

 private:
  bool pre_write_called_ = false;
  WriteCallbackObservationState post_write_observation_state_ = NOT_CALLED;

  DISALLOW_COPY_AND_ASSIGN(WriteCallbacksObserver);
};

void WriteCallbacksObserver::ObserveNextWriteCallback(JsonPrefStore* writer) {
  writer->RegisterOnNextWriteSynchronousCallbacks(GetCallbackPair());
}

bool WriteCallbacksObserver::GetAndResetPreWriteObservationState() {
  bool observation_state = pre_write_called_;
  pre_write_called_ = false;
  return observation_state;
}

WriteCallbackObservationState
WriteCallbacksObserver::GetAndResetPostWriteObservationState() {
  WriteCallbackObservationState state = post_write_observation_state_;
  pre_write_called_ = false;
  post_write_observation_state_ = NOT_CALLED;
  return state;
}

class JsonPrefStoreCallbackTest : public JsonPrefStoreTest {
 public:
  JsonPrefStoreCallbackTest() = default;

 protected:
  void SetUp() override {
    JsonPrefStoreTest::SetUp();
    test_file_ = temp_dir_.GetPath().AppendASCII("test.json");
  }

  scoped_refptr<JsonPrefStore> CreatePrefStore() {
    return base::MakeRefCounted<JsonPrefStore>(test_file_);
  }

  // Return the ImportantFileWriter for a given JsonPrefStore.
  ImportantFileWriter* GetImportantFileWriter(JsonPrefStore* pref_store) {
    return &(pref_store->writer_);
  }

  void TriggerFakeWriteForCallback(JsonPrefStore* pref_store, bool success) {
    JsonPrefStore::PostWriteCallback(
        base::Bind(&JsonPrefStore::RunOrScheduleNextSuccessfulWriteCallback,
                   pref_store->AsWeakPtr()),
        base::Bind(&WriteCallbacksObserver::OnPostWrite,
                   base::Unretained(&write_callback_observer_)),
        base::SequencedTaskRunnerHandle::Get(), success);
  }

  SuccessfulWriteReplyObserver successful_write_reply_observer_;
  WriteCallbacksObserver write_callback_observer_;

 private:
  base::FilePath test_file_;

  DISALLOW_COPY_AND_ASSIGN(JsonPrefStoreCallbackTest);
};

TEST_F(JsonPrefStoreCallbackTest, TestSerializeDataCallbacks) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_LT(0,
            base::WriteFile(input_file, kReadJson, arraysize(kReadJson) - 1));

  std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
      new InterceptingPrefFilter(write_callback_observer_.GetCallbackPair()));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(
      input_file, base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}),
      std::move(intercepting_pref_filter));
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());
  pref_store->SetValue("normal", std::make_unique<base::Value>("normal"),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  file_writer->DoScheduledWrite();

  // The observer should not be invoked right away.
  EXPECT_FALSE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  scoped_task_environment_.RunUntilIdle();

  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());
}

TEST_F(JsonPrefStoreCallbackTest, TestPostWriteCallbacks) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Test RegisterOnNextWriteSynchronousCallbacks after
  // RegisterOnNextSuccessfulWriteReply.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  write_callback_observer_.ObserveNextWriteCallback(pref_store.get());
  file_writer->WriteNow(std::make_unique<std::string>("foo"));
  scoped_task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Test RegisterOnNextSuccessfulWriteReply after
  // RegisterOnNextWriteSynchronousCallbacks.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  write_callback_observer_.ObserveNextWriteCallback(pref_store.get());
  file_writer->WriteNow(std::make_unique<std::string>("foo"));
  scoped_task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Test RegisterOnNextSuccessfulWriteReply only.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  file_writer->WriteNow(std::make_unique<std::string>("foo"));
  scoped_task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_FALSE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Test RegisterOnNextWriteSynchronousCallbacks only.
  write_callback_observer_.ObserveNextWriteCallback(pref_store.get());
  file_writer->WriteNow(std::make_unique<std::string>("foo"));
  scoped_task_environment_.RunUntilIdle();
  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());
}

TEST_F(JsonPrefStoreCallbackTest, TestPostWriteCallbacksWithFakeFailure) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();

  // Confirm that the observers are invoked.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  TriggerFakeWriteForCallback(pref_store.get(), true);
  scoped_task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Confirm that the observation states were reset.
  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Confirm that re-installing the observers works for another write.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  TriggerFakeWriteForCallback(pref_store.get(), true);
  scoped_task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Confirm that the successful observer is not invoked by an unsuccessful
  // write, and that the synchronous observer is invoked.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  TriggerFakeWriteForCallback(pref_store.get(), false);
  scoped_task_environment_.RunUntilIdle();
  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(CALLED_WITH_ERROR,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Do a real write, and confirm that the successful observer was invoked after
  // being set by |PostWriteCallback| by the last TriggerFakeWriteCallback.
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
  file_writer->WriteNow(std::make_unique<std::string>("foo"));
  scoped_task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());
}

TEST_F(JsonPrefStoreCallbackTest, TestPostWriteCallbacksDuringProfileDeath) {
  // Create a JsonPrefStore and attach observers to it, then delete it by making
  // it go out of scope to simulate profile switch or Chrome shutdown.
  {
    scoped_refptr<JsonPrefStore> soon_out_of_scope_pref_store =
        CreatePrefStore();
    ImportantFileWriter* file_writer =
        GetImportantFileWriter(soon_out_of_scope_pref_store.get());
    successful_write_reply_observer_.ObserveNextWriteCallback(
        soon_out_of_scope_pref_store.get());
    write_callback_observer_.ObserveNextWriteCallback(
        soon_out_of_scope_pref_store.get());
    file_writer->WriteNow(std::make_unique<std::string>("foo"));
  }
  scoped_task_environment_.RunUntilIdle();
  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());
}

}  // namespace base
