• 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 "components/metrics/structured/persistent_proto.h"
6 
7 #include <memory>
8 #include <string>
9 
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/logging.h"
14 #include "base/test/task_environment.h"
15 #include "base/time/time.h"
16 #include "components/metrics/structured/storage.pb.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 
19 namespace metrics {
20 namespace 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 }  // namespace
51 
52 class PersistentProtoTest : public testing::Test {
53  public:
SetUp()54   void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
55 
GetPath()56   base::FilePath GetPath() {
57     return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("proto"));
58   }
59 
ClearDisk()60   void ClearDisk() {
61     base::DeleteFile(GetPath());
62     ASSERT_FALSE(base::PathExists(GetPath()));
63   }
64 
65   // Read the file at GetPath and parse it as a KeyProto.
ReadFromDisk()66   KeyProto ReadFromDisk() {
67     std::string proto_str;
68     CHECK(base::ReadFileToString(GetPath(), &proto_str));
69     KeyProto proto;
70     CHECK(proto.ParseFromString(proto_str));
71     return proto;
72   }
73 
WriteToDisk(const KeyProto & proto)74   void WriteToDisk(const KeyProto& proto) {
75     ASSERT_TRUE(base::WriteFile(GetPath(), proto.SerializeAsString()));
76   }
77 
OnRead(const ReadStatus status)78   void OnRead(const ReadStatus status) {
79     read_status_ = status;
80     ++read_count_;
81   }
82 
ReadCallback()83   base::OnceCallback<void(ReadStatus)> ReadCallback() {
84     return base::BindOnce(&PersistentProtoTest::OnRead, base::Unretained(this));
85   }
86 
OnWrite(const WriteStatus status)87   void OnWrite(const WriteStatus status) {
88     ASSERT_EQ(status, WriteStatus::kOk);
89     ++write_count_;
90   }
91 
WriteCallback()92   base::RepeatingCallback<void(WriteStatus)> WriteCallback() {
93     return base::BindRepeating(&PersistentProtoTest::OnWrite,
94                                base::Unretained(this));
95   }
96 
Wait()97   void Wait() { task_environment_.RunUntilIdle(); }
98 
99   // Records the information passed to the callbacks for later expectation.
100   ReadStatus read_status_;
101   int read_count_ = 0;
102   int write_count_ = 0;
103 
104   base::test::TaskEnvironment task_environment_{
105       base::test::TaskEnvironment::MainThreadType::UI,
106       base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
107   base::ScopedTempDir temp_dir_;
108 };
109 
110 // Test that the underlying proto is nullptr until a read is complete, and isn't
111 // after that.
TEST_F(PersistentProtoTest,Initialization)112 TEST_F(PersistentProtoTest, Initialization) {
113   PersistentProto<KeyProto> pproto(GetPath(), WriteDelay(), ReadCallback(),
114                                    WriteCallback());
115   EXPECT_EQ(pproto.get(), nullptr);
116   Wait();
117   EXPECT_NE(pproto.get(), nullptr);
118 }
119 
120 // Test bool conversion and has_value.
TEST_F(PersistentProtoTest,BoolTests)121 TEST_F(PersistentProtoTest, BoolTests) {
122   PersistentProto<KeyProto> pproto(GetPath(), WriteDelay(), ReadCallback(),
123                                    WriteCallback());
124   EXPECT_EQ(pproto.get(), nullptr);
125   EXPECT_FALSE(pproto);
126   EXPECT_FALSE(pproto.has_value());
127   Wait();
128   EXPECT_NE(pproto.get(), nullptr);
129   EXPECT_TRUE(pproto);
130   EXPECT_TRUE(pproto.has_value());
131 }
132 
133 // Test -> and *.
TEST_F(PersistentProtoTest,Getters)134 TEST_F(PersistentProtoTest, Getters) {
135   PersistentProto<KeyProto> pproto(GetPath(), WriteDelay(), ReadCallback(),
136                                    WriteCallback());
137   Wait();
138   // We're really just checking these don't crash.
139   EXPECT_EQ(pproto->last_rotation(), 0);
140   KeyProto val = *pproto;
141 }
142 
143 // Test that the pproto correctly saves the in-memory proto to disk.
TEST_F(PersistentProtoTest,Read)144 TEST_F(PersistentProtoTest, Read) {
145   PersistentProto<KeyProto> pproto(GetPath(), WriteDelay(), ReadCallback(),
146                                    WriteCallback());
147   // Underlying proto should be nullptr until read is complete.
148   EXPECT_EQ(pproto.get(), nullptr);
149 
150   Wait();
151   EXPECT_EQ(read_status_, ReadStatus::kMissing);
152   EXPECT_EQ(read_count_, 1);
153   EXPECT_EQ(write_count_, 1);
154 
155   PopulateTestProto(pproto.get());
156   pproto.StartWrite();
157   Wait();
158   EXPECT_EQ(write_count_, 2);
159 
160   KeyProto written = ReadFromDisk();
161   EXPECT_TRUE(ProtoEquals(&written, pproto.get()));
162 }
163 
164 // Test that invalid files on disk are handled correctly.
TEST_F(PersistentProtoTest,ReadInvalidProto)165 TEST_F(PersistentProtoTest, ReadInvalidProto) {
166   ASSERT_TRUE(base::WriteFile(GetPath(), "this isn't a valid proto"));
167 
168   PersistentProto<KeyProto> pproto(GetPath(), WriteDelay(), ReadCallback(),
169                                    WriteCallback());
170   Wait();
171   EXPECT_EQ(read_status_, ReadStatus::kParseError);
172   EXPECT_EQ(read_count_, 1);
173   EXPECT_EQ(write_count_, 1);
174 }
175 
176 // Test that the pproto correctly loads an on-disk proto into memory.
TEST_F(PersistentProtoTest,Write)177 TEST_F(PersistentProtoTest, Write) {
178   const auto test_proto = MakeTestProto();
179   WriteToDisk(test_proto);
180 
181   PersistentProto<KeyProto> pproto(GetPath(), WriteDelay(), ReadCallback(),
182                                    WriteCallback());
183   EXPECT_EQ(pproto.get(), nullptr);
184 
185   Wait();
186   EXPECT_EQ(read_status_, ReadStatus::kOk);
187   EXPECT_EQ(read_count_, 1);
188   EXPECT_EQ(write_count_, 0);
189   EXPECT_NE(pproto.get(), nullptr);
190   EXPECT_TRUE(ProtoEquals(pproto.get(), &test_proto));
191 }
192 
193 // Test that several saves all happen correctly.
TEST_F(PersistentProtoTest,MultipleWrites)194 TEST_F(PersistentProtoTest, MultipleWrites) {
195   PersistentProto<KeyProto> pproto(GetPath(), WriteDelay(), ReadCallback(),
196                                    WriteCallback());
197   EXPECT_EQ(pproto.get(), nullptr);
198 
199   Wait();
200   EXPECT_EQ(write_count_, 1);
201 
202   for (int i = 1; i <= 10; ++i) {
203     pproto.get()->set_last_rotation(i * i);
204     pproto.StartWrite();
205     Wait();
206     EXPECT_EQ(write_count_, i + 1);
207 
208     KeyProto written = ReadFromDisk();
209     ASSERT_EQ(written.last_rotation(), i * i);
210   }
211 }
212 
213 // Test that many calls to QueueWrite get batched, leading to only one real
214 // write.
TEST_F(PersistentProtoTest,QueueWrites)215 TEST_F(PersistentProtoTest, QueueWrites) {
216   PersistentProto<KeyProto> pproto(GetPath(), WriteDelay(), ReadCallback(),
217                                    WriteCallback());
218   Wait();
219   EXPECT_EQ(write_count_, 1);
220 
221   // Three successive StartWrite calls result in three writes.
222   write_count_ = 0;
223   for (int i = 0; i < 3; ++i)
224     pproto.StartWrite();
225   Wait();
226   EXPECT_EQ(write_count_, 3);
227 
228   // Three successive QueueWrite calls results in one write.
229   write_count_ = 0;
230   for (int i = 0; i < 3; ++i)
231     pproto.QueueWrite();
232   Wait();
233   EXPECT_EQ(write_count_, 1);
234 }
235 
236 }  // namespace structured
237 }  // namespace metrics
238