summaryrefslogtreecommitdiffstats
path: root/chrome/browser/metrics
diff options
context:
space:
mode:
authorziadh@chromium.org <ziadh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-19 08:00:42 +0000
committerziadh@chromium.org <ziadh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-19 08:00:42 +0000
commit46f89e149da3971fcf52b778de939256fae4249a (patch)
tree24a85f97832b082072dd54035a89de4ca9cc86cf /chrome/browser/metrics
parenta110dd1ff6a3b8507ee164ababea537df52642cb (diff)
downloadchromium_src-46f89e149da3971fcf52b778de939256fae4249a.zip
chromium_src-46f89e149da3971fcf52b778de939256fae4249a.tar.gz
chromium_src-46f89e149da3971fcf52b778de939256fae4249a.tar.bz2
Compress and checksum pending logs that are going to be persisted. Persisted logs now have the following format:
[list_size, log1, log2, ..., log_n, checksum]. where each log is bzipped before being written. Upon reading the logs from disk, we verify the data and register whether we faced corruptions or not. r=jar Review URL: http://codereview.chromium.org/2936005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52885 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/metrics')
-rw-r--r--chrome/browser/metrics/metrics_service.cc220
-rw-r--r--chrome/browser/metrics/metrics_service.h42
-rw-r--r--chrome/browser/metrics/metrics_service_unittest.cc179
3 files changed, 374 insertions, 67 deletions
diff --git a/chrome/browser/metrics/metrics_service.cc b/chrome/browser/metrics/metrics_service.cc
index 3e30eab..87eae58 100644
--- a/chrome/browser/metrics/metrics_service.cc
+++ b/chrome/browser/metrics/metrics_service.cc
@@ -19,9 +19,8 @@
// transmission. Transmission includes submitting a compressed log as data in a
// URL-post, and retransmitting (or retaining at process termination) if the
// attempted transmission failed. Retention across process terminations is done
-// using the the PrefServices facilities. The format for the retained
-// logs (ones that never got transmitted) is always the uncompressed textual
-// representation.
+// using the the PrefServices facilities. The retained logs (the ones that never
+// got transmitted) are compressed and base64-encoded before being persisted.
//
// Logs fall into one of two categories: "initial logs," and "ongoing logs."
// There is at most one initial log sent for each complete run of the chromium
@@ -164,12 +163,8 @@
#include <objbase.h>
#endif
-#if defined(USE_SYSTEM_LIBBZ2)
-#include <bzlib.h>
-#else
-#include "third_party/bzip2/bzlib.h"
-#endif
-
+#include "base/base64.h"
+#include "base/md5.h"
#include "base/thread.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/browser_list.h"
@@ -205,6 +200,21 @@ static const char kHardwareClassTool[] = "/usr/bin/hardware_class";
static const char kUnknownHardwareClass[] = "unknown";
#endif
+namespace {
+MetricsService::LogRecallStatus MakeRecallStatusHistogram(
+ MetricsService::LogRecallStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecall", status,
+ MetricsService::END_RECALL_STATUS);
+ return status;
+}
+
+// TODO(ziadh): Remove this when done with experiment.
+void MakeStoreStatusHistogram(MetricsService::LogStoreStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogStore", status,
+ MetricsService::END_STORE_STATUS);
+}
+} // namespace
+
using base::Time;
using base::TimeDelta;
@@ -270,6 +280,10 @@ static const size_t kMaxInitialLogsPersisted = 20;
// ongoing_log_ at startup).
static const size_t kMaxOngoingLogsPersisted = 8;
+// We append (2) more elements to persisted lists: the size of the list and a
+// checksum of the elements.
+static const size_t kChecksumEntryCount = 2;
+
// Handles asynchronous fetching of memory details.
// Will run the provided task after finished.
@@ -883,7 +897,7 @@ void MetricsService::PushPendingLogsToUnsentLists() {
PreparePendingLogText();
if (state_ == INITIAL_LOG_READY) {
// We may race here, and send second copy of initial log later.
- unsent_initial_logs_.push_back(pending_log_text_);
+ unsent_initial_logs_.push_back(compressed_log_);
state_ = SEND_OLD_INITIAL_LOGS;
} else {
// TODO(jar): Verify correctness in other states, including sending unsent
@@ -905,14 +919,13 @@ void MetricsService::PushPendingLogTextToUnsentOngoingLogs() {
// log. It wasn't supposed to be uploaded anyway.
if (!server_permits_upload_)
return;
-
- if (pending_log_text_.length() >
+ if (compressed_log_.length() >
static_cast<size_t>(kUploadLogAvoidRetransmitSize)) {
UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted",
- static_cast<int>(pending_log_text_.length()));
+ static_cast<int>(compressed_log_.length()));
return;
}
- unsent_ongoing_logs_.push_back(pending_log_text_);
+ unsent_ongoing_logs_.push_back(compressed_log_);
}
//------------------------------------------------------------------------------
@@ -1078,7 +1091,7 @@ void MetricsService::MakePendingLog() {
case SEND_OLD_INITIAL_LOGS:
if (!unsent_initial_logs_.empty()) {
- pending_log_text_ = unsent_initial_logs_.back();
+ compressed_log_ = unsent_initial_logs_.back();
break;
}
state_ = SENDING_OLD_LOGS;
@@ -1086,7 +1099,7 @@ void MetricsService::MakePendingLog() {
case SENDING_OLD_LOGS:
if (!unsent_ongoing_logs_.empty()) {
- pending_log_text_ = unsent_ongoing_logs_.back();
+ compressed_log_ = unsent_ongoing_logs_.back();
break;
}
state_ = SENDING_CURRENT_LOGS;
@@ -1145,30 +1158,114 @@ void MetricsService::PrepareInitialLog() {
pending_log_ = log;
}
-void MetricsService::RecallUnsentLogs() {
- DCHECK(unsent_initial_logs_.empty());
- DCHECK(unsent_ongoing_logs_.empty());
+// static
+MetricsService::LogRecallStatus MetricsService::RecallUnsentLogsHelper(
+ const ListValue& list,
+ std::vector<std::string>* local_list) {
+ DCHECK(local_list->empty());
+ if (list.GetSize() == 0)
+ return MakeRecallStatusHistogram(LIST_EMPTY);
+ if (list.GetSize() < 3)
+ return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL);
+
+ // The size is stored at the beginning of the list.
+ int size;
+ bool valid = (*list.begin())->GetAsInteger(&size);
+ if (!valid)
+ return MakeRecallStatusHistogram(LIST_SIZE_MISSING);
+
+ // Account for checksum and size included in the list.
+ if (static_cast<unsigned int>(size) !=
+ list.GetSize() - kChecksumEntryCount)
+ return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION);
+
+ MD5Context ctx;
+ MD5Init(&ctx);
+ std::string encoded_log;
+ std::string decoded_log;
+ for (ListValue::const_iterator it = list.begin() + 1;
+ it != list.end() - 1; ++it) { // Last element is the checksum.
+ valid = (*it)->GetAsString(&encoded_log);
+ if (!valid) {
+ local_list->clear();
+ return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION);
+ }
+ MD5Update(&ctx, encoded_log.data(), encoded_log.length());
+
+ if (!base::Base64Decode(encoded_log, &decoded_log)) {
+ local_list->clear();
+ return MakeRecallStatusHistogram(DECODE_FAIL);
+ }
+ local_list->push_back(decoded_log);
+ }
+
+ // Verify checksum.
+ MD5Digest digest;
+ MD5Final(&digest, &ctx);
+ std::string recovered_md5;
+ // We store the hash at the end of the list.
+ valid = (*(list.end() - 1))->GetAsString(&recovered_md5);
+ if (!valid) {
+ local_list->clear();
+ return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION);
+ }
+ if (recovered_md5 != MD5DigestToBase16(digest)) {
+ local_list->clear();
+ return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION);
+ }
+ return MakeRecallStatusHistogram(RECALL_SUCCESS);
+}
+void MetricsService::RecallUnsentLogs() {
PrefService* local_state = g_browser_process->local_state();
DCHECK(local_state);
ListValue* unsent_initial_logs = local_state->GetMutableList(
prefs::kMetricsInitialLogs);
- for (ListValue::const_iterator it = unsent_initial_logs->begin();
- it != unsent_initial_logs->end(); ++it) {
- std::string log;
- (*it)->GetAsString(&log);
- unsent_initial_logs_.push_back(log);
- }
+ RecallUnsentLogsHelper(*unsent_initial_logs, &unsent_initial_logs_);
ListValue* unsent_ongoing_logs = local_state->GetMutableList(
prefs::kMetricsOngoingLogs);
- for (ListValue::const_iterator it = unsent_ongoing_logs->begin();
- it != unsent_ongoing_logs->end(); ++it) {
- std::string log;
- (*it)->GetAsString(&log);
- unsent_ongoing_logs_.push_back(log);
+ RecallUnsentLogsHelper(*unsent_ongoing_logs, &unsent_ongoing_logs_);
+}
+
+// static
+void MetricsService::StoreUnsentLogsHelper(
+ const std::vector<std::string>& local_list,
+ const size_t kMaxLocalListSize,
+ ListValue* list) {
+ list->Clear();
+ size_t start = 0;
+ if (local_list.size() > kMaxLocalListSize)
+ start = local_list.size() - kMaxLocalListSize;
+ DCHECK(start <= local_list.size());
+ if (local_list.size() == start)
+ return;
+
+ // Store size at the beginning of the list.
+ list->Append(Value::CreateIntegerValue(local_list.size() - start));
+
+ MD5Context ctx;
+ MD5Init(&ctx);
+ std::string encoded_log;
+ for (std::vector<std::string>::const_iterator it = local_list.begin() + start;
+ it != local_list.end(); ++it) {
+ // We encode the compressed log as Value::CreateStringValue() expects to
+ // take a valid UTF8 string.
+ if (!base::Base64Encode(*it, &encoded_log)) {
+ MakeStoreStatusHistogram(ENCODE_FAIL);
+ list->Clear();
+ return;
+ }
+ MD5Update(&ctx, encoded_log.data(), encoded_log.length());
+ list->Append(Value::CreateStringValue(encoded_log));
}
+
+ // Append hash to the end of the list.
+ MD5Digest digest;
+ MD5Final(&digest, &ctx);
+ list->Append(Value::CreateStringValue(MD5DigestToBase16(digest)));
+ DCHECK(list->GetSize() >= 3); // Minimum of 3 elements (size, data, hash).
}
void MetricsService::StoreUnsentLogs() {
@@ -1180,59 +1277,51 @@ void MetricsService::StoreUnsentLogs() {
ListValue* unsent_initial_logs = local_state->GetMutableList(
prefs::kMetricsInitialLogs);
- unsent_initial_logs->Clear();
- size_t start = 0;
- if (unsent_initial_logs_.size() > kMaxInitialLogsPersisted)
- start = unsent_initial_logs_.size() - kMaxInitialLogsPersisted;
- for (size_t i = start; i < unsent_initial_logs_.size(); ++i)
- unsent_initial_logs->Append(
- Value::CreateStringValue(unsent_initial_logs_[i]));
+ StoreUnsentLogsHelper(unsent_initial_logs_, kMaxInitialLogsPersisted,
+ unsent_initial_logs);
ListValue* unsent_ongoing_logs = local_state->GetMutableList(
prefs::kMetricsOngoingLogs);
- unsent_ongoing_logs->Clear();
- start = 0;
- if (unsent_ongoing_logs_.size() > kMaxOngoingLogsPersisted)
- start = unsent_ongoing_logs_.size() - kMaxOngoingLogsPersisted;
-
- for (size_t i = start; i < unsent_ongoing_logs_.size(); ++i)
- unsent_ongoing_logs->Append(
- Value::CreateStringValue(unsent_ongoing_logs_[i]));
+ StoreUnsentLogsHelper(unsent_ongoing_logs_, kMaxOngoingLogsPersisted,
+ unsent_ongoing_logs);
}
void MetricsService::PreparePendingLogText() {
DCHECK(pending_log());
- if (!pending_log_text_.empty())
+ if (!compressed_log_.empty())
return;
int text_size = pending_log_->GetEncodedLogSize();
- // Leave room for the NUL terminator.
- pending_log_->GetEncodedLog(WriteInto(&pending_log_text_, text_size + 1),
+ std::string pending_log_text;
+ // Leave room for the NULL terminator.
+ pending_log_->GetEncodedLog(WriteInto(&pending_log_text, text_size + 1),
text_size);
+
+ if (Bzip2Compress(pending_log_text, &compressed_log_)) {
+ // Allow security conscious users to see all metrics logs that we send.
+ LOG(INFO) << "COMPRESSED FOLLOWING METRICS LOG: " << pending_log_text;
+ } else {
+ LOG(DFATAL) << "Failed to compress log for transmission.";
+ // We can't discard the logs as other caller functions expect that
+ // |compressed_log_| not be empty. We can detect this failure at the server
+ // after we transmit.
+ compressed_log_ = "Unable to compress!";
+ MakeStoreStatusHistogram(COMPRESS_FAIL);
+ return;
+ }
}
void MetricsService::PrepareFetchWithPendingLog() {
DCHECK(pending_log());
DCHECK(!current_fetch_.get());
PreparePendingLogText();
- DCHECK(!pending_log_text_.empty());
-
- // Allow security conscious users to see all metrics logs that we send.
- LOG(INFO) << "METRICS LOG: " << pending_log_text_;
-
- std::string compressed_log;
- if (!Bzip2Compress(pending_log_text_, &compressed_log)) {
- LOG(DFATAL) << "Failed to compress log for transmission.";
- DiscardPendingLog();
- StartLogTransmissionTimer(); // Maybe we'll do better on next log :-/.
- return;
- }
+ DCHECK(!compressed_log_.empty());
current_fetch_.reset(new URLFetcher(GURL(WideToUTF16(server_url_)),
URLFetcher::POST,
this));
current_fetch_->set_request_context(Profile::GetDefaultRequestContext());
- current_fetch_->set_upload_data(kMetricsType, compressed_log);
+ current_fetch_->set_upload_data(kMetricsType, compressed_log_);
}
static const char* StatusToString(const URLRequestStatus& status) {
@@ -1277,10 +1366,10 @@ void MetricsService::OnURLFetchComplete(const URLFetcher* source,
bool discard_log = false;
if (response_code != 200 &&
- pending_log_text_.length() >
- static_cast<size_t>(kUploadLogAvoidRetransmitSize)) {
+ (compressed_log_.length() >
+ static_cast<size_t>(kUploadLogAvoidRetransmitSize))) {
UMA_HISTOGRAM_COUNTS("UMA.Large Rejected Log was Discarded",
- static_cast<int>(pending_log_text_.length()));
+ static_cast<int>(compressed_log_.length()));
discard_log = true;
} else if (response_code == 400) {
// Bad syntax. Retransmission won't work.
@@ -1361,7 +1450,7 @@ void MetricsService::HandleBadResponseCode() {
LOG(INFO) << "METRICS: transmission retry being scheduled in " <<
interlog_duration_.InSeconds() << " seconds for " <<
- pending_log_text_;
+ compressed_log_;
}
}
@@ -1378,7 +1467,6 @@ void MetricsService::GetSettingsFromResponseData(const std::string& data) {
return;
}
xmlDocPtr doc = xmlReadMemory(data.c_str(), data_size, "", NULL, 0);
- DCHECK(doc);
// If the document is malformed, we just use the settings that were there.
if (!doc) {
LOG(INFO) << "METRICS: reading xml from server response data failed";
diff --git a/chrome/browser/metrics/metrics_service.h b/chrome/browser/metrics/metrics_service.h
index 7e318f0..5066d82 100644
--- a/chrome/browser/metrics/metrics_service.h
+++ b/chrome/browser/metrics/metrics_service.h
@@ -80,6 +80,30 @@ class MetricsService : public NotificationObserver,
public URLFetcher::Delegate,
public MetricsServiceBase {
public:
+ // Used to produce a historgram that keeps track of the status of recalling
+ // persisted per logs.
+ enum LogRecallStatus {
+ RECALL_SUCCESS, // We were able to correctly recall a persisted log.
+ LIST_EMPTY, // Attempting to recall from an empty list.
+ LIST_SIZE_MISSING, // Failed to recover list size using GetAsInteger().
+ LIST_SIZE_TOO_SMALL, // Too few elements in the list (less than 3).
+ LIST_SIZE_CORRUPTION, // List size is not as expected.
+ LOG_STRING_CORRUPTION, // Failed to recover log string using GetAsString().
+ CHECKSUM_CORRUPTION, // Failed to verify checksum.
+ CHECKSUM_STRING_CORRUPTION, // Failed to recover checksum string using
+ // GetAsString().
+ DECODE_FAIL, // Failed to decode log.
+ END_RECALL_STATUS // Number of bins to use to create the histogram.
+ };
+
+ // TODO(ziadh): This is here temporarily for a side experiment. Remove later
+ // on.
+ enum LogStoreStatus {
+ ENCODE_FAIL, // Failed to encode log.
+ COMPRESS_FAIL, // Failed to compress log.
+ END_STORE_STATUS // Number of bins to use to create the histogram.
+ };
+
MetricsService();
virtual ~MetricsService();
@@ -263,7 +287,16 @@ class MetricsService : public NotificationObserver,
void PrepareInitialLog();
// Pull copies of unsent logs from prefs into instance variables.
void RecallUnsentLogs();
- // Convert pending_log_ to XML in pending_log_text_ for transmission.
+ // Decode and verify written pref log data.
+ static MetricsService::LogRecallStatus RecallUnsentLogsHelper(
+ const ListValue& list,
+ std::vector<std::string>* local_list);
+ // Encode and write list size and checksum for perf log data.
+ static void StoreUnsentLogsHelper(const std::vector<std::string>& local_list,
+ const size_t kMaxLocalListSize,
+ ListValue* list);
+ // Convert |pending_log_| to XML in |compressed_log_|, and compress it for
+ // transmission.
void PreparePendingLogText();
// Convert pending_log_ to XML, compress it, and prepare to pass to server.
@@ -489,6 +522,13 @@ class MetricsService : public NotificationObserver,
scoped_refptr<chromeos::ExternalMetrics> external_metrics_;
#endif
+ FRIEND_TEST(MetricsServiceTest, EmptyLogList);
+ FRIEND_TEST(MetricsServiceTest, SingleElementLogList);
+ FRIEND_TEST(MetricsServiceTest, OverLimitLogList);
+ FRIEND_TEST(MetricsServiceTest, SmallRecoveredListSize);
+ FRIEND_TEST(MetricsServiceTest, RemoveSizeFromLogList);
+ FRIEND_TEST(MetricsServiceTest, CorruptSizeOfLogList);
+ FRIEND_TEST(MetricsServiceTest, CorruptChecksumOfLogList);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, ClientIdGeneratesAllZeroes);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, ClientIdGeneratesCorrectly);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, ClientIdCorrectlyFormatted);
diff --git a/chrome/browser/metrics/metrics_service_unittest.cc b/chrome/browser/metrics/metrics_service_unittest.cc
index 9de7644..e2c822d 100644
--- a/chrome/browser/metrics/metrics_service_unittest.cc
+++ b/chrome/browser/metrics/metrics_service_unittest.cc
@@ -5,6 +5,11 @@
#include "chrome/browser/metrics/metrics_service.h"
#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/md5.h"
+
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_POSIX) && defined(LINUX2)
@@ -43,3 +48,177 @@ TEST(MetricsServiceTest, GetHardwareClass) {
EXPECT_EQ("unknown", hardware_class);
}
#endif // OS_CHROMEOS
+
+class MetricsServiceTest : public ::testing::Test {
+};
+
+static const size_t kMaxLocalListSize = 3;
+
+// Store and retrieve empty list.
+TEST(MetricsServiceTest, EmptyLogList) {
+ ListValue list;
+ std::vector<std::string> local_list;
+
+ MetricsService::StoreUnsentLogsHelper(local_list, kMaxLocalListSize, &list);
+ EXPECT_EQ(0U, list.GetSize());
+
+ local_list.clear(); // RecallUnsentLogsHelper() expects empty |local_list|.
+ EXPECT_EQ(MetricsService::LIST_EMPTY,
+ MetricsService::RecallUnsentLogsHelper(list, &local_list));
+ EXPECT_EQ(0U, local_list.size());
+}
+
+// Store and retrieve a single log value.
+TEST(MetricsServiceTest, SingleElementLogList) {
+ ListValue list;
+ std::vector<std::string> local_list;
+
+ local_list.push_back("Hello world!");
+ EXPECT_EQ(1U, local_list.size());
+
+ MetricsService::StoreUnsentLogsHelper(local_list, kMaxLocalListSize, &list);
+
+ // |list| will now contain the following:
+ // [1, Base64Encode("Hello world!"), MD5("Hello world!")].
+ EXPECT_EQ(3U, list.GetSize());
+
+ // Examine each element.
+ ListValue::const_iterator it = list.begin();
+ int size = 0;
+ (*it)->GetAsInteger(&size);
+ EXPECT_EQ(1, size);
+
+ ++it;
+ std::string str;
+ (*it)->GetAsString(&str); // Base64 encoded "Hello world!" string.
+ std::string encoded;
+ base::Base64Encode("Hello world!", &encoded);
+ EXPECT_TRUE(encoded == str);
+
+ ++it;
+ (*it)->GetAsString(&str); // MD5 for encoded "Hello world!" string.
+ EXPECT_TRUE(MD5String(encoded) == str);
+
+ ++it;
+ EXPECT_TRUE(it == list.end()); // Reached end of list.
+
+ local_list.clear();
+ EXPECT_EQ(MetricsService::RECALL_SUCCESS,
+ MetricsService::RecallUnsentLogsHelper(list, &local_list));
+ EXPECT_EQ(1U, local_list.size());
+}
+
+// Store elements greater than the limit.
+TEST(MetricsServiceTest, OverLimitLogList) {
+ ListValue list;
+ std::vector<std::string> local_list;
+
+ local_list.push_back("one");
+ local_list.push_back("two");
+ local_list.push_back("three");
+ local_list.push_back("four");
+ EXPECT_EQ(4U, local_list.size());
+
+ std::string expected_first;
+ base::Base64Encode(local_list[local_list.size() - kMaxLocalListSize],
+ &expected_first);
+ std::string expected_last;
+ base::Base64Encode(local_list[local_list.size() - 1],
+ &expected_last);
+
+ MetricsService::StoreUnsentLogsHelper(local_list, kMaxLocalListSize, &list);
+ EXPECT_EQ(kMaxLocalListSize + 2, list.GetSize());
+
+ std::string actual_first;
+ EXPECT_TRUE((*(list.begin() + 1))->GetAsString(&actual_first));
+ EXPECT_TRUE(expected_first == actual_first);
+
+ std::string actual_last;
+ EXPECT_TRUE((*(list.end() - 2))->GetAsString(&actual_last));
+ EXPECT_TRUE(expected_last == actual_last);
+
+ local_list.clear();
+ EXPECT_EQ(MetricsService::RECALL_SUCCESS,
+ MetricsService::RecallUnsentLogsHelper(list, &local_list));
+ EXPECT_EQ(kMaxLocalListSize, local_list.size());
+}
+
+// Induce LIST_SIZE_TOO_SMALL corruption
+TEST(MetricsServiceTest, SmallRecoveredListSize) {
+ ListValue list;
+ std::vector<std::string> local_list;
+
+ local_list.push_back("Hello world!");
+ EXPECT_EQ(1U, local_list.size());
+ MetricsService::StoreUnsentLogsHelper(local_list, kMaxLocalListSize, &list);
+ EXPECT_EQ(3U, list.GetSize());
+
+ // Remove last element.
+ list.Remove(list.GetSize() - 1, NULL);
+ EXPECT_EQ(2U, list.GetSize());
+
+ local_list.clear();
+ EXPECT_EQ(MetricsService::LIST_SIZE_TOO_SMALL,
+ MetricsService::RecallUnsentLogsHelper(list, &local_list));
+}
+
+// Remove size from the stored list.
+TEST(MetricsServiceTest, RemoveSizeFromLogList) {
+ ListValue list;
+ std::vector<std::string> local_list;
+
+ local_list.push_back("one");
+ local_list.push_back("two");
+ EXPECT_EQ(2U, local_list.size());
+ MetricsService::StoreUnsentLogsHelper(local_list, kMaxLocalListSize, &list);
+ EXPECT_EQ(4U, list.GetSize());
+
+ list.Remove(0, NULL); // Delete size (1st element).
+ EXPECT_EQ(3U, list.GetSize());
+
+ local_list.clear();
+ EXPECT_EQ(MetricsService::LIST_SIZE_MISSING,
+ MetricsService::RecallUnsentLogsHelper(list, &local_list));
+}
+
+// Corrupt size of stored list.
+TEST(MetricsServiceTest, CorruptSizeOfLogList) {
+ ListValue list;
+ std::vector<std::string> local_list;
+
+ local_list.push_back("Hello world!");
+ EXPECT_EQ(1U, local_list.size());
+ MetricsService::StoreUnsentLogsHelper(local_list, kMaxLocalListSize, &list);
+ EXPECT_EQ(3U, list.GetSize());
+
+ // Change list size from 1 to 2.
+ EXPECT_TRUE(list.Set(0, Value::CreateIntegerValue(2)));
+ EXPECT_EQ(3U, list.GetSize());
+
+ local_list.clear();
+ EXPECT_EQ(MetricsService::LIST_SIZE_CORRUPTION,
+ MetricsService::RecallUnsentLogsHelper(list, &local_list));
+}
+
+// Corrupt checksum of stored list.
+TEST(MetricsServiceTest, CorruptChecksumOfLogList) {
+ ListValue list;
+ std::vector<std::string> local_list;
+
+ local_list.clear();
+ local_list.push_back("Hello world!");
+ EXPECT_EQ(1U, local_list.size());
+ MetricsService::StoreUnsentLogsHelper(local_list, kMaxLocalListSize, &list);
+ EXPECT_EQ(3U, list.GetSize());
+
+ // Fetch checksum (last element) and change it.
+ std::string checksum;
+ EXPECT_TRUE((*(list.end() - 1))->GetAsString(&checksum));
+ checksum[0] = (checksum[0] == 'a') ? 'b' : 'a';
+ EXPECT_TRUE(list.Set(2, Value::CreateStringValue(checksum)));
+ EXPECT_EQ(3U, list.GetSize());
+
+ local_list.clear();
+ EXPECT_EQ(MetricsService::CHECKSUM_CORRUPTION,
+ MetricsService::RecallUnsentLogsHelper(list, &local_list));
+}