• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 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/flushed_map.h"
6 
7 #include <cstdint>
8 #include <optional>
9 #include <vector>
10 
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/run_loop.h"
15 #include "base/test/bind.h"
16 #include "base/test/task_environment.h"
17 #include "components/metrics/structured/lib/event_buffer.h"
18 #include "components/metrics/structured/proto/event_storage.pb.h"
19 #include "testing/gmock/include/gmock/gmock-matchers.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "third_party/metrics_proto/structured_data.pb.h"
22 
23 namespace metrics::structured {
24 namespace {
25 
26 class TestEventBuffer : public EventBuffer<StructuredEventProto> {
27  public:
TestEventBuffer()28   TestEventBuffer() : EventBuffer<StructuredEventProto>(ResourceInfo(1024)) {}
29 
30   // EventBuffer:
AddEvent(StructuredEventProto event)31   Result AddEvent(StructuredEventProto event) override {
32     events_.mutable_events()->Add(std::move(event));
33     return Result::kOk;
34   }
35 
Purge()36   void Purge() override { events_.Clear(); }
37 
Serialize()38   google::protobuf::RepeatedPtrField<StructuredEventProto> Serialize()
39       override {
40     return events_.events();
41   }
42 
Flush(const base::FilePath & path,FlushedCallback callback)43   void Flush(const base::FilePath& path, FlushedCallback callback) override {
44     std::string content;
45     EXPECT_TRUE(events_.SerializeToString(&content));
46     EXPECT_TRUE(base::WriteFile(path, content));
47     std::move(callback).Run(FlushedKey{
48         .size = static_cast<int32_t>(content.size()),
49         .path = path,
50         .creation_time = base::Time::Now(),
51     });
52   }
53 
Size()54   uint64_t Size() override { return events_.events_size(); }
55 
events() const56   const EventsProto& events() const { return events_; }
57 
58  private:
59   EventsProto events_;
60 };
61 
BuildTestEvent(int id)62 StructuredEventProto BuildTestEvent(int id) {
63   StructuredEventProto event;
64   event.set_device_project_id(id);
65   return event;
66 }
67 
BuildTestBuffer(const std::vector<int> & ids)68 TestEventBuffer BuildTestBuffer(const std::vector<int>& ids) {
69   TestEventBuffer buffer;
70 
71   for (int id : ids) {
72     EXPECT_EQ(buffer.AddEvent(BuildTestEvent(id)), Result::kOk);
73   }
74 
75   return buffer;
76 }
77 
BuildTestEvents(const std::vector<int> & ids)78 EventsProto BuildTestEvents(const std::vector<int>& ids) {
79   EventsProto events;
80 
81   for (int id : ids) {
82     events.mutable_events()->Add(BuildTestEvent(id));
83   }
84 
85   return events;
86 }
87 
88 }  // namespace
89 
90 class FlushedMapTest : public testing::Test {
91  public:
92   FlushedMapTest() = default;
93   ~FlushedMapTest() override = default;
94 
SetUp()95   void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
96 
GetDir() const97   base::FilePath GetDir() const {
98     return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("events"));
99   }
100 
BuildFlushedMap(int32_t max_size=2048)101   FlushedMap BuildFlushedMap(int32_t max_size = 2048) {
102     return FlushedMap(GetDir(), max_size);
103   }
104 
ReadEventsProto(const base::FilePath & path)105   EventsProto ReadEventsProto(const base::FilePath& path) {
106     std::string content;
107     EXPECT_TRUE(base::ReadFileToString(path, &content));
108 
109     EventsProto events;
110     EXPECT_TRUE(events.MergeFromString(content));
111     return events;
112   }
113 
WriteToDisk(const base::FilePath & path,EventsProto && events)114   void WriteToDisk(const base::FilePath& path, EventsProto&& events) {
115     std::string content;
116     EXPECT_TRUE(events.SerializeToString(&content));
117     EXPECT_TRUE(base::WriteFile(path, content));
118   }
119 
Wait()120   void Wait() { task_environment_.RunUntilIdle(); }
121 
122  private:
123   base::test::TaskEnvironment task_environment_{
124       base::test::TaskEnvironment::MainThreadType::UI,
125       base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
126 
127   base::ScopedTempDir temp_dir_;
128 
129  protected:
130 };
131 
TEST_F(FlushedMapTest,FlushFile)132 TEST_F(FlushedMapTest, FlushFile) {
133   FlushedMap map = BuildFlushedMap();
134   Wait();
135   auto buffer = BuildTestBuffer({1, 2, 3});
136 
137   map.Flush(buffer,
138             base::BindLambdaForTesting(
139                 [&](base::expected<FlushedKey, FlushError> key) {
140                   EXPECT_TRUE(key.has_value());
141                   EXPECT_TRUE(base::PathExists(base::FilePath(key->path)));
142                 }));
143   Wait();
144 
145   const std::vector<FlushedKey>& keys = map.keys();
146   EXPECT_EQ(keys.size(), 1ul);
147 
148   std::optional<int64_t> file_size = base::GetFileSize(keys[0].path);
149   ASSERT_TRUE(file_size.has_value());
150   EXPECT_EQ(keys[0].size, static_cast<int32_t>(file_size.value()));
151 }
152 
TEST_F(FlushedMapTest,ReadFile)153 TEST_F(FlushedMapTest, ReadFile) {
154   FlushedMap map = BuildFlushedMap();
155   Wait();
156 
157   auto buffer = BuildTestBuffer({1, 2, 3});
158   const EventsProto& events = buffer.events();
159 
160   map.Flush(buffer,
161             base::BindLambdaForTesting(
162                 [&](base::expected<FlushedKey, FlushError> key) {
163                   EXPECT_TRUE(key.has_value());
164                   EXPECT_TRUE(base::PathExists(base::FilePath(key->path)));
165                 }));
166   Wait();
167 
168   auto key = map.keys().front();
169 
170   std::optional<EventsProto> read_events = map.ReadKey(key);
171   EXPECT_EQ(read_events->events_size(), events.events_size());
172 
173   for (int i = 0; i < read_events->events_size(); ++i) {
174     EXPECT_EQ(read_events->events(i).device_project_id(),
175               events.events(i).device_project_id());
176   }
177 }
178 
TEST_F(FlushedMapTest,UniqueFlushes)179 TEST_F(FlushedMapTest, UniqueFlushes) {
180   FlushedMap map = BuildFlushedMap();
181   Wait();
182 
183   auto events = BuildTestBuffer({1, 2, 3});
184   map.Flush(events,
185             base::BindLambdaForTesting(
186                 [&](base::expected<FlushedKey, FlushError> key) {
187                   EXPECT_TRUE(key.has_value());
188                   EXPECT_TRUE(base::PathExists(base::FilePath(key->path)));
189                 }));
190   Wait();
191 
192   auto events2 = BuildTestBuffer({4, 5, 6});
193   map.Flush(events2,
194             base::BindLambdaForTesting(
195                 [&](base::expected<FlushedKey, FlushError> key) {
196                   EXPECT_TRUE(key.has_value());
197                   EXPECT_TRUE(base::PathExists(base::FilePath(key->path)));
198                 }));
199   Wait();
200 
201   EXPECT_EQ(map.keys().size(), 2ul);
202   const auto& key1 = map.keys()[0];
203   const auto& key2 = map.keys()[1];
204 
205   EXPECT_NE(key1.path, key2.path);
206 }
207 
TEST_F(FlushedMapTest,DeleteKey)208 TEST_F(FlushedMapTest, DeleteKey) {
209   FlushedMap map = BuildFlushedMap();
210   Wait();
211 
212   auto events = BuildTestBuffer({1, 2, 3});
213   map.Flush(events, base::BindLambdaForTesting(
214                         [&](base::expected<FlushedKey, FlushError> key) {
215                           EXPECT_TRUE(key.has_value());
216                           EXPECT_TRUE(base::PathExists(key->path));
217                         }));
218   Wait();
219 
220   const std::vector<FlushedKey>& keys = map.keys();
221   auto key = map.keys().front();
222   EXPECT_EQ(keys.size(), 1ul);
223   EXPECT_TRUE(base::PathExists(key.path));
224 
225   map.DeleteKey(key);
226   Wait();
227 
228   EXPECT_FALSE(base::PathExists(key.path));
229 }
230 
TEST_F(FlushedMapTest,LoadPreviousSessionKeys)231 TEST_F(FlushedMapTest, LoadPreviousSessionKeys) {
232   EXPECT_TRUE(base::CreateDirectory(GetDir()));
233   auto events = BuildTestEvents({1, 2, 3});
234   auto events2 = BuildTestEvents({4, 5, 6});
235 
236   base::FilePath path1 = GetDir().Append(FILE_PATH_LITERAL("events"));
237   base::FilePath path2 = GetDir().Append(FILE_PATH_LITERAL("events2"));
238 
239   WriteToDisk(path1, std::move(events));
240   // Force a small difference in the creation time of the two files.
241   base::PlatformThreadBase::Sleep(base::Seconds(1));
242   WriteToDisk(path2, std::move(events2));
243 
244   FlushedMap map = BuildFlushedMap();
245   Wait();
246 
247   const std::vector<FlushedKey>& keys = map.keys();
248   EXPECT_EQ(keys.size(), 2ul);
249 
250   // The order the files are loaded in is unknown.
251   std::vector<base::FilePath> paths;
252   for (const auto& key : keys) {
253     paths.push_back(base::FilePath(key.path));
254   }
255 
256   EXPECT_THAT(paths, testing::ElementsAre(path1, path2));
257 }
258 
TEST_F(FlushedMapTest,ExceedQuota)259 TEST_F(FlushedMapTest, ExceedQuota) {
260   FlushedMap map = BuildFlushedMap(/*max_size=*/64);
261   Wait();
262 
263   auto events = BuildTestBuffer({1, 2, 3});
264   map.Flush(events, base::BindLambdaForTesting(
265                         [&](base::expected<FlushedKey, FlushError> key) {
266                           EXPECT_TRUE(key.has_value());
267                           EXPECT_TRUE(base::PathExists(key->path));
268                         }));
269   Wait();
270 
271   auto events2 = BuildTestBuffer({1, 2, 3});
272   map.Flush(events2, base::BindLambdaForTesting(
273                          [&](base::expected<FlushedKey, FlushError> key) {
274                            EXPECT_FALSE(key.has_value());
275                            const FlushError err = key.error();
276                            EXPECT_EQ(err, kQuotaExceeded);
277                          }));
278   Wait();
279 }
280 
281 }  // namespace metrics::structured
282