/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gtest/gtest.h" #include "mozilla/DataStorage.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" #include "nsStreamUtils.h" #include "prtime.h" using namespace mozilla; class psm_DataStorageTest : public ::testing::Test { protected: virtual void SetUp() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); NS_ConvertUTF8toUTF16 testName(testInfo->name()); storage = DataStorage::Get(testName); storage->Init(dataWillPersist); } RefPtr storage; bool dataWillPersist; }; NS_NAMED_LITERAL_CSTRING(testKey, "test"); NS_NAMED_LITERAL_CSTRING(testValue, "value"); NS_NAMED_LITERAL_CSTRING(privateTestValue, "private"); TEST_F(psm_DataStorageTest, GetPutRemove) { EXPECT_TRUE(dataWillPersist); // Test Put/Get on Persistent data EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent)); // Don't re-use testKey / testValue here, to make sure that this works as // expected with objects that have the same semantic value but are not // literally the same object. nsCString result = storage->Get(NS_LITERAL_CSTRING("test"), DataStorage_Persistent); EXPECT_STREQ("value", result.get()); // Get on Temporary/Private data with the same key should give nothing result = storage->Get(testKey, DataStorage_Temporary); EXPECT_TRUE(result.IsEmpty()); result = storage->Get(testKey, DataStorage_Private); EXPECT_TRUE(result.IsEmpty()); // Put with Temporary/Private data shouldn't affect Persistent data NS_NAMED_LITERAL_CSTRING(temporaryTestValue, "temporary"); EXPECT_EQ(NS_OK, storage->Put(testKey, temporaryTestValue, DataStorage_Temporary)); EXPECT_EQ(NS_OK, storage->Put(testKey, privateTestValue, DataStorage_Private)); result = storage->Get(testKey, DataStorage_Temporary); EXPECT_STREQ("temporary", result.get()); result = storage->Get(testKey, DataStorage_Private); EXPECT_STREQ("private", result.get()); result = storage->Get(testKey, DataStorage_Persistent); EXPECT_STREQ("value", result.get()); // Put of a previously-present key overwrites it (if of the same type) NS_NAMED_LITERAL_CSTRING(newValue, "new"); EXPECT_EQ(NS_OK, storage->Put(testKey, newValue, DataStorage_Persistent)); result = storage->Get(testKey, DataStorage_Persistent); EXPECT_STREQ("new", result.get()); // Removal should work storage->Remove(testKey, DataStorage_Temporary); result = storage->Get(testKey, DataStorage_Temporary); EXPECT_TRUE(result.IsEmpty()); // But removing one type shouldn't affect the others result = storage->Get(testKey, DataStorage_Private); EXPECT_STREQ("private", result.get()); result = storage->Get(testKey, DataStorage_Persistent); EXPECT_STREQ("new", result.get()); // Test removing the other types as well storage->Remove(testKey, DataStorage_Private); result = storage->Get(testKey, DataStorage_Private); EXPECT_TRUE(result.IsEmpty()); storage->Remove(testKey, DataStorage_Persistent); result = storage->Get(testKey, DataStorage_Persistent); EXPECT_TRUE(result.IsEmpty()); } TEST_F(psm_DataStorageTest, InputValidation) { EXPECT_TRUE(dataWillPersist); // Keys may not have tabs or newlines EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put(NS_LITERAL_CSTRING("key\thas tab"), testValue, DataStorage_Persistent)); nsCString result = storage->Get(NS_LITERAL_CSTRING("key\thas tab"), DataStorage_Persistent); EXPECT_TRUE(result.IsEmpty()); EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put(NS_LITERAL_CSTRING("key has\nnewline"), testValue, DataStorage_Persistent)); result = storage->Get(NS_LITERAL_CSTRING("keyhas\nnewline"), DataStorage_Persistent); EXPECT_TRUE(result.IsEmpty()); // Values may not have newlines EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put(testKey, NS_LITERAL_CSTRING("value\nhas newline"), DataStorage_Persistent)); result = storage->Get(testKey, DataStorage_Persistent); // Values may have tabs EXPECT_TRUE(result.IsEmpty()); EXPECT_EQ(NS_OK, storage->Put(testKey, NS_LITERAL_CSTRING("val\thas tab; this is ok"), DataStorage_Persistent)); result = storage->Get(testKey, DataStorage_Persistent); EXPECT_STREQ("val\thas tab; this is ok", result.get()); nsCString longKey("a"); for (int i = 0; i < 8; i++) { longKey.Append(longKey); } // A key of length 256 will work EXPECT_EQ(NS_OK, storage->Put(longKey, testValue, DataStorage_Persistent)); result = storage->Get(longKey, DataStorage_Persistent); EXPECT_STREQ("value", result.get()); longKey.Append("a"); // A key longer than that will not work EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put(longKey, testValue, DataStorage_Persistent)); result = storage->Get(longKey, DataStorage_Persistent); EXPECT_TRUE(result.IsEmpty()); nsCString longValue("a"); for (int i = 0; i < 10; i++) { longValue.Append(longValue); } // A value of length 1024 will work EXPECT_EQ(NS_OK, storage->Put(testKey, longValue, DataStorage_Persistent)); result = storage->Get(testKey, DataStorage_Persistent); EXPECT_STREQ(longValue.get(), result.get()); longValue.Append("a"); // A value longer than that will not work storage->Remove(testKey, DataStorage_Persistent); EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put(testKey, longValue, DataStorage_Persistent)); result = storage->Get(testKey, DataStorage_Persistent); EXPECT_TRUE(result.IsEmpty()); } TEST_F(psm_DataStorageTest, Eviction) { EXPECT_TRUE(dataWillPersist); // Eviction is on a per-table basis. Tables shouldn't affect each other. EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent)); for (int i = 0; i < 1025; i++) { EXPECT_EQ(NS_OK, storage->Put(nsPrintfCString("%d", i), nsPrintfCString("%d", i), DataStorage_Temporary)); nsCString result = storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary); EXPECT_STREQ(nsPrintfCString("%d", i).get(), result.get()); } // We don't know which entry got evicted, but we can count them. int entries = 0; for (int i = 0; i < 1025; i++) { nsCString result = storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary); if (!result.IsEmpty()) { entries++; } } EXPECT_EQ(entries, 1024); nsCString result = storage->Get(testKey, DataStorage_Persistent); EXPECT_STREQ("value", result.get()); } TEST_F(psm_DataStorageTest, ClearPrivateData) { EXPECT_TRUE(dataWillPersist); EXPECT_EQ(NS_OK, storage->Put(testKey, privateTestValue, DataStorage_Private)); nsCString result = storage->Get(testKey, DataStorage_Private); EXPECT_STREQ("private", result.get()); storage->Observe(nullptr, "last-pb-context-exited", nullptr); result = storage->Get(testKey, DataStorage_Private); EXPECT_TRUE(result.IsEmpty()); } TEST_F(psm_DataStorageTest, Shutdown) { EXPECT_TRUE(dataWillPersist); EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent)); nsCString result = storage->Get(testKey, DataStorage_Persistent); EXPECT_STREQ("value", result.get()); // Get "now" (in days) close to when the data was last touched, so we won't // get intermittent failures with the day not matching. int64_t microsecondsPerDay = 24 * 60 * 60 * int64_t(PR_USEC_PER_SEC); int32_t nowInDays = int32_t(PR_Now() / microsecondsPerDay); storage->Observe(nullptr, "profile-before-change", nullptr); nsCOMPtr backingFile; EXPECT_EQ(NS_OK, NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(backingFile))); const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); NS_ConvertUTF8toUTF16 testName(testInfo->name()); EXPECT_EQ(NS_OK, backingFile->Append(testName)); nsCOMPtr fileInputStream; EXPECT_EQ(NS_OK, NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), backingFile)); nsCString data; EXPECT_EQ(NS_OK, NS_ConsumeStream(fileInputStream, UINT32_MAX, data)); // The data will be of the form 'test\t0\t\tvalue' EXPECT_STREQ(nsPrintfCString("test\t0\t%d\tvalue\n", nowInDays).get(), data.get()); }