// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/metrics/persisted_logs.h"

#include "base/base64.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/prefs/testing_pref_service.h"
#include "base/rand_util.h"
#include "base/sha1.h"
#include "base/values.h"
#include "components/metrics/compression_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace metrics {

namespace {

const char kTestPrefName[] = "TestPref";
const size_t kLogCountLimit = 3;
const size_t kLogByteLimit = 1000;

// Compresses |log_data| and returns the result.
std::string Compress(const std::string& log_data) {
  std::string compressed_log_data;
  EXPECT_TRUE(GzipCompress(log_data, &compressed_log_data));
  return compressed_log_data;
}

// Generates and returns log data such that its size after compression is at
// least |min_compressed_size|.
std::string GenerateLogWithMinCompressedSize(size_t min_compressed_size) {
  // Since the size check is done against a compressed log, generate enough
  // data that compresses to larger than |log_size|.
  std::string rand_bytes = base::RandBytesAsString(min_compressed_size);
  while (Compress(rand_bytes).size() < min_compressed_size)
    rand_bytes.append(base::RandBytesAsString(min_compressed_size));
  std::string base64_data_for_logging;
  base::Base64Encode(rand_bytes, &base64_data_for_logging);
  SCOPED_TRACE(testing::Message() << "Using random data "
                                  << base64_data_for_logging);
  return rand_bytes;
}

class PersistedLogsTest : public testing::Test {
 public:
  PersistedLogsTest() {
    prefs_.registry()->RegisterListPref(kTestPrefName);
  }

 protected:
  TestingPrefServiceSimple prefs_;

 private:
  DISALLOW_COPY_AND_ASSIGN(PersistedLogsTest);
};

class TestPersistedLogs : public PersistedLogs {
 public:
  TestPersistedLogs(PrefService* service, size_t min_log_bytes)
      : PersistedLogs(service, kTestPrefName, kLogCountLimit, min_log_bytes,
                      0) {
  }

  // Stages and removes the next log, while testing it's value.
  void ExpectNextLog(const std::string& expected_log) {
    StageLog();
    EXPECT_EQ(staged_log(), Compress(expected_log));
    DiscardStagedLog();
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(TestPersistedLogs);
};

}  // namespace

// Store and retrieve empty list_value.
TEST_F(PersistedLogsTest, EmptyLogList) {
  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);

  persisted_logs.SerializeLogs();
  const base::ListValue* list_value = prefs_.GetList(kTestPrefName);
  EXPECT_EQ(0U, list_value->GetSize());

  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
  EXPECT_EQ(PersistedLogs::LIST_EMPTY, result_persisted_logs.DeserializeLogs());
  EXPECT_EQ(0U, result_persisted_logs.size());
}

// Store and retrieve a single log value.
TEST_F(PersistedLogsTest, SingleElementLogList) {
  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);

  persisted_logs.StoreLog("Hello world!");
  persisted_logs.SerializeLogs();

  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
            result_persisted_logs.DeserializeLogs());
  EXPECT_EQ(1U, result_persisted_logs.size());

  // Verify that the result log matches the initial log.
  persisted_logs.StageLog();
  result_persisted_logs.StageLog();
  EXPECT_EQ(persisted_logs.staged_log(), result_persisted_logs.staged_log());
  EXPECT_EQ(persisted_logs.staged_log_hash(),
            result_persisted_logs.staged_log_hash());
}

// Store a set of logs over the length limit, but smaller than the min number of
// bytes.
TEST_F(PersistedLogsTest, LongButTinyLogList) {
  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);

  size_t log_count = kLogCountLimit * 5;
  for (size_t i = 0; i < log_count; ++i)
    persisted_logs.StoreLog("x");

  persisted_logs.SerializeLogs();

  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
            result_persisted_logs.DeserializeLogs());
  EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());

  result_persisted_logs.ExpectNextLog("x");
}

// Store a set of logs over the length limit, but that doesn't reach the minimum
// number of bytes until after passing the length limit.
TEST_F(PersistedLogsTest, LongButSmallLogList) {
  size_t log_count = kLogCountLimit * 5;
  size_t log_size = 50;

  std::string first_kept = "First to keep";
  first_kept.resize(log_size, ' ');

  std::string blank_log = std::string(log_size, ' ');

  std::string last_kept = "Last to keep";
  last_kept.resize(log_size, ' ');

  // Set the byte limit enough to keep everything but the first two logs.
  const size_t min_log_bytes =
      Compress(first_kept).length() + Compress(last_kept).length() +
      (log_count - 4) * Compress(blank_log).length();
  TestPersistedLogs persisted_logs(&prefs_, min_log_bytes);

  persisted_logs.StoreLog("one");
  persisted_logs.StoreLog("two");
  persisted_logs.StoreLog(first_kept);
  for (size_t i = persisted_logs.size(); i < log_count - 1; ++i) {
    persisted_logs.StoreLog(blank_log);
  }
  persisted_logs.StoreLog(last_kept);
  persisted_logs.SerializeLogs();

  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
            result_persisted_logs.DeserializeLogs());
  EXPECT_EQ(persisted_logs.size() - 2, result_persisted_logs.size());

  result_persisted_logs.ExpectNextLog(last_kept);
  while (result_persisted_logs.size() > 1) {
    result_persisted_logs.ExpectNextLog(blank_log);
  }
  result_persisted_logs.ExpectNextLog(first_kept);
}

// Store a set of logs within the length limit, but well over the minimum
// number of bytes.
TEST_F(PersistedLogsTest, ShortButLargeLogList) {
  // Make the total byte count about twice the minimum.
  size_t log_count = kLogCountLimit;
  size_t log_size = (kLogByteLimit / log_count) * 2;
  std::string log_data = GenerateLogWithMinCompressedSize(log_size);

  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
  for (size_t i = 0; i < log_count; ++i) {
    persisted_logs.StoreLog(log_data);
  }
  persisted_logs.SerializeLogs();

  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
            result_persisted_logs.DeserializeLogs());
  EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());
}

// Store a set of logs over the length limit, and over the minimum number of
// bytes.
TEST_F(PersistedLogsTest, LongAndLargeLogList) {
  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);

  // Include twice the max number of logs.
  size_t log_count = kLogCountLimit * 2;
  // Make the total byte count about four times the minimum.
  size_t log_size = (kLogByteLimit / log_count) * 4;

  std::string target_log = "First to keep";
  target_log += GenerateLogWithMinCompressedSize(log_size);

  std::string log_data = GenerateLogWithMinCompressedSize(log_size);
  for (size_t i = 0; i < log_count; ++i) {
    if (i == log_count - kLogCountLimit)
      persisted_logs.StoreLog(target_log);
    else
      persisted_logs.StoreLog(log_data);
  }

  persisted_logs.SerializeLogs();

  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
            result_persisted_logs.DeserializeLogs());
  EXPECT_EQ(kLogCountLimit, result_persisted_logs.size());

  while (result_persisted_logs.size() > 1) {
    result_persisted_logs.ExpectNextLog(log_data);
  }
  result_persisted_logs.ExpectNextLog(target_log);
}

// Check that the store/stage/discard functions work as expected.
TEST_F(PersistedLogsTest, Staging) {
  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
  std::string tmp;

  EXPECT_FALSE(persisted_logs.has_staged_log());
  persisted_logs.StoreLog("one");
  EXPECT_FALSE(persisted_logs.has_staged_log());
  persisted_logs.StoreLog("two");
  persisted_logs.StageLog();
  EXPECT_TRUE(persisted_logs.has_staged_log());
  EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
  persisted_logs.StoreLog("three");
  EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
  EXPECT_EQ(persisted_logs.size(), 3U);
  persisted_logs.DiscardStagedLog();
  EXPECT_FALSE(persisted_logs.has_staged_log());
  EXPECT_EQ(persisted_logs.size(), 2U);
  persisted_logs.StageLog();
  EXPECT_EQ(persisted_logs.staged_log(), Compress("three"));
  persisted_logs.DiscardStagedLog();
  persisted_logs.StageLog();
  EXPECT_EQ(persisted_logs.staged_log(), Compress("one"));
  persisted_logs.DiscardStagedLog();
  EXPECT_FALSE(persisted_logs.has_staged_log());
  EXPECT_EQ(persisted_logs.size(), 0U);
}

TEST_F(PersistedLogsTest, DiscardOrder) {
  // Ensure that the correct log is discarded if new logs are pushed while
  // a log is staged.
  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);

  persisted_logs.StoreLog("one");
  persisted_logs.StageLog();
  persisted_logs.StoreLog("two");
  persisted_logs.DiscardStagedLog();
  persisted_logs.SerializeLogs();

  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
  EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
            result_persisted_logs.DeserializeLogs());
  EXPECT_EQ(1U, result_persisted_logs.size());
  result_persisted_logs.ExpectNextLog("two");
}


TEST_F(PersistedLogsTest, Hashes) {
  const char kFooText[] = "foo";
  const std::string foo_hash = base::SHA1HashString(kFooText);

  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
  persisted_logs.StoreLog(kFooText);
  persisted_logs.StageLog();

  EXPECT_EQ(Compress(kFooText), persisted_logs.staged_log());
  EXPECT_EQ(foo_hash, persisted_logs.staged_log_hash());
}

}  // namespace metrics