1// Copyright 2014 The Chromium Authors. All rights reserved.
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/persisted_logs.h"
6
7#include "base/base64.h"
8#include "base/prefs/pref_registry_simple.h"
9#include "base/prefs/scoped_user_pref_update.h"
10#include "base/prefs/testing_pref_service.h"
11#include "base/rand_util.h"
12#include "base/sha1.h"
13#include "base/values.h"
14#include "components/metrics/compression_utils.h"
15#include "testing/gtest/include/gtest/gtest.h"
16
17namespace metrics {
18
19namespace {
20
21const char kTestPrefName[] = "TestPref";
22const size_t kLogCountLimit = 3;
23const size_t kLogByteLimit = 1000;
24
25// Compresses |log_data| and returns the result.
26std::string Compress(const std::string& log_data) {
27  std::string compressed_log_data;
28  EXPECT_TRUE(GzipCompress(log_data, &compressed_log_data));
29  return compressed_log_data;
30}
31
32// Generates and returns log data such that its size after compression is at
33// least |min_compressed_size|.
34std::string GenerateLogWithMinCompressedSize(size_t min_compressed_size) {
35  // Since the size check is done against a compressed log, generate enough
36  // data that compresses to larger than |log_size|.
37  std::string rand_bytes = base::RandBytesAsString(min_compressed_size);
38  while (Compress(rand_bytes).size() < min_compressed_size)
39    rand_bytes.append(base::RandBytesAsString(min_compressed_size));
40  std::string base64_data_for_logging;
41  base::Base64Encode(rand_bytes, &base64_data_for_logging);
42  SCOPED_TRACE(testing::Message() << "Using random data "
43                                  << base64_data_for_logging);
44  return rand_bytes;
45}
46
47class PersistedLogsTest : public testing::Test {
48 public:
49  PersistedLogsTest() {
50    prefs_.registry()->RegisterListPref(kTestPrefName);
51  }
52
53 protected:
54  TestingPrefServiceSimple prefs_;
55
56 private:
57  DISALLOW_COPY_AND_ASSIGN(PersistedLogsTest);
58};
59
60class TestPersistedLogs : public PersistedLogs {
61 public:
62  TestPersistedLogs(PrefService* service, size_t min_log_bytes)
63      : PersistedLogs(service, kTestPrefName, kLogCountLimit, min_log_bytes,
64                      0) {
65  }
66
67  // Stages and removes the next log, while testing it's value.
68  void ExpectNextLog(const std::string& expected_log) {
69    StageLog();
70    EXPECT_EQ(staged_log(), Compress(expected_log));
71    DiscardStagedLog();
72  }
73
74 private:
75  DISALLOW_COPY_AND_ASSIGN(TestPersistedLogs);
76};
77
78}  // namespace
79
80// Store and retrieve empty list_value.
81TEST_F(PersistedLogsTest, EmptyLogList) {
82  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
83
84  persisted_logs.SerializeLogs();
85  const base::ListValue* list_value = prefs_.GetList(kTestPrefName);
86  EXPECT_EQ(0U, list_value->GetSize());
87
88  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
89  EXPECT_EQ(PersistedLogs::LIST_EMPTY, result_persisted_logs.DeserializeLogs());
90  EXPECT_EQ(0U, result_persisted_logs.size());
91}
92
93// Store and retrieve a single log value.
94TEST_F(PersistedLogsTest, SingleElementLogList) {
95  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
96
97  persisted_logs.StoreLog("Hello world!");
98  persisted_logs.SerializeLogs();
99
100  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
101  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
102            result_persisted_logs.DeserializeLogs());
103  EXPECT_EQ(1U, result_persisted_logs.size());
104
105  // Verify that the result log matches the initial log.
106  persisted_logs.StageLog();
107  result_persisted_logs.StageLog();
108  EXPECT_EQ(persisted_logs.staged_log(), result_persisted_logs.staged_log());
109  EXPECT_EQ(persisted_logs.staged_log_hash(),
110            result_persisted_logs.staged_log_hash());
111}
112
113// Store a set of logs over the length limit, but smaller than the min number of
114// bytes.
115TEST_F(PersistedLogsTest, LongButTinyLogList) {
116  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
117
118  size_t log_count = kLogCountLimit * 5;
119  for (size_t i = 0; i < log_count; ++i)
120    persisted_logs.StoreLog("x");
121
122  persisted_logs.SerializeLogs();
123
124  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
125  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
126            result_persisted_logs.DeserializeLogs());
127  EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());
128
129  result_persisted_logs.ExpectNextLog("x");
130}
131
132// Store a set of logs over the length limit, but that doesn't reach the minimum
133// number of bytes until after passing the length limit.
134TEST_F(PersistedLogsTest, LongButSmallLogList) {
135  size_t log_count = kLogCountLimit * 5;
136  size_t log_size = 50;
137
138  std::string first_kept = "First to keep";
139  first_kept.resize(log_size, ' ');
140
141  std::string blank_log = std::string(log_size, ' ');
142
143  std::string last_kept = "Last to keep";
144  last_kept.resize(log_size, ' ');
145
146  // Set the byte limit enough to keep everything but the first two logs.
147  const size_t min_log_bytes =
148      Compress(first_kept).length() + Compress(last_kept).length() +
149      (log_count - 4) * Compress(blank_log).length();
150  TestPersistedLogs persisted_logs(&prefs_, min_log_bytes);
151
152  persisted_logs.StoreLog("one");
153  persisted_logs.StoreLog("two");
154  persisted_logs.StoreLog(first_kept);
155  for (size_t i = persisted_logs.size(); i < log_count - 1; ++i) {
156    persisted_logs.StoreLog(blank_log);
157  }
158  persisted_logs.StoreLog(last_kept);
159  persisted_logs.SerializeLogs();
160
161  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
162  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
163            result_persisted_logs.DeserializeLogs());
164  EXPECT_EQ(persisted_logs.size() - 2, result_persisted_logs.size());
165
166  result_persisted_logs.ExpectNextLog(last_kept);
167  while (result_persisted_logs.size() > 1) {
168    result_persisted_logs.ExpectNextLog(blank_log);
169  }
170  result_persisted_logs.ExpectNextLog(first_kept);
171}
172
173// Store a set of logs within the length limit, but well over the minimum
174// number of bytes.
175TEST_F(PersistedLogsTest, ShortButLargeLogList) {
176  // Make the total byte count about twice the minimum.
177  size_t log_count = kLogCountLimit;
178  size_t log_size = (kLogByteLimit / log_count) * 2;
179  std::string log_data = GenerateLogWithMinCompressedSize(log_size);
180
181  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
182  for (size_t i = 0; i < log_count; ++i) {
183    persisted_logs.StoreLog(log_data);
184  }
185  persisted_logs.SerializeLogs();
186
187  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
188  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
189            result_persisted_logs.DeserializeLogs());
190  EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());
191}
192
193// Store a set of logs over the length limit, and over the minimum number of
194// bytes.
195TEST_F(PersistedLogsTest, LongAndLargeLogList) {
196  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
197
198  // Include twice the max number of logs.
199  size_t log_count = kLogCountLimit * 2;
200  // Make the total byte count about four times the minimum.
201  size_t log_size = (kLogByteLimit / log_count) * 4;
202
203  std::string target_log = "First to keep";
204  target_log += GenerateLogWithMinCompressedSize(log_size);
205
206  std::string log_data = GenerateLogWithMinCompressedSize(log_size);
207  for (size_t i = 0; i < log_count; ++i) {
208    if (i == log_count - kLogCountLimit)
209      persisted_logs.StoreLog(target_log);
210    else
211      persisted_logs.StoreLog(log_data);
212  }
213
214  persisted_logs.SerializeLogs();
215
216  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
217  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
218            result_persisted_logs.DeserializeLogs());
219  EXPECT_EQ(kLogCountLimit, result_persisted_logs.size());
220
221  while (result_persisted_logs.size() > 1) {
222    result_persisted_logs.ExpectNextLog(log_data);
223  }
224  result_persisted_logs.ExpectNextLog(target_log);
225}
226
227// Check that the store/stage/discard functions work as expected.
228TEST_F(PersistedLogsTest, Staging) {
229  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
230  std::string tmp;
231
232  EXPECT_FALSE(persisted_logs.has_staged_log());
233  persisted_logs.StoreLog("one");
234  EXPECT_FALSE(persisted_logs.has_staged_log());
235  persisted_logs.StoreLog("two");
236  persisted_logs.StageLog();
237  EXPECT_TRUE(persisted_logs.has_staged_log());
238  EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
239  persisted_logs.StoreLog("three");
240  EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
241  EXPECT_EQ(persisted_logs.size(), 3U);
242  persisted_logs.DiscardStagedLog();
243  EXPECT_FALSE(persisted_logs.has_staged_log());
244  EXPECT_EQ(persisted_logs.size(), 2U);
245  persisted_logs.StageLog();
246  EXPECT_EQ(persisted_logs.staged_log(), Compress("three"));
247  persisted_logs.DiscardStagedLog();
248  persisted_logs.StageLog();
249  EXPECT_EQ(persisted_logs.staged_log(), Compress("one"));
250  persisted_logs.DiscardStagedLog();
251  EXPECT_FALSE(persisted_logs.has_staged_log());
252  EXPECT_EQ(persisted_logs.size(), 0U);
253}
254
255TEST_F(PersistedLogsTest, DiscardOrder) {
256  // Ensure that the correct log is discarded if new logs are pushed while
257  // a log is staged.
258  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
259
260  persisted_logs.StoreLog("one");
261  persisted_logs.StageLog();
262  persisted_logs.StoreLog("two");
263  persisted_logs.DiscardStagedLog();
264  persisted_logs.SerializeLogs();
265
266  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
267  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
268            result_persisted_logs.DeserializeLogs());
269  EXPECT_EQ(1U, result_persisted_logs.size());
270  result_persisted_logs.ExpectNextLog("two");
271}
272
273
274TEST_F(PersistedLogsTest, Hashes) {
275  const char kFooText[] = "foo";
276  const std::string foo_hash = base::SHA1HashString(kFooText);
277
278  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
279  persisted_logs.StoreLog(kFooText);
280  persisted_logs.StageLog();
281
282  EXPECT_EQ(Compress(kFooText), persisted_logs.staged_log());
283  EXPECT_EQ(foo_hash, persisted_logs.staged_log_hash());
284}
285
286}  // namespace metrics
287