1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "gtest/gtest.h"
8
9 #include "mozilla/DataStorage.h"
10 #include "nsAppDirectoryServiceDefs.h"
11 #include "nsDirectoryServiceUtils.h"
12 #include "nsNetUtil.h"
13 #include "nsPrintfCString.h"
14 #include "nsStreamUtils.h"
15 #include "prtime.h"
16
17 using namespace mozilla;
18
19 class psm_DataStorageTest : public ::testing::Test {
20 protected:
SetUp()21 void SetUp() override {
22 const ::testing::TestInfo* const testInfo =
23 ::testing::UnitTest::GetInstance()->current_test_info();
24 NS_ConvertUTF8toUTF16 testName(testInfo->name());
25 storage = DataStorage::GetFromRawFileName(testName);
26 storage->Init();
27 }
28
29 RefPtr<DataStorage> storage;
30 };
31
32 constexpr auto testKey = "test"_ns;
33 constexpr auto testValue = "value"_ns;
34 constexpr auto privateTestValue = "private"_ns;
35
TEST_F(psm_DataStorageTest,GetPutRemove)36 TEST_F(psm_DataStorageTest, GetPutRemove) {
37 // Test Put/Get on Persistent data
38 EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
39 // Don't re-use testKey / testValue here, to make sure that this works as
40 // expected with objects that have the same semantic value but are not
41 // literally the same object.
42 nsCString result = storage->Get("test"_ns, DataStorage_Persistent);
43 EXPECT_STREQ("value", result.get());
44
45 // Get on Temporary/Private data with the same key should give nothing
46 result = storage->Get(testKey, DataStorage_Temporary);
47 EXPECT_TRUE(result.IsEmpty());
48 result = storage->Get(testKey, DataStorage_Private);
49 EXPECT_TRUE(result.IsEmpty());
50
51 // Put with Temporary/Private data shouldn't affect Persistent data
52 constexpr auto temporaryTestValue = "temporary"_ns;
53 EXPECT_EQ(NS_OK,
54 storage->Put(testKey, temporaryTestValue, DataStorage_Temporary));
55 EXPECT_EQ(NS_OK,
56 storage->Put(testKey, privateTestValue, DataStorage_Private));
57 result = storage->Get(testKey, DataStorage_Temporary);
58 EXPECT_STREQ("temporary", result.get());
59 result = storage->Get(testKey, DataStorage_Private);
60 EXPECT_STREQ("private", result.get());
61 result = storage->Get(testKey, DataStorage_Persistent);
62 EXPECT_STREQ("value", result.get());
63
64 // Put of a previously-present key overwrites it (if of the same type)
65 constexpr auto newValue = "new"_ns;
66 EXPECT_EQ(NS_OK, storage->Put(testKey, newValue, DataStorage_Persistent));
67 result = storage->Get(testKey, DataStorage_Persistent);
68 EXPECT_STREQ("new", result.get());
69
70 // Removal should work
71 storage->Remove(testKey, DataStorage_Temporary);
72 result = storage->Get(testKey, DataStorage_Temporary);
73 EXPECT_TRUE(result.IsEmpty());
74 // But removing one type shouldn't affect the others
75 result = storage->Get(testKey, DataStorage_Private);
76 EXPECT_STREQ("private", result.get());
77 result = storage->Get(testKey, DataStorage_Persistent);
78 EXPECT_STREQ("new", result.get());
79 // Test removing the other types as well
80 storage->Remove(testKey, DataStorage_Private);
81 result = storage->Get(testKey, DataStorage_Private);
82 EXPECT_TRUE(result.IsEmpty());
83 storage->Remove(testKey, DataStorage_Persistent);
84 result = storage->Get(testKey, DataStorage_Persistent);
85 EXPECT_TRUE(result.IsEmpty());
86 }
87
TEST_F(psm_DataStorageTest,InputValidation)88 TEST_F(psm_DataStorageTest, InputValidation) {
89 // Keys may not have tabs or newlines
90 EXPECT_EQ(NS_ERROR_INVALID_ARG,
91 storage->Put("key\thas tab"_ns, testValue, DataStorage_Persistent));
92 nsCString result = storage->Get("key\thas tab"_ns, DataStorage_Persistent);
93 EXPECT_TRUE(result.IsEmpty());
94 EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put("key has\nnewline"_ns, testValue,
95 DataStorage_Persistent));
96 result = storage->Get("keyhas\nnewline"_ns, DataStorage_Persistent);
97 EXPECT_TRUE(result.IsEmpty());
98 // Values may not have newlines
99 EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put(testKey, "value\nhas newline"_ns,
100 DataStorage_Persistent));
101 result = storage->Get(testKey, DataStorage_Persistent);
102 // Values may have tabs
103 EXPECT_TRUE(result.IsEmpty());
104 EXPECT_EQ(NS_OK, storage->Put(testKey, "val\thas tab; this is ok"_ns,
105 DataStorage_Persistent));
106 result = storage->Get(testKey, DataStorage_Persistent);
107 EXPECT_STREQ("val\thas tab; this is ok", result.get());
108
109 nsCString longKey("a");
110 for (int i = 0; i < 8; i++) {
111 longKey.Append(longKey);
112 }
113 // A key of length 256 will work
114 EXPECT_EQ(NS_OK, storage->Put(longKey, testValue, DataStorage_Persistent));
115 result = storage->Get(longKey, DataStorage_Persistent);
116 EXPECT_STREQ("value", result.get());
117 longKey.AppendLiteral("a");
118 // A key longer than that will not work
119 EXPECT_EQ(NS_ERROR_INVALID_ARG,
120 storage->Put(longKey, testValue, DataStorage_Persistent));
121 result = storage->Get(longKey, DataStorage_Persistent);
122 EXPECT_TRUE(result.IsEmpty());
123
124 nsCString longValue("a");
125 for (int i = 0; i < 10; i++) {
126 longValue.Append(longValue);
127 }
128 // A value of length 1024 will work
129 EXPECT_EQ(NS_OK, storage->Put(testKey, longValue, DataStorage_Persistent));
130 result = storage->Get(testKey, DataStorage_Persistent);
131 EXPECT_STREQ(longValue.get(), result.get());
132 longValue.AppendLiteral("a");
133 // A value longer than that will not work
134 storage->Remove(testKey, DataStorage_Persistent);
135 EXPECT_EQ(NS_ERROR_INVALID_ARG,
136 storage->Put(testKey, longValue, DataStorage_Persistent));
137 result = storage->Get(testKey, DataStorage_Persistent);
138 EXPECT_TRUE(result.IsEmpty());
139 }
140
TEST_F(psm_DataStorageTest,Eviction)141 TEST_F(psm_DataStorageTest, Eviction) {
142 // Eviction is on a per-table basis. Tables shouldn't affect each other.
143 EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
144 for (int i = 0; i < 1025; i++) {
145 EXPECT_EQ(NS_OK,
146 storage->Put(nsPrintfCString("%d", i), nsPrintfCString("%d", i),
147 DataStorage_Temporary));
148 nsCString result =
149 storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary);
150 EXPECT_STREQ(nsPrintfCString("%d", i).get(), result.get());
151 }
152 // We don't know which entry got evicted, but we can count them.
153 int entries = 0;
154 for (int i = 0; i < 1025; i++) {
155 nsCString result =
156 storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary);
157 if (!result.IsEmpty()) {
158 entries++;
159 }
160 }
161 EXPECT_EQ(entries, 1024);
162 nsCString result = storage->Get(testKey, DataStorage_Persistent);
163 EXPECT_STREQ("value", result.get());
164 }
165
TEST_F(psm_DataStorageTest,ClearPrivateData)166 TEST_F(psm_DataStorageTest, ClearPrivateData) {
167 EXPECT_EQ(NS_OK,
168 storage->Put(testKey, privateTestValue, DataStorage_Private));
169 nsCString result = storage->Get(testKey, DataStorage_Private);
170 EXPECT_STREQ("private", result.get());
171 storage->Observe(nullptr, "last-pb-context-exited", nullptr);
172 result = storage->Get(testKey, DataStorage_Private);
173 EXPECT_TRUE(result.IsEmpty());
174 }
175
TEST_F(psm_DataStorageTest,Shutdown)176 TEST_F(psm_DataStorageTest, Shutdown) {
177 EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
178 nsCString result = storage->Get(testKey, DataStorage_Persistent);
179 EXPECT_STREQ("value", result.get());
180 // Get "now" (in days) close to when the data was last touched, so we won't
181 // get intermittent failures with the day not matching.
182 int64_t microsecondsPerDay = 24 * 60 * 60 * int64_t(PR_USEC_PER_SEC);
183 int32_t nowInDays = int32_t(PR_Now() / microsecondsPerDay);
184 // Simulate shutdown.
185 storage->Observe(nullptr, "profile-before-change", nullptr);
186 nsCOMPtr<nsIFile> backingFile;
187 EXPECT_EQ(NS_OK, NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
188 getter_AddRefs(backingFile)));
189 const ::testing::TestInfo* const testInfo =
190 ::testing::UnitTest::GetInstance()->current_test_info();
191 NS_ConvertUTF8toUTF16 testName(testInfo->name());
192 EXPECT_EQ(NS_OK, backingFile->Append(testName));
193 nsCOMPtr<nsIInputStream> fileInputStream;
194 EXPECT_EQ(NS_OK, NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
195 backingFile));
196 nsCString data;
197 EXPECT_EQ(NS_OK, NS_ConsumeStream(fileInputStream, UINT32_MAX, data));
198 // The data will be of the form 'test\t0\t<days since the epoch>\tvalue'
199 EXPECT_STREQ(nsPrintfCString("test\t0\t%d\tvalue\n", nowInDays).get(),
200 data.get());
201 }
202