1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/prefs/json_pref_store.h"
8 #include "base/file_util.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/memory/ref_counted.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/path_service.h"
14 #include "base/prefs/pref_filter.h"
15 #include "base/run_loop.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/threading/sequenced_worker_pool.h"
20 #include "base/threading/thread.h"
21 #include "base/values.h"
22 #include "testing/gmock/include/gmock/gmock.h"
23 #include "testing/gtest/include/gtest/gtest.h"
28 const char kHomePage[] = "homepage";
30 // A PrefFilter that will intercept all calls to FilterOnLoad() and hold on
31 // to the |prefs| until explicitly asked to release them.
32 class InterceptingPrefFilter : public PrefFilter {
34 InterceptingPrefFilter();
35 virtual ~InterceptingPrefFilter();
37 // PrefFilter implementation:
38 virtual void FilterOnLoad(
39 const PostFilterOnLoadCallback& post_filter_on_load_callback,
40 scoped_ptr<base::DictionaryValue> pref_store_contents) OVERRIDE;
41 virtual void FilterUpdate(const std::string& path) OVERRIDE {}
42 virtual void FilterSerializeData(
43 const base::DictionaryValue* pref_store_contents) OVERRIDE {}
45 bool has_intercepted_prefs() const { return intercepted_prefs_ != NULL; }
47 // Finalize an intercepted read, handing |intercept_prefs_| back to its
52 PostFilterOnLoadCallback post_filter_on_load_callback_;
53 scoped_ptr<base::DictionaryValue> intercepted_prefs_;
55 DISALLOW_COPY_AND_ASSIGN(InterceptingPrefFilter);
58 InterceptingPrefFilter::InterceptingPrefFilter() {}
59 InterceptingPrefFilter::~InterceptingPrefFilter() {}
61 void InterceptingPrefFilter::FilterOnLoad(
62 const PostFilterOnLoadCallback& post_filter_on_load_callback,
63 scoped_ptr<base::DictionaryValue> pref_store_contents) {
64 post_filter_on_load_callback_ = post_filter_on_load_callback;
65 intercepted_prefs_ = pref_store_contents.Pass();
68 void InterceptingPrefFilter::ReleasePrefs() {
69 EXPECT_FALSE(post_filter_on_load_callback_.is_null());
70 post_filter_on_load_callback_.Run(intercepted_prefs_.Pass(), false);
71 post_filter_on_load_callback_.Reset();
74 class MockPrefStoreObserver : public PrefStore::Observer {
76 MOCK_METHOD1(OnPrefValueChanged, void (const std::string&));
77 MOCK_METHOD1(OnInitializationCompleted, void (bool));
80 class MockReadErrorDelegate : public PersistentPrefStore::ReadErrorDelegate {
82 MOCK_METHOD1(OnError, void(PersistentPrefStore::PrefReadError));
87 class JsonPrefStoreTest : public testing::Test {
89 virtual void SetUp() OVERRIDE {
90 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
92 ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &data_dir_));
93 data_dir_ = data_dir_.AppendASCII("prefs");
94 ASSERT_TRUE(PathExists(data_dir_));
97 virtual void TearDown() OVERRIDE {
98 // Make sure all pending tasks have been processed (e.g., deleting the
99 // JsonPrefStore may post write tasks).
100 message_loop_.PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure());
104 // The path to temporary directory used to contain the test operations.
105 base::ScopedTempDir temp_dir_;
106 // The path to the directory where the test data is stored.
107 base::FilePath data_dir_;
108 // A message loop that we can use as the file thread message loop.
109 MessageLoop message_loop_;
112 // Test fallback behavior for a nonexistent file.
113 TEST_F(JsonPrefStoreTest, NonExistentFile) {
114 base::FilePath bogus_input_file = data_dir_.AppendASCII("read.txt");
115 ASSERT_FALSE(PathExists(bogus_input_file));
116 scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore(
118 message_loop_.message_loop_proxy().get(),
119 scoped_ptr<PrefFilter>());
120 EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
121 pref_store->ReadPrefs());
122 EXPECT_FALSE(pref_store->ReadOnly());
125 // Test fallback behavior for an invalid file.
126 TEST_F(JsonPrefStoreTest, InvalidFile) {
127 base::FilePath invalid_file_original = data_dir_.AppendASCII("invalid.json");
128 base::FilePath invalid_file = temp_dir_.path().AppendASCII("invalid.json");
129 ASSERT_TRUE(base::CopyFile(invalid_file_original, invalid_file));
130 scoped_refptr<JsonPrefStore> pref_store =
131 new JsonPrefStore(invalid_file,
132 message_loop_.message_loop_proxy().get(),
133 scoped_ptr<PrefFilter>());
134 EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE,
135 pref_store->ReadPrefs());
136 EXPECT_FALSE(pref_store->ReadOnly());
138 // The file should have been moved aside.
139 EXPECT_FALSE(PathExists(invalid_file));
140 base::FilePath moved_aside = temp_dir_.path().AppendASCII("invalid.bad");
141 EXPECT_TRUE(PathExists(moved_aside));
142 EXPECT_TRUE(TextContentsEqual(invalid_file_original, moved_aside));
145 // This function is used to avoid code duplication while testing synchronous and
146 // asynchronous version of the JsonPrefStore loading.
147 void RunBasicJsonPrefStoreTest(JsonPrefStore* pref_store,
148 const base::FilePath& output_file,
149 const base::FilePath& golden_output_file) {
150 const char kNewWindowsInTabs[] = "tabs.new_windows_in_tabs";
151 const char kMaxTabs[] = "tabs.max_tabs";
152 const char kLongIntPref[] = "long_int.pref";
154 std::string cnn("http://www.cnn.com");
157 EXPECT_TRUE(pref_store->GetValue(kHomePage, &actual));
158 std::string string_value;
159 EXPECT_TRUE(actual->GetAsString(&string_value));
160 EXPECT_EQ(cnn, string_value);
162 const char kSomeDirectory[] = "some_directory";
164 EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual));
165 base::FilePath::StringType path;
166 EXPECT_TRUE(actual->GetAsString(&path));
167 EXPECT_EQ(base::FilePath::StringType(FILE_PATH_LITERAL("/usr/local/")), path);
168 base::FilePath some_path(FILE_PATH_LITERAL("/usr/sbin/"));
170 pref_store->SetValue(kSomeDirectory, new StringValue(some_path.value()));
171 EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual));
172 EXPECT_TRUE(actual->GetAsString(&path));
173 EXPECT_EQ(some_path.value(), path);
175 // Test reading some other data types from sub-dictionaries.
176 EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual));
177 bool boolean = false;
178 EXPECT_TRUE(actual->GetAsBoolean(&boolean));
179 EXPECT_TRUE(boolean);
181 pref_store->SetValue(kNewWindowsInTabs, new FundamentalValue(false));
182 EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual));
183 EXPECT_TRUE(actual->GetAsBoolean(&boolean));
184 EXPECT_FALSE(boolean);
186 EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual));
188 EXPECT_TRUE(actual->GetAsInteger(&integer));
189 EXPECT_EQ(20, integer);
190 pref_store->SetValue(kMaxTabs, new FundamentalValue(10));
191 EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual));
192 EXPECT_TRUE(actual->GetAsInteger(&integer));
193 EXPECT_EQ(10, integer);
195 pref_store->SetValue(kLongIntPref,
196 new StringValue(base::Int64ToString(214748364842LL)));
197 EXPECT_TRUE(pref_store->GetValue(kLongIntPref, &actual));
198 EXPECT_TRUE(actual->GetAsString(&string_value));
200 base::StringToInt64(string_value, &value);
201 EXPECT_EQ(214748364842LL, value);
203 // Serialize and compare to expected output.
204 ASSERT_TRUE(PathExists(golden_output_file));
205 pref_store->CommitPendingWrite();
206 RunLoop().RunUntilIdle();
207 EXPECT_TRUE(TextContentsEqual(golden_output_file, output_file));
208 ASSERT_TRUE(base::DeleteFile(output_file, false));
211 TEST_F(JsonPrefStoreTest, Basic) {
212 ASSERT_TRUE(base::CopyFile(data_dir_.AppendASCII("read.json"),
213 temp_dir_.path().AppendASCII("write.json")));
215 // Test that the persistent value can be loaded.
216 base::FilePath input_file = temp_dir_.path().AppendASCII("write.json");
217 ASSERT_TRUE(PathExists(input_file));
218 scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore(
220 message_loop_.message_loop_proxy().get(),
221 scoped_ptr<PrefFilter>());
222 ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
223 EXPECT_FALSE(pref_store->ReadOnly());
224 EXPECT_TRUE(pref_store->IsInitializationComplete());
226 // The JSON file looks like this:
228 // "homepage": "http://www.cnn.com",
229 // "some_directory": "/usr/local/",
231 // "new_windows_in_tabs": true,
236 RunBasicJsonPrefStoreTest(
237 pref_store.get(), input_file, data_dir_.AppendASCII("write.golden.json"));
240 TEST_F(JsonPrefStoreTest, BasicAsync) {
241 ASSERT_TRUE(base::CopyFile(data_dir_.AppendASCII("read.json"),
242 temp_dir_.path().AppendASCII("write.json")));
244 // Test that the persistent value can be loaded.
245 base::FilePath input_file = temp_dir_.path().AppendASCII("write.json");
246 ASSERT_TRUE(PathExists(input_file));
247 scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore(
249 message_loop_.message_loop_proxy().get(),
250 scoped_ptr<PrefFilter>());
253 MockPrefStoreObserver mock_observer;
254 pref_store->AddObserver(&mock_observer);
256 MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate;
257 pref_store->ReadPrefsAsync(mock_error_delegate);
259 EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
260 EXPECT_CALL(*mock_error_delegate,
261 OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);
262 RunLoop().RunUntilIdle();
263 pref_store->RemoveObserver(&mock_observer);
265 EXPECT_FALSE(pref_store->ReadOnly());
266 EXPECT_TRUE(pref_store->IsInitializationComplete());
269 // The JSON file looks like this:
271 // "homepage": "http://www.cnn.com",
272 // "some_directory": "/usr/local/",
274 // "new_windows_in_tabs": true,
279 RunBasicJsonPrefStoreTest(
280 pref_store.get(), input_file, data_dir_.AppendASCII("write.golden.json"));
283 TEST_F(JsonPrefStoreTest, PreserveEmptyValues) {
284 FilePath pref_file = temp_dir_.path().AppendASCII("empty_values.json");
286 scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore(
288 message_loop_.message_loop_proxy(),
289 scoped_ptr<PrefFilter>());
291 // Set some keys with empty values.
292 pref_store->SetValue("list", new base::ListValue);
293 pref_store->SetValue("dict", new base::DictionaryValue);
296 pref_store->CommitPendingWrite();
297 MessageLoop::current()->RunUntilIdle();
300 pref_store = new JsonPrefStore(
302 message_loop_.message_loop_proxy(),
303 scoped_ptr<PrefFilter>());
304 ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
305 ASSERT_FALSE(pref_store->ReadOnly());
308 const Value* result = NULL;
309 EXPECT_TRUE(pref_store->GetValue("list", &result));
310 EXPECT_TRUE(ListValue().Equals(result));
311 EXPECT_TRUE(pref_store->GetValue("dict", &result));
312 EXPECT_TRUE(DictionaryValue().Equals(result));
315 // This test is just documenting some potentially non-obvious behavior. It
316 // shouldn't be taken as normative.
317 TEST_F(JsonPrefStoreTest, RemoveClearsEmptyParent) {
318 FilePath pref_file = temp_dir_.path().AppendASCII("empty_values.json");
320 scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore(
322 message_loop_.message_loop_proxy(),
323 scoped_ptr<PrefFilter>());
325 base::DictionaryValue* dict = new base::DictionaryValue;
326 dict->SetString("key", "value");
327 pref_store->SetValue("dict", dict);
329 pref_store->RemoveValue("dict.key");
331 const base::Value* retrieved_dict = NULL;
332 bool has_dict = pref_store->GetValue("dict", &retrieved_dict);
333 EXPECT_FALSE(has_dict);
336 // Tests asynchronous reading of the file when there is no file.
337 TEST_F(JsonPrefStoreTest, AsyncNonExistingFile) {
338 base::FilePath bogus_input_file = data_dir_.AppendASCII("read.txt");
339 ASSERT_FALSE(PathExists(bogus_input_file));
340 scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore(
342 message_loop_.message_loop_proxy().get(),
343 scoped_ptr<PrefFilter>());
344 MockPrefStoreObserver mock_observer;
345 pref_store->AddObserver(&mock_observer);
347 MockReadErrorDelegate *mock_error_delegate = new MockReadErrorDelegate;
348 pref_store->ReadPrefsAsync(mock_error_delegate);
350 EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
351 EXPECT_CALL(*mock_error_delegate,
352 OnError(PersistentPrefStore::PREF_READ_ERROR_NO_FILE)).Times(1);
353 RunLoop().RunUntilIdle();
354 pref_store->RemoveObserver(&mock_observer);
356 EXPECT_FALSE(pref_store->ReadOnly());
359 TEST_F(JsonPrefStoreTest, ReadWithInterceptor) {
360 ASSERT_TRUE(base::CopyFile(data_dir_.AppendASCII("read.json"),
361 temp_dir_.path().AppendASCII("write.json")));
363 // Test that the persistent value can be loaded.
364 base::FilePath input_file = temp_dir_.path().AppendASCII("write.json");
365 ASSERT_TRUE(PathExists(input_file));
367 scoped_ptr<InterceptingPrefFilter> intercepting_pref_filter(
368 new InterceptingPrefFilter());
369 InterceptingPrefFilter* raw_intercepting_pref_filter_ =
370 intercepting_pref_filter.get();
371 scoped_refptr<JsonPrefStore> pref_store =
372 new JsonPrefStore(input_file,
373 message_loop_.message_loop_proxy().get(),
374 intercepting_pref_filter.PassAs<PrefFilter>());
376 ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE,
377 pref_store->ReadPrefs());
378 EXPECT_FALSE(pref_store->ReadOnly());
380 // The store shouldn't be considered initialized until the interceptor
382 EXPECT_TRUE(raw_intercepting_pref_filter_->has_intercepted_prefs());
383 EXPECT_FALSE(pref_store->IsInitializationComplete());
384 EXPECT_FALSE(pref_store->GetValue(kHomePage, NULL));
386 raw_intercepting_pref_filter_->ReleasePrefs();
388 EXPECT_FALSE(raw_intercepting_pref_filter_->has_intercepted_prefs());
389 EXPECT_TRUE(pref_store->IsInitializationComplete());
390 EXPECT_TRUE(pref_store->GetValue(kHomePage, NULL));
392 // The JSON file looks like this:
394 // "homepage": "http://www.cnn.com",
395 // "some_directory": "/usr/local/",
397 // "new_windows_in_tabs": true,
402 RunBasicJsonPrefStoreTest(
403 pref_store.get(), input_file, data_dir_.AppendASCII("write.golden.json"));
406 TEST_F(JsonPrefStoreTest, ReadAsyncWithInterceptor) {
407 ASSERT_TRUE(base::CopyFile(data_dir_.AppendASCII("read.json"),
408 temp_dir_.path().AppendASCII("write.json")));
410 // Test that the persistent value can be loaded.
411 base::FilePath input_file = temp_dir_.path().AppendASCII("write.json");
412 ASSERT_TRUE(PathExists(input_file));
414 scoped_ptr<InterceptingPrefFilter> intercepting_pref_filter(
415 new InterceptingPrefFilter());
416 InterceptingPrefFilter* raw_intercepting_pref_filter_ =
417 intercepting_pref_filter.get();
418 scoped_refptr<JsonPrefStore> pref_store =
419 new JsonPrefStore(input_file,
420 message_loop_.message_loop_proxy().get(),
421 intercepting_pref_filter.PassAs<PrefFilter>());
423 MockPrefStoreObserver mock_observer;
424 pref_store->AddObserver(&mock_observer);
426 // Ownership of the |mock_error_delegate| is handed to the |pref_store| below.
427 MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate;
430 pref_store->ReadPrefsAsync(mock_error_delegate);
432 EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(0);
433 // EXPECT_CALL(*mock_error_delegate,
434 // OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);
435 RunLoop().RunUntilIdle();
437 EXPECT_FALSE(pref_store->ReadOnly());
438 EXPECT_TRUE(raw_intercepting_pref_filter_->has_intercepted_prefs());
439 EXPECT_FALSE(pref_store->IsInitializationComplete());
440 EXPECT_FALSE(pref_store->GetValue(kHomePage, NULL));
444 EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
445 // EXPECT_CALL(*mock_error_delegate,
446 // OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);
448 raw_intercepting_pref_filter_->ReleasePrefs();
450 EXPECT_FALSE(pref_store->ReadOnly());
451 EXPECT_FALSE(raw_intercepting_pref_filter_->has_intercepted_prefs());
452 EXPECT_TRUE(pref_store->IsInitializationComplete());
453 EXPECT_TRUE(pref_store->GetValue(kHomePage, NULL));
456 pref_store->RemoveObserver(&mock_observer);
458 // The JSON file looks like this:
460 // "homepage": "http://www.cnn.com",
461 // "some_directory": "/usr/local/",
463 // "new_windows_in_tabs": true,
468 RunBasicJsonPrefStoreTest(
469 pref_store.get(), input_file, data_dir_.AppendASCII("write.golden.json"));