blob: 3e6e3adbfaa66bbb09829cd79a5b57121e6ddd0c [file] [log] [blame]
// 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/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/location.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/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_task_environment.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}}";
// 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 {
// Basic mode.
WITHOUT_CALLBACK,
// With reply callback.
WITH_CALLBACK,
// With synchronous notify callback (synchronous after the write -- shouldn't
// require pumping messages to observe).
WITH_SYNCHRONOUS_CALLBACK,
};
base::test::ScopedTaskEnvironment::ThreadPoolExecutionMode GetExecutionMode(
CommitPendingWriteMode commit_mode) {
switch (commit_mode) {
case CommitPendingWriteMode::WITHOUT_CALLBACK:
FALLTHROUGH;
case CommitPendingWriteMode::WITH_CALLBACK:
return base::test::ScopedTaskEnvironment::ThreadPoolExecutionMode::QUEUED;
case CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK:
// Synchronous callbacks require async tasks to run on their own.
return base::test::ScopedTaskEnvironment::ThreadPoolExecutionMode::ASYNC;
}
}
void CommitPendingWrite(
JsonPrefStore* pref_store,
CommitPendingWriteMode commit_pending_write_mode,
base::test::ScopedTaskEnvironment* scoped_task_environment) {
switch (commit_pending_write_mode) {
case CommitPendingWriteMode::WITHOUT_CALLBACK: {
pref_store->CommitPendingWrite();
scoped_task_environment->RunUntilIdle();
break;
}
case CommitPendingWriteMode::WITH_CALLBACK: {
TestCommitPendingWriteWithCallback(pref_store, scoped_task_environment);
break;
}
case CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK: {
base::WaitableEvent written;
pref_store->CommitPendingWrite(
base::OnceClosure(),
base::BindOnce(&base::WaitableEvent::Signal, Unretained(&written)));
written.Wait();
break;
}
}
}
class JsonPrefStoreTest
: public testing::TestWithParam<CommitPendingWriteMode> {
public:
JsonPrefStoreTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
GetExecutionMode(GetParam())) {}
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_P(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_P(JsonPrefStoreTest, InvalidFile) {
base::FilePath invalid_file = temp_dir_.GetPath().AppendASCII("invalid.json");
ASSERT_LT(0, base::WriteFile(invalid_file, kInvalidJson,
base::size(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::NumberToString(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, base::size(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, base::size(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_P(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_P(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, base::size(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, 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, base::size(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, 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_);
}
INSTANTIATE_TEST_SUITE_P(
WithoutCallback,
JsonPrefStoreTest,
::testing::Values(CommitPendingWriteMode::WITHOUT_CALLBACK));
INSTANTIATE_TEST_SUITE_P(
WithCallback,
JsonPrefStoreTest,
::testing::Values(CommitPendingWriteMode::WITH_CALLBACK));
INSTANTIATE_TEST_SUITE_P(
WithSynchronousCallback,
JsonPrefStoreTest,
::testing::Values(CommitPendingWriteMode::WITH_SYNCHRONOUS_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_P(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_P(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_P(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_P(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());
}
INSTANTIATE_TEST_SUITE_P(
WithoutCallback,
JsonPrefStoreLossyWriteTest,
::testing::Values(CommitPendingWriteMode::WITHOUT_CALLBACK));
INSTANTIATE_TEST_SUITE_P(
WithReply,
JsonPrefStoreLossyWriteTest,
::testing::Values(CommitPendingWriteMode::WITH_CALLBACK));
INSTANTIATE_TEST_SUITE_P(
WithNotify,
JsonPrefStoreLossyWriteTest,
::testing::Values(CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK));
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 testing::Test {
public:
JsonPrefStoreCallbackTest() = default;
protected:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
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_;
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_{
base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
base::test::ScopedTaskEnvironment::ThreadPoolExecutionMode::QUEUED};
base::ScopedTempDir temp_dir_;
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, base::size(kReadJson) - 1));
std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
new InterceptingPrefFilter(write_callback_observer_.GetCallbackPair()));
auto pref_store = base::MakeRefCounted<JsonPrefStore>(
input_file, 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