• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <memory>
6 #include <string>
7 
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/logging.h"
12 #include "base/test/task_environment.h"
13 #include "base/time/time.h"
14 #include "components/metrics/structured/lib/arena_persistent_proto.h"
15 #include "components/metrics/structured/lib/persistent_proto.h"
16 #include "components/metrics/structured/lib/persistent_proto_internal.h"
17 #include "components/metrics/structured/lib/proto/key.pb.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 
20 namespace metrics::structured {
21 namespace {
22 
23 // Populate |proto| with some test data.
PopulateTestProto(KeyProto * proto)24 void PopulateTestProto(KeyProto* proto) {
25   proto->set_key("abcdefghijkl");
26   proto->set_last_rotation(12345);
27   proto->set_rotation_period(54321);
28 }
29 
30 // Make a proto with test data.
MakeTestProto()31 KeyProto MakeTestProto() {
32   KeyProto proto;
33   PopulateTestProto(&proto);
34   return proto;
35 }
36 
37 // Returns whether |actual| and |expected| are equal.
ProtoEquals(const KeyProto * actual,const KeyProto * expected)38 bool ProtoEquals(const KeyProto* actual, const KeyProto* expected) {
39   bool equal = true;
40   equal &= actual->key() == expected->key();
41   equal &= actual->last_rotation() == expected->last_rotation();
42   equal &= actual->rotation_period() == expected->rotation_period();
43   return equal;
44 }
45 
WriteDelay()46 base::TimeDelta WriteDelay() {
47   return base::Seconds(0);
48 }
49 
50 template <typename T>
51 class TestCase {
52  public:
53   using PProtoType = T;
54 
TestCase()55   TestCase() { Setup(); }
56   TestCase(const TestCase&) = delete;
57   ~TestCase() = default;
58 
Setup()59   void Setup() { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
60 
GetPath()61   base::FilePath GetPath() {
62     return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("proto"));
63   }
64 
ClearDisk()65   void ClearDisk() {
66     base::DeleteFile(GetPath());
67     ASSERT_FALSE(base::PathExists(GetPath()));
68   }
69 
70   // Read the file at GetPath and parse it as a KeyProto.
ReadFromDisk()71   KeyProto ReadFromDisk() {
72     std::string proto_str;
73     CHECK(base::ReadFileToString(GetPath(), &proto_str));
74     KeyProto proto;
75     CHECK(proto.ParseFromString(proto_str));
76     return proto;
77   }
78 
WriteToDisk(const KeyProto & proto)79   void WriteToDisk(const KeyProto& proto) { WriteToDisk(GetPath(), proto); }
80 
WriteToDisk(const base::FilePath & path,const KeyProto & proto)81   void WriteToDisk(const base::FilePath& path, const KeyProto& proto) {
82     ASSERT_TRUE(base::WriteFile(path, proto.SerializeAsString()));
83   }
84 
OnRead(const ReadStatus status)85   void OnRead(const ReadStatus status) {
86     read_status_ = status;
87     ++read_count_;
88   }
89 
ReadCallback()90   base::OnceCallback<void(ReadStatus)> ReadCallback() {
91     return base::BindOnce(&TestCase::OnRead, base::Unretained(this));
92   }
93 
OnWrite(const WriteStatus status)94   void OnWrite(const WriteStatus status) {
95     ASSERT_EQ(status, WriteStatus::kOk);
96     ++write_count_;
97   }
98 
WriteCallback()99   base::RepeatingCallback<void(WriteStatus)> WriteCallback() {
100     return base::BindRepeating(&TestCase::OnWrite, base::Unretained(this));
101   }
102 
103   // Constructs the proto of type T.
104   T BuildTestProto();
105 
106   // Records the information passed to the callbacks for later expectation.
107   ReadStatus read_status_;
108   int read_count_ = 0;
109   int write_count_ = 0;
110   base::ScopedTempDir temp_dir_;
111 };
112 
113 template <typename T>
BuildTestProto()114 T TestCase<T>::BuildTestProto() {
115   ASSERT_TRUE(false)
116       << "Invalid type parameter, please implement BuildTestProto for T";
117 }
118 
119 template <>
120 PersistentProto<KeyProto>
BuildTestProto()121 TestCase<PersistentProto<KeyProto>>::BuildTestProto() {
122   return PersistentProto<KeyProto>(GetPath(), WriteDelay(), ReadCallback(),
123                                    WriteCallback());
124 }
125 
126 template <>
127 ArenaPersistentProto<KeyProto>
BuildTestProto()128 TestCase<ArenaPersistentProto<KeyProto>>::BuildTestProto() {
129   return ArenaPersistentProto<KeyProto>(GetPath(), WriteDelay(), ReadCallback(),
130                                         WriteCallback());
131 }
132 
133 }  // namespace
134 
135 // Testing suite for any class that is a persistent proto. This is a series of
136 // tests needed by any PersistentProtoInternal implementation. Currently this
137 // includes: PersistentProto and ArenaPersistentProto.
138 template <typename T>
139 class PersistentProtoTest : public testing::Test {
140  public:
Wait()141   void Wait() { task_environment_.RunUntilIdle(); }
142 
BuildTestProto()143   T BuildTestProto() { return test_.BuildTestProto(); }
144 
145   base::test::TaskEnvironment task_environment_{
146       base::test::TaskEnvironment::MainThreadType::UI,
147       base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
148 
149   TestCase<T> test_;
150 };
151 
152 using Implementations =
153     testing::Types<PersistentProto<KeyProto>, ArenaPersistentProto<KeyProto>>;
154 TYPED_TEST_SUITE(PersistentProtoTest, Implementations);
155 
156 // Test that the underlying proto is nullptr until a read is complete, and isn't
157 // after that.
TYPED_TEST(PersistentProtoTest,Initialization)158 TYPED_TEST(PersistentProtoTest, Initialization) {
159   auto pproto = this->BuildTestProto();
160 
161   EXPECT_EQ(pproto.get(), nullptr);
162   this->Wait();
163   EXPECT_NE(pproto.get(), nullptr);
164 }
165 
166 // Test bool conversion and has_value.
TYPED_TEST(PersistentProtoTest,BoolTests)167 TYPED_TEST(PersistentProtoTest, BoolTests) {
168   auto pproto = this->BuildTestProto();
169   EXPECT_EQ(pproto.get(), nullptr);
170   EXPECT_FALSE(pproto);
171   EXPECT_FALSE(pproto.has_value());
172   this->Wait();
173   EXPECT_NE(pproto.get(), nullptr);
174   EXPECT_TRUE(pproto);
175   EXPECT_TRUE(pproto.has_value());
176 }
177 
178 // Test -> and *.
TYPED_TEST(PersistentProtoTest,Getters)179 TYPED_TEST(PersistentProtoTest, Getters) {
180   auto pproto = this->BuildTestProto();
181   this->Wait();
182   // We're really just checking these don't crash.
183   EXPECT_EQ(pproto->last_rotation(), 0);
184   KeyProto val = *pproto;
185 }
186 
187 // Test that the pproto correctly saves the in-memory proto to disk.
TYPED_TEST(PersistentProtoTest,Read)188 TYPED_TEST(PersistentProtoTest, Read) {
189   auto pproto = this->BuildTestProto();
190 
191   // Underlying proto should be nullptr until read is complete.
192   EXPECT_EQ(pproto.get(), nullptr);
193 
194   this->Wait();
195   EXPECT_EQ(this->test_.read_status_, ReadStatus::kMissing);
196   EXPECT_EQ(this->test_.read_count_, 1);
197   EXPECT_EQ(this->test_.write_count_, 1);
198 
199   PopulateTestProto(pproto.get());
200   pproto.StartWriteForTesting();
201   this->Wait();
202   EXPECT_EQ(this->test_.write_count_, 2);
203 
204   KeyProto written = this->test_.ReadFromDisk();
205   EXPECT_TRUE(ProtoEquals(&written, pproto.get()));
206 }
207 
208 // Test that invalid files on disk are handled correctly.
TYPED_TEST(PersistentProtoTest,ReadInvalidProto)209 TYPED_TEST(PersistentProtoTest, ReadInvalidProto) {
210   ASSERT_TRUE(
211       base::WriteFile(this->test_.GetPath(), "this isn't a valid proto"));
212 
213   auto pproto = this->BuildTestProto();
214 
215   this->Wait();
216   EXPECT_EQ(this->test_.read_status_, ReadStatus::kParseError);
217   EXPECT_EQ(this->test_.read_count_, 1);
218   EXPECT_EQ(this->test_.write_count_, 1);
219 }
220 
221 // Test that the pproto correctly loads an on-disk proto into memory.
TYPED_TEST(PersistentProtoTest,Write)222 TYPED_TEST(PersistentProtoTest, Write) {
223   const auto test_proto = MakeTestProto();
224   this->test_.WriteToDisk(test_proto);
225 
226   auto pproto = this->BuildTestProto();
227 
228   EXPECT_EQ(pproto.get(), nullptr);
229 
230   this->Wait();
231   EXPECT_EQ(this->test_.read_status_, ReadStatus::kOk);
232   EXPECT_EQ(this->test_.read_count_, 1);
233   EXPECT_EQ(this->test_.write_count_, 0);
234   EXPECT_NE(pproto.get(), nullptr);
235   EXPECT_TRUE(ProtoEquals(pproto.get(), &test_proto));
236 }
237 
238 // Test that several saves all happen correctly.
TYPED_TEST(PersistentProtoTest,MultipleWrites)239 TYPED_TEST(PersistentProtoTest, MultipleWrites) {
240   auto pproto = this->BuildTestProto();
241 
242   EXPECT_EQ(pproto.get(), nullptr);
243 
244   this->Wait();
245   EXPECT_EQ(this->test_.write_count_, 1);
246 
247   for (int i = 1; i <= 10; ++i) {
248     pproto.get()->set_last_rotation(i * i);
249     pproto.StartWriteForTesting();
250     this->Wait();
251     EXPECT_EQ(this->test_.write_count_, i + 1);
252 
253     KeyProto written = this->test_.ReadFromDisk();
254     ASSERT_EQ(written.last_rotation(), i * i);
255   }
256 }
257 
258 // Test that many calls to QueueWrite get batched, leading to only one real
259 // write.
TYPED_TEST(PersistentProtoTest,QueueWrites)260 TYPED_TEST(PersistentProtoTest, QueueWrites) {
261   auto pproto = this->BuildTestProto();
262 
263   this->Wait();
264   EXPECT_EQ(this->test_.write_count_, 1);
265 
266   // Three successive StartWrite calls result in three writes.
267   this->test_.write_count_ = 0;
268   for (int i = 0; i < 3; ++i) {
269     pproto.StartWriteForTesting();
270   }
271   this->Wait();
272   EXPECT_EQ(this->test_.write_count_, 3);
273 
274   // Three successive QueueWrite calls results in one write.
275   this->test_.write_count_ = 0;
276   for (int i = 0; i < 3; ++i) {
277     pproto.QueueWrite();
278   }
279   this->Wait();
280   EXPECT_EQ(this->test_.write_count_, 1);
281 }
282 
TYPED_TEST(PersistentProtoTest,ClearContents)283 TYPED_TEST(PersistentProtoTest, ClearContents) {
284   const auto test_proto = MakeTestProto();
285   this->test_.WriteToDisk(test_proto);
286 
287   {
288     auto pproto = this->BuildTestProto();
289 
290     EXPECT_EQ(pproto.get(), nullptr);
291 
292     this->Wait();
293     EXPECT_EQ(this->test_.read_status_, ReadStatus::kOk);
294     EXPECT_EQ(this->test_.read_count_, 1);
295     EXPECT_EQ(this->test_.write_count_, 0);
296 
297     pproto->Clear();
298     pproto.QueueWrite();
299   }
300 
301   this->Wait();
302 
303   std::string empty_proto;
304   KeyProto().SerializeToString(&empty_proto);
305 
306   std::optional<int64_t> size = base::GetFileSize(this->test_.GetPath());
307   ASSERT_TRUE(size.has_value());
308   EXPECT_EQ(size.value(), static_cast<int64_t>(empty_proto.size()));
309 }
310 
TYPED_TEST(PersistentProtoTest,UpdatePath)311 TYPED_TEST(PersistentProtoTest, UpdatePath) {
312   const base::FilePath new_path =
313       this->test_.temp_dir_.GetPath().Append(FILE_PATH_LITERAL("new_proto"));
314   const int64_t kNewLastRotation = 10;
315 
316   const auto test_proto = MakeTestProto();
317   this->test_.WriteToDisk(test_proto);
318 
319   auto test_proto2 = MakeTestProto();
320   test_proto2.set_last_rotation(kNewLastRotation);
321   this->test_.WriteToDisk(new_path, test_proto2);
322 
323   auto pproto = this->BuildTestProto();
324 
325   // Underlying proto should be nullptr until read is complete.
326   EXPECT_EQ(pproto.get(), nullptr);
327 
328   this->Wait();
329   EXPECT_EQ(this->test_.read_status_, ReadStatus::kOk);
330   EXPECT_EQ(this->test_.read_count_, 1);
331   EXPECT_EQ(this->test_.write_count_, 0);
332 
333   const KeyProto* ptr = pproto.get();
334 
335   pproto.UpdatePath(new_path, this->test_.ReadCallback(),
336                     /*remove_existing=*/true);
337   this->Wait();
338 
339   EXPECT_EQ(this->test_.read_status_, ReadStatus::kOk);
340   EXPECT_EQ(this->test_.read_count_, 2);
341   EXPECT_EQ(this->test_.write_count_, 1);
342 
343   // It is expected that the underlying proto doesn't change.
344   EXPECT_EQ(ptr, pproto.get());
345 
346   // Check the content of the updated proto.
347   EXPECT_EQ(ptr->key(), test_proto.key());
348   EXPECT_EQ(ptr->rotation_period(), test_proto.rotation_period());
349   EXPECT_EQ(ptr->last_rotation(), kNewLastRotation);
350 
351   // Check the state of the files are what we expect.
352   ASSERT_FALSE(base::PathExists(this->test_.GetPath()));
353   ASSERT_TRUE(base::PathExists(new_path));
354 }
355 
356 }  // namespace metrics::structured
357