xref: /aosp_15_r20/external/cronet/components/metrics/structured/lib/persistent_proto_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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   // Arena instance for ArenaPersistentProto test cases.
113   google::protobuf::Arena arena_;
114 };
115 
116 template <typename T>
BuildTestProto()117 T TestCase<T>::BuildTestProto() {
118   ASSERT_TRUE(false)
119       << "Invalid type parameter, please implement BuildTestProto for T";
120 }
121 
122 template <>
123 PersistentProto<KeyProto>
BuildTestProto()124 TestCase<PersistentProto<KeyProto>>::BuildTestProto() {
125   return PersistentProto<KeyProto>(GetPath(), WriteDelay(), ReadCallback(),
126                                    WriteCallback());
127 }
128 
129 template <>
130 ArenaPersistentProto<KeyProto>
BuildTestProto()131 TestCase<ArenaPersistentProto<KeyProto>>::BuildTestProto() {
132   return ArenaPersistentProto<KeyProto>(&arena_, GetPath(), WriteDelay(),
133                                         ReadCallback(), WriteCallback());
134 }
135 
136 }  // namespace
137 
138 // Testing suite for any class that is a persistent proto. This is a series of
139 // tests needed by any PersistentProtoInternal implementation. Currently this
140 // includes: PersistentProto and ArenaPersistentProto.
141 template <typename T>
142 class PersistentProtoTest : public testing::Test {
143  public:
Wait()144   void Wait() { task_environment_.RunUntilIdle(); }
145 
BuildTestProto()146   T BuildTestProto() { return test_.BuildTestProto(); }
147 
148   base::test::TaskEnvironment task_environment_{
149       base::test::TaskEnvironment::MainThreadType::UI,
150       base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
151 
152   TestCase<T> test_;
153 };
154 
155 using Implementations =
156     testing::Types<PersistentProto<KeyProto>, ArenaPersistentProto<KeyProto>>;
157 TYPED_TEST_SUITE(PersistentProtoTest, Implementations);
158 
159 // Test that the underlying proto is nullptr until a read is complete, and isn't
160 // after that.
TYPED_TEST(PersistentProtoTest,Initialization)161 TYPED_TEST(PersistentProtoTest, Initialization) {
162   auto pproto = this->BuildTestProto();
163 
164   EXPECT_EQ(pproto.get(), nullptr);
165   this->Wait();
166   EXPECT_NE(pproto.get(), nullptr);
167 }
168 
169 // Test bool conversion and has_value.
TYPED_TEST(PersistentProtoTest,BoolTests)170 TYPED_TEST(PersistentProtoTest, BoolTests) {
171   auto pproto = this->BuildTestProto();
172   EXPECT_EQ(pproto.get(), nullptr);
173   EXPECT_FALSE(pproto);
174   EXPECT_FALSE(pproto.has_value());
175   this->Wait();
176   EXPECT_NE(pproto.get(), nullptr);
177   EXPECT_TRUE(pproto);
178   EXPECT_TRUE(pproto.has_value());
179 }
180 
181 // Test -> and *.
TYPED_TEST(PersistentProtoTest,Getters)182 TYPED_TEST(PersistentProtoTest, Getters) {
183   auto pproto = this->BuildTestProto();
184   this->Wait();
185   // We're really just checking these don't crash.
186   EXPECT_EQ(pproto->last_rotation(), 0);
187   KeyProto val = *pproto;
188 }
189 
190 // Test that the pproto correctly saves the in-memory proto to disk.
TYPED_TEST(PersistentProtoTest,Read)191 TYPED_TEST(PersistentProtoTest, Read) {
192   auto pproto = this->BuildTestProto();
193 
194   // Underlying proto should be nullptr until read is complete.
195   EXPECT_EQ(pproto.get(), nullptr);
196 
197   this->Wait();
198   EXPECT_EQ(this->test_.read_status_, ReadStatus::kMissing);
199   EXPECT_EQ(this->test_.read_count_, 1);
200   EXPECT_EQ(this->test_.write_count_, 1);
201 
202   PopulateTestProto(pproto.get());
203   pproto.StartWriteForTesting();
204   this->Wait();
205   EXPECT_EQ(this->test_.write_count_, 2);
206 
207   KeyProto written = this->test_.ReadFromDisk();
208   EXPECT_TRUE(ProtoEquals(&written, pproto.get()));
209 }
210 
211 // Test that invalid files on disk are handled correctly.
TYPED_TEST(PersistentProtoTest,ReadInvalidProto)212 TYPED_TEST(PersistentProtoTest, ReadInvalidProto) {
213   ASSERT_TRUE(
214       base::WriteFile(this->test_.GetPath(), "this isn't a valid proto"));
215 
216   auto pproto = this->BuildTestProto();
217 
218   this->Wait();
219   EXPECT_EQ(this->test_.read_status_, ReadStatus::kParseError);
220   EXPECT_EQ(this->test_.read_count_, 1);
221   EXPECT_EQ(this->test_.write_count_, 1);
222 }
223 
224 // Test that the pproto correctly loads an on-disk proto into memory.
TYPED_TEST(PersistentProtoTest,Write)225 TYPED_TEST(PersistentProtoTest, Write) {
226   const auto test_proto = MakeTestProto();
227   this->test_.WriteToDisk(test_proto);
228 
229   auto pproto = this->BuildTestProto();
230 
231   EXPECT_EQ(pproto.get(), nullptr);
232 
233   this->Wait();
234   EXPECT_EQ(this->test_.read_status_, ReadStatus::kOk);
235   EXPECT_EQ(this->test_.read_count_, 1);
236   EXPECT_EQ(this->test_.write_count_, 0);
237   EXPECT_NE(pproto.get(), nullptr);
238   EXPECT_TRUE(ProtoEquals(pproto.get(), &test_proto));
239 }
240 
241 // Test that several saves all happen correctly.
TYPED_TEST(PersistentProtoTest,MultipleWrites)242 TYPED_TEST(PersistentProtoTest, MultipleWrites) {
243   auto pproto = this->BuildTestProto();
244 
245   EXPECT_EQ(pproto.get(), nullptr);
246 
247   this->Wait();
248   EXPECT_EQ(this->test_.write_count_, 1);
249 
250   for (int i = 1; i <= 10; ++i) {
251     pproto.get()->set_last_rotation(i * i);
252     pproto.StartWriteForTesting();
253     this->Wait();
254     EXPECT_EQ(this->test_.write_count_, i + 1);
255 
256     KeyProto written = this->test_.ReadFromDisk();
257     ASSERT_EQ(written.last_rotation(), i * i);
258   }
259 }
260 
261 // Test that many calls to QueueWrite get batched, leading to only one real
262 // write.
TYPED_TEST(PersistentProtoTest,QueueWrites)263 TYPED_TEST(PersistentProtoTest, QueueWrites) {
264   auto pproto = this->BuildTestProto();
265 
266   this->Wait();
267   EXPECT_EQ(this->test_.write_count_, 1);
268 
269   // Three successive StartWrite calls result in three writes.
270   this->test_.write_count_ = 0;
271   for (int i = 0; i < 3; ++i) {
272     pproto.StartWriteForTesting();
273   }
274   this->Wait();
275   EXPECT_EQ(this->test_.write_count_, 3);
276 
277   // Three successive QueueWrite calls results in one write.
278   this->test_.write_count_ = 0;
279   for (int i = 0; i < 3; ++i) {
280     pproto.QueueWrite();
281   }
282   this->Wait();
283   EXPECT_EQ(this->test_.write_count_, 1);
284 }
285 
TYPED_TEST(PersistentProtoTest,ClearContents)286 TYPED_TEST(PersistentProtoTest, ClearContents) {
287   const auto test_proto = MakeTestProto();
288   this->test_.WriteToDisk(test_proto);
289 
290   {
291     auto pproto = this->BuildTestProto();
292 
293     EXPECT_EQ(pproto.get(), nullptr);
294 
295     this->Wait();
296     EXPECT_EQ(this->test_.read_status_, ReadStatus::kOk);
297     EXPECT_EQ(this->test_.read_count_, 1);
298     EXPECT_EQ(this->test_.write_count_, 0);
299 
300     pproto->Clear();
301     pproto.QueueWrite();
302   }
303 
304   this->Wait();
305 
306   int64_t size = 0;
307   std::string empty_proto;
308   KeyProto().SerializeToString(&empty_proto);
309 
310   ASSERT_TRUE(base::GetFileSize(this->test_.GetPath(), &size));
311   EXPECT_EQ(size, static_cast<int64_t>(empty_proto.size()));
312 }
313 
TYPED_TEST(PersistentProtoTest,UpdatePath)314 TYPED_TEST(PersistentProtoTest, UpdatePath) {
315   const base::FilePath new_path =
316       this->test_.temp_dir_.GetPath().Append(FILE_PATH_LITERAL("new_proto"));
317   const int64_t kNewLastRotation = 10;
318 
319   const auto test_proto = MakeTestProto();
320   this->test_.WriteToDisk(test_proto);
321 
322   auto test_proto2 = MakeTestProto();
323   test_proto2.set_last_rotation(kNewLastRotation);
324   this->test_.WriteToDisk(new_path, test_proto2);
325 
326   auto pproto = this->BuildTestProto();
327 
328   // Underlying proto should be nullptr until read is complete.
329   EXPECT_EQ(pproto.get(), nullptr);
330 
331   this->Wait();
332   EXPECT_EQ(this->test_.read_status_, ReadStatus::kOk);
333   EXPECT_EQ(this->test_.read_count_, 1);
334   EXPECT_EQ(this->test_.write_count_, 0);
335 
336   const KeyProto* ptr = pproto.get();
337 
338   pproto.UpdatePath(new_path, this->test_.ReadCallback(),
339                     /*remove_existing=*/true);
340   this->Wait();
341 
342   EXPECT_EQ(this->test_.read_status_, ReadStatus::kOk);
343   EXPECT_EQ(this->test_.read_count_, 2);
344   EXPECT_EQ(this->test_.write_count_, 1);
345 
346   // It is expected that the underlying proto doesn't change.
347   EXPECT_EQ(ptr, pproto.get());
348 
349   // Check the content of the updated proto.
350   EXPECT_EQ(ptr->key(), test_proto.key());
351   EXPECT_EQ(ptr->rotation_period(), test_proto.rotation_period());
352   EXPECT_EQ(ptr->last_rotation(), kNewLastRotation);
353 
354   // Check the state of the files are what we expect.
355   ASSERT_FALSE(base::PathExists(this->test_.GetPath()));
356   ASSERT_TRUE(base::PathExists(new_path));
357 }
358 
359 }  // namespace metrics::structured
360