root/chrome/browser/prefs/pref_hash_store_impl_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. hash_store_id
  2. Reset
  3. IsInitialized
  4. GetVersion
  5. SetVersion
  6. GetContents
  7. GetMutableContents
  8. GetSuperMac
  9. SetSuperMac
  10. CommitPendingWrite
  11. CreateHashStoreContents
  12. TEST_F
  13. TEST_F
  14. TEST_F
  15. TEST_F
  16. TEST_F

// Copyright 2013 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 "chrome/browser/prefs/pref_hash_store_impl.h"

#include <string>

#include "base/macros.h"
#include "base/values.h"
#include "chrome/browser/prefs/pref_hash_store_impl.h"
#include "chrome/browser/prefs/pref_hash_store_transaction.h"
#include "chrome/browser/prefs/tracked/hash_store_contents.h"
#include "testing/gtest/include/gtest/gtest.h"

class MockHashStoreContents : public HashStoreContents {
 public:
  // Keep the data separate from the API implementation so that it can be owned
  // by the test and reused. The API implementation is owned by the
  // PrefHashStoreImpl.
  struct Data {
    Data() : commit_performed(false) {}

    // Returns the current value of |commit_performed| and resets it to false
    // immediately after.
    bool GetCommitPerformedAndReset() {
      bool current_commit_performed = commit_performed;
      commit_performed = false;
      return current_commit_performed;
    }

    scoped_ptr<base::DictionaryValue> contents;
    std::string super_mac;
    scoped_ptr<int> version;
    bool commit_performed;
  };

  explicit MockHashStoreContents(Data* data) : data_(data) {}

  // HashStoreContents implementation
  virtual std::string hash_store_id() const OVERRIDE { return "store_id"; }

  virtual void Reset() OVERRIDE {
    data_->contents.reset();
    data_->super_mac = "";
    data_->version.reset();
  }

  virtual bool IsInitialized() const OVERRIDE { return data_->contents; }

  virtual bool GetVersion(int* version) const OVERRIDE {
    if (data_->version)
      *version = *data_->version;
    return data_->version;
  }

  virtual void SetVersion(int version) OVERRIDE {
    data_->version.reset(new int(version));
  }

  virtual const base::DictionaryValue* GetContents() const OVERRIDE {
    return data_->contents.get();
  }

  virtual scoped_ptr<MutableDictionary> GetMutableContents() OVERRIDE {
    return scoped_ptr<MutableDictionary>(new MockMutableDictionary(data_));
  }

  virtual std::string GetSuperMac() const OVERRIDE { return data_->super_mac; }

  virtual void SetSuperMac(const std::string& super_mac) OVERRIDE {
    data_->super_mac = super_mac;
  }

  virtual void CommitPendingWrite() OVERRIDE {
    EXPECT_FALSE(data_->commit_performed);
    data_->commit_performed = true;
  }

 private:
  class MockMutableDictionary : public MutableDictionary {
   public:
    explicit MockMutableDictionary(Data* data) : data_(data) {}

    // MutableDictionary implementation
    virtual base::DictionaryValue* operator->() OVERRIDE {
      if (!data_->contents)
        data_->contents.reset(new base::DictionaryValue);
      return data_->contents.get();
    }

   private:
    Data* data_;
    DISALLOW_COPY_AND_ASSIGN(MockMutableDictionary);
  };

  Data* data_;

  DISALLOW_COPY_AND_ASSIGN(MockHashStoreContents);
};

class PrefHashStoreImplTest : public testing::Test {
 protected:
  scoped_ptr<HashStoreContents> CreateHashStoreContents() {
    return scoped_ptr<HashStoreContents>(
        new MockHashStoreContents(&hash_store_data_));
  }

  MockHashStoreContents::Data hash_store_data_;
};

TEST_F(PrefHashStoreImplTest, AtomicHashStoreAndCheck) {
  base::StringValue string_1("string1");
  base::StringValue string_2("string2");

  {
    // 32 NULL bytes is the seed that was used to generate the legacy hash.
    PrefHashStoreImpl pref_hash_store(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction());

    // Only NULL should be trusted in the absence of a hash.
    EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("path1", &string_1));
    EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("path1", NULL));

    transaction->StoreHash("path1", &string_1);
    EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
              transaction->CheckValue("path1", &string_1));
    EXPECT_EQ(PrefHashStoreTransaction::CLEARED,
              transaction->CheckValue("path1", NULL));
    transaction->StoreHash("path1", NULL);
    EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
              transaction->CheckValue("path1", NULL));
    EXPECT_EQ(PrefHashStoreTransaction::CHANGED,
              transaction->CheckValue("path1", &string_2));

    base::DictionaryValue dict;
    dict.Set("a", new base::StringValue("foo"));
    dict.Set("d", new base::StringValue("bad"));
    dict.Set("b", new base::StringValue("bar"));
    dict.Set("c", new base::StringValue("baz"));

    // Manually shove in a legacy hash.
    (*CreateHashStoreContents()->GetMutableContents())->SetString(
        "path1",
        "C503FB7C65EEFD5C07185F616A0AA67923C069909933F362022B1F187E73E9A2");

    EXPECT_EQ(PrefHashStoreTransaction::WEAK_LEGACY,
              transaction->CheckValue("path1", &dict));
    transaction->StoreHash("path1", &dict);
    EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
              transaction->CheckValue("path1", &dict));

    // Test that the |pref_hash_store| flushes its changes on request post
    // transaction.
    transaction.reset();
    pref_hash_store.CommitPendingWrite();
    EXPECT_TRUE(hash_store_data_.GetCommitPerformedAndReset());
  }

  {
    // |pref_hash_store2| should trust its initial hashes dictionary and thus
    // trust new unknown values.
    PrefHashStoreImpl pref_hash_store2(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store2.BeginTransaction());
    EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", &string_1));
    EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", &string_2));
    EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", NULL));

    // Test that |pref_hash_store2| doesn't flush its contents to disk when it
    // didn't change.
    transaction.reset();
    pref_hash_store2.CommitPendingWrite();
    EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset());
  }

  // Manually corrupt the super MAC.
  hash_store_data_.super_mac = std::string(64, 'A');

  {
    // |pref_hash_store3| should no longer trust its initial hashes dictionary
    // and thus shouldn't trust non-NULL unknown values.
    PrefHashStoreImpl pref_hash_store3(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store3.BeginTransaction());
    EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", &string_1));
    EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", &string_2));
    EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", NULL));

    // Test that |pref_hash_store3| doesn't flush its contents to disk when it
    // didn't change.
    transaction.reset();
    pref_hash_store3.CommitPendingWrite();
    EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset());
  }
}

TEST_F(PrefHashStoreImplTest, SplitHashStoreAndCheck) {
  base::DictionaryValue dict;
  dict.Set("a", new base::StringValue("to be replaced"));
  dict.Set("b", new base::StringValue("same"));
  dict.Set("o", new base::StringValue("old"));

  base::DictionaryValue modified_dict;
  modified_dict.Set("a", new base::StringValue("replaced"));
  modified_dict.Set("b", new base::StringValue("same"));
  modified_dict.Set("c", new base::StringValue("new"));

  base::DictionaryValue empty_dict;

  std::vector<std::string> invalid_keys;

  {
    PrefHashStoreImpl pref_hash_store(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction());

    // No hashes stored yet and hashes dictionary is empty (and thus not
    // trusted).
    EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    transaction->StoreSplitHash("path1", &dict);

    // Verify match post storage.
    EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Verify new path is still unknown.
    EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("path2", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Verify NULL or empty dicts are declared as having been cleared.
    EXPECT_EQ(PrefHashStoreTransaction::CLEARED,
              transaction->CheckSplitValue("path1", NULL, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
    EXPECT_EQ(
        PrefHashStoreTransaction::CLEARED,
        transaction->CheckSplitValue("path1", &empty_dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Verify changes are properly detected.
    EXPECT_EQ(
        PrefHashStoreTransaction::CHANGED,
        transaction->CheckSplitValue("path1", &modified_dict, &invalid_keys));
    std::vector<std::string> expected_invalid_keys1;
    expected_invalid_keys1.push_back("a");
    expected_invalid_keys1.push_back("c");
    expected_invalid_keys1.push_back("o");
    EXPECT_EQ(expected_invalid_keys1, invalid_keys);
    invalid_keys.clear();

    // Verify |dict| still matches post check.
    EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Store hash for |modified_dict|.
    transaction->StoreSplitHash("path1", &modified_dict);

    // Verify |modified_dict| is now the one that verifies correctly.
    EXPECT_EQ(
        PrefHashStoreTransaction::UNCHANGED,
        transaction->CheckSplitValue("path1", &modified_dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Verify old dict no longer matches.
    EXPECT_EQ(PrefHashStoreTransaction::CHANGED,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    std::vector<std::string> expected_invalid_keys2;
    expected_invalid_keys2.push_back("a");
    expected_invalid_keys2.push_back("o");
    expected_invalid_keys2.push_back("c");
    EXPECT_EQ(expected_invalid_keys2, invalid_keys);
    invalid_keys.clear();

    // Test that the |pref_hash_store| flushes its changes on request.
    transaction.reset();
    pref_hash_store.CommitPendingWrite();
    EXPECT_TRUE(hash_store_data_.GetCommitPerformedAndReset());
  }

  {
    // |pref_hash_store2| should trust its initial hashes dictionary and thus
    // trust new unknown values.
    PrefHashStoreImpl pref_hash_store2(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store2.BeginTransaction());
    EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("new_path", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Test that |pref_hash_store2| doesn't flush its contents to disk when it
    // didn't change.
    transaction.reset();
    pref_hash_store2.CommitPendingWrite();
    EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset());
  }

  // Manually corrupt the super MAC.
  hash_store_data_.super_mac = std::string(64, 'A');

  {
    // |pref_hash_store3| should no longer trust its initial hashes dictionary
    // and thus shouldn't trust unknown values.
    PrefHashStoreImpl pref_hash_store3(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store3.BeginTransaction());
    EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("new_path", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Test that |pref_hash_store3| doesn't flush its contents to disk when it
    // didn't change.
    transaction.reset();
    pref_hash_store3.CommitPendingWrite();
    EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset());
  }
}

TEST_F(PrefHashStoreImplTest, EmptyAndNULLSplitDict) {
  base::DictionaryValue empty_dict;

  std::vector<std::string> invalid_keys;

  {
    PrefHashStoreImpl pref_hash_store(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction());

    // Store hashes for a random dict to be overwritten below.
    base::DictionaryValue initial_dict;
    initial_dict.Set("a", new base::StringValue("foo"));
    transaction->StoreSplitHash("path1", &initial_dict);

    // Verify stored empty dictionary matches NULL and empty dictionary back.
    transaction->StoreSplitHash("path1", &empty_dict);
    EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
              transaction->CheckSplitValue("path1", NULL, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
    EXPECT_EQ(
        PrefHashStoreTransaction::UNCHANGED,
        transaction->CheckSplitValue("path1", &empty_dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Same when storing NULL directly.
    transaction->StoreSplitHash("path1", NULL);
    EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
              transaction->CheckSplitValue("path1", NULL, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
    EXPECT_EQ(
        PrefHashStoreTransaction::UNCHANGED,
        transaction->CheckSplitValue("path1", &empty_dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }

  {
    // |pref_hash_store2| should trust its initial hashes dictionary (and thus
    // trust new unknown values) even though the last action done was to clear
    // the hashes for path1 by setting its value to NULL (this is a regression
    // test ensuring that the internal action of clearing some hashes does
    // update the stored hash of hashes).
    PrefHashStoreImpl pref_hash_store2(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store2.BeginTransaction());

    base::DictionaryValue tested_dict;
    tested_dict.Set("a", new base::StringValue("foo"));
    tested_dict.Set("b", new base::StringValue("bar"));
    EXPECT_EQ(
        PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
        transaction->CheckSplitValue("new_path", &tested_dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }
}

// Test that the PrefHashStore returns TRUSTED_UNKNOWN_VALUE when checking for
// a split preference even if there is an existing atomic preference's hash
// stored. There is no point providing a migration path for preferences
// switching strategies after their initial release as split preferences are
// turned into split preferences specifically because the atomic hash isn't
// considered useful.
TEST_F(PrefHashStoreImplTest, TrustedUnknownSplitValueFromExistingAtomic) {
  base::StringValue string("string1");

  base::DictionaryValue dict;
  dict.Set("a", new base::StringValue("foo"));
  dict.Set("d", new base::StringValue("bad"));
  dict.Set("b", new base::StringValue("bar"));
  dict.Set("c", new base::StringValue("baz"));

  {
    PrefHashStoreImpl pref_hash_store(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction());

    transaction->StoreHash("path1", &string);
    EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
              transaction->CheckValue("path1", &string));
  }

  {
    // Load a new |pref_hash_store2| in which the hashes dictionary is trusted.
    PrefHashStoreImpl pref_hash_store2(
        std::string(32, 0), "device_id", CreateHashStoreContents());
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store2.BeginTransaction());
    std::vector<std::string> invalid_keys;
    EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }
}

TEST_F(PrefHashStoreImplTest, GetCurrentVersion) {
  COMPILE_ASSERT(PrefHashStoreImpl::VERSION_LATEST == 2,
                 new_versions_should_be_tested_here);
  {
    PrefHashStoreImpl pref_hash_store(
        std::string(32, 0), "device_id", CreateHashStoreContents());

    // VERSION_UNINITIALIZED when no hashes are stored.
    EXPECT_EQ(PrefHashStoreImpl::VERSION_UNINITIALIZED,
              pref_hash_store.GetCurrentVersion());

    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction());
    base::StringValue string_value("foo");
    transaction->StoreHash("path1", &string_value);

    // Test that |pref_hash_store| flushes its content to disk when it
    // initializes its version.
    transaction.reset();
    pref_hash_store.CommitPendingWrite();
    EXPECT_TRUE(hash_store_data_.GetCommitPerformedAndReset());
  }
  {
    PrefHashStoreImpl pref_hash_store(
        std::string(32, 0), "device_id", CreateHashStoreContents());

    // VERSION_LATEST after storing a hash.
    EXPECT_EQ(PrefHashStoreImpl::VERSION_LATEST,
              pref_hash_store.GetCurrentVersion());

    // Test that |pref_hash_store| doesn't flush its contents to disk when it
    // didn't change.
    pref_hash_store.CommitPendingWrite();
    EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset());
  }

  // Manually clear the version number.
  hash_store_data_.version.reset();

  {
    PrefHashStoreImpl pref_hash_store(
        std::string(32, 0), "device_id", CreateHashStoreContents());

    // VERSION_PRE_MIGRATION when no version is stored.
    EXPECT_EQ(PrefHashStoreImpl::VERSION_PRE_MIGRATION,
              pref_hash_store.GetCurrentVersion());

    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction());

    // Test that |pref_hash_store| flushes its content to disk when it
    // re-initializes its version.
    transaction.reset();
    pref_hash_store.CommitPendingWrite();
    EXPECT_TRUE(hash_store_data_.GetCommitPerformedAndReset());
  }
  {
    PrefHashStoreImpl pref_hash_store(
        std::string(32, 0), "device_id", CreateHashStoreContents());

    // Back to VERSION_LATEST after performing a transaction from
    // VERSION_PRE_MIGRATION (the presence of an existing hash should be
    // sufficient, no need for the transaction itself to perform any work).
    EXPECT_EQ(PrefHashStoreImpl::VERSION_LATEST,
              pref_hash_store.GetCurrentVersion());

    // Test that |pref_hash_store| doesn't flush its contents to disk when it
    // didn't change (i.e., its version was already up-to-date and the only
    // transaction performed was empty).
    scoped_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction());
    transaction.reset();
    pref_hash_store.CommitPendingWrite();
    EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset());
  }
}

/* [<][>][^][v][top][bottom][index][help] */