Commit 059035c6 authored by Xing Liu's avatar Xing Liu Committed by Commit Bot

Notification scheduler: Add impression store.

This CL adds impression store that talked to the level database that
persists impression types.

Bug: 930968
Change-Id: I6e1074901f78a8a4fc23fd33d77507426edc89fc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1560490
Commit-Queue: Xing Liu <xingliu@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#649711}
parent 77bfc27f
...@@ -73,6 +73,8 @@ source_set("lib") { ...@@ -73,6 +73,8 @@ source_set("lib") {
"icon_store.h", "icon_store.h",
"impression_history_tracker.cc", "impression_history_tracker.cc",
"impression_history_tracker.h", "impression_history_tracker.h",
"impression_store.cc",
"impression_store.h",
"impression_types.cc", "impression_types.cc",
"impression_types.h", "impression_types.h",
"internal_types.h", "internal_types.h",
...@@ -112,6 +114,7 @@ source_set("unit_tests") { ...@@ -112,6 +114,7 @@ source_set("unit_tests") {
"distribution_policy_unittest.cc", "distribution_policy_unittest.cc",
"icon_store_unittest.cc", "icon_store_unittest.cc",
"impression_history_tracker_unittest.cc", "impression_history_tracker_unittest.cc",
"impression_store_unittest.cc",
"proto_conversion_unittest.cc", "proto_conversion_unittest.cc",
"scheduler_utils_unittest.cc", "scheduler_utils_unittest.cc",
] ]
......
// Copyright 2019 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/notifications/scheduler/impression_store.h"
#include "chrome/browser/notifications/scheduler/proto_conversion.h"
namespace leveldb_proto {
void DataToProto(notifications::ClientState* client_state,
notifications::proto::ClientState* proto) {
ClientStateToProto(client_state, proto);
}
void ProtoToData(notifications::proto::ClientState* proto,
notifications::ClientState* client_state) {
ClientStateFromProto(proto, client_state);
}
} // namespace leveldb_proto
namespace notifications {
ImpressionStore::ImpressionStore(
std::unique_ptr<
leveldb_proto::ProtoDatabase<proto::ClientState, ClientState>> db)
: db_(std::move(db)), weak_ptr_factory_(this) {}
ImpressionStore::~ImpressionStore() = default;
void ImpressionStore::InitAndLoad(LoadCallback callback) {
db_->Init(base::BindOnce(&ImpressionStore::OnDbInitialized,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void ImpressionStore::OnDbInitialized(LoadCallback callback,
leveldb_proto::Enums::InitStatus status) {
if (status != leveldb_proto::Enums::InitStatus::kOK) {
std::move(callback).Run(false, Entries());
return;
}
// Load the data after a successful initialization.
db_->LoadEntries(base::BindOnce(&ImpressionStore::OnDataLoaded,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void ImpressionStore::OnDataLoaded(LoadCallback callback,
bool success,
std::unique_ptr<EntryVector> entry_vector) {
// The database failed to load.
if (!success) {
std::move(callback).Run(false, Entries());
return;
}
// Success to load but no data.
if (!entry_vector) {
std::move(callback).Run(true, Entries());
return;
}
// Load data.
Entries entries;
for (auto it = entry_vector->begin(); it != entry_vector->end(); ++it) {
std::unique_ptr<ClientState> client_state = std::make_unique<ClientState>();
*client_state = std::move(*it);
entries.emplace_back(std::move(client_state));
}
std::move(callback).Run(true, std::move(entries));
}
void ImpressionStore::Add(const std::string& key,
const ClientState& client_state,
UpdateCallback callback) {
Update(key, client_state, std::move(callback));
}
void ImpressionStore::Update(const std::string& key,
const ClientState& client_state,
UpdateCallback callback) {
auto entries_to_save = std::make_unique<KeyEntryVector>();
entries_to_save->emplace_back(std::make_pair(key, client_state));
db_->UpdateEntries(std::move(entries_to_save),
std::make_unique<KeyVector>() /*keys_to_remove*/,
std::move(callback));
}
void ImpressionStore::Delete(const std::string& key, UpdateCallback callback) {
auto keys_to_delete = std::make_unique<KeyVector>();
keys_to_delete->emplace_back(key);
db_->UpdateEntries(std::make_unique<KeyEntryVector>() /*entries_to_save*/,
std::move(keys_to_delete), std::move(callback));
}
} // namespace notifications
// Copyright 2019 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.
#ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_IMPRESSION_STORE_H_
#define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_IMPRESSION_STORE_H_
#include <memory>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/notifications/proto/client_state.pb.h"
#include "chrome/browser/notifications/scheduler/collection_store.h"
#include "chrome/browser/notifications/scheduler/impression_types.h"
#include "components/leveldb_proto/public/proto_database.h"
// Forward declaration for proto conversion.
namespace leveldb_proto {
void DataToProto(notifications::ClientState* client_state,
notifications::proto::ClientState* proto);
void ProtoToData(notifications::proto::ClientState* proto,
notifications::ClientState* client_state);
} // namespace leveldb_proto
namespace notifications {
// An impression storage using a proto database to persist data.
class ImpressionStore : public CollectionStore<ClientState> {
public:
ImpressionStore(
std::unique_ptr<
leveldb_proto::ProtoDatabase<proto::ClientState, ClientState>> db);
~ImpressionStore() override;
private:
using KeyEntryVector = std::vector<std::pair<std::string, ClientState>>;
using KeyVector = std::vector<std::string>;
using EntryVector = std::vector<ClientState>;
// CollectionStore implementation.
void InitAndLoad(LoadCallback callback) override;
void Add(const std::string& key,
const ClientState& client_state,
UpdateCallback callback) override;
void Update(const std::string& key,
const ClientState& client_state,
UpdateCallback callback) override;
void Delete(const std::string& key, UpdateCallback callback) override;
// Called when the proto database is initialized but no yet loading the data
// into memory.
void OnDbInitialized(LoadCallback callback,
leveldb_proto::Enums::InitStatus status);
// Called after loading the data from database.
void OnDataLoaded(LoadCallback callback,
bool success,
std::unique_ptr<EntryVector> entry_vector);
std::unique_ptr<leveldb_proto::ProtoDatabase<proto::ClientState, ClientState>>
db_;
base::WeakPtrFactory<ImpressionStore> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ImpressionStore);
};
} // namespace notifications
#endif // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_IMPRESSION_STORE_H_
// Copyright 2019 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/notifications/scheduler/impression_store.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/test/scoped_task_environment.h"
#include "chrome/browser/notifications/proto/client_state.pb.h"
#include "chrome/browser/notifications/scheduler/impression_types.h"
#include "chrome/browser/notifications/scheduler/proto_conversion.h"
#include "components/leveldb_proto/public/proto_database.h"
#include "components/leveldb_proto/testing/fake_db.h"
#include "testing/gtest/include/gtest/gtest.h"
using leveldb_proto::test::FakeDB;
using InitStatus = leveldb_proto::Enums::InitStatus;
using Entries = notifications::ImpressionStore::Entries;
using DbEntries = std::vector<notifications::ClientState>;
using DbEntriesPtr = std::unique_ptr<std::vector<notifications::ClientState>>;
using TestClientStates = std::map<std::string, notifications::ClientState>;
namespace notifications {
namespace {
const char kClientStateKey[] = "guid_client_state_key1";
const ClientState kDefaultClientState;
// Test fixture to verify impression store.
class ImpressionStoreTest : public testing::Test {
public:
ImpressionStoreTest() : load_result_(false), db_(nullptr) {}
~ImpressionStoreTest() override = default;
void SetUp() override {}
protected:
// Initialize the store with test data.
void Init(const TestClientStates& test_data, InitStatus status) {
CreateTestProto(test_data);
auto db =
std::make_unique<FakeDB<proto::ClientState, ClientState>>(&db_entries_);
db_ = db.get();
store_ = std::make_unique<ImpressionStore>(std::move(db));
store_->InitAndLoad(base::BindOnce(&ImpressionStoreTest::OnEntriesLoaded,
base::Unretained(this)));
db_->InitStatusCallback(status);
}
bool load_result() const { return load_result_; }
const Entries& loaded_entries() const { return loaded_entries_; }
FakeDB<proto::ClientState, ClientState>* db() { return db_; }
CollectionStore<ClientState>* store() { return store_.get(); }
void OnEntriesLoaded(bool success, Entries entries) {
loaded_entries_ = std::move(entries);
load_result_ = success;
}
// Verifies the entries in the db is |expected|.
void VerifyDataInDb(DbEntriesPtr expected) {
db_->LoadEntries(base::BindOnce(
[](DbEntriesPtr expected, bool success, DbEntriesPtr entries) {
EXPECT_TRUE(success);
DCHECK(entries);
DCHECK(expected);
EXPECT_EQ(entries->size(), expected->size());
for (size_t i = 0, size = entries->size(); i < size; ++i) {
EXPECT_EQ((*entries)[i], (*expected)[i]);
}
},
std::move(expected)));
db_->LoadCallback(true);
}
private:
void CreateTestProto(const TestClientStates& client_states) {
for (const auto& pair : client_states) {
auto client_state(pair.second);
auto key = pair.first;
notifications::proto::ClientState proto;
ClientStateToProto(&client_state, &proto);
db_entries_.emplace(key, proto);
}
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::map<std::string, proto::ClientState> db_entries_;
bool load_result_;
Entries loaded_entries_;
FakeDB<proto::ClientState, ClientState>* db_;
std::unique_ptr<CollectionStore<ClientState>> store_;
DISALLOW_COPY_AND_ASSIGN(ImpressionStoreTest);
};
// Initializes an empty database should success.
TEST_F(ImpressionStoreTest, InitSuccessEmptyDb) {
Init(TestClientStates(), InitStatus::kOK);
db()->LoadCallback(true);
EXPECT_EQ(load_result(), true);
EXPECT_TRUE(loaded_entries().empty());
}
// Initialize non-empty database should success.
TEST_F(ImpressionStoreTest, InitSuccessWithData) {
auto test_data = TestClientStates();
test_data.emplace(kClientStateKey, kDefaultClientState);
Init(test_data, InitStatus::kOK);
db()->LoadCallback(true);
EXPECT_EQ(load_result(), true);
EXPECT_EQ(loaded_entries().size(), 1u);
EXPECT_EQ(*loaded_entries().back(), kDefaultClientState);
}
// Failure when loading the data will result in error.
TEST_F(ImpressionStoreTest, InitSuccessLoadFailed) {
Init(TestClientStates(), InitStatus::kOK);
db()->LoadCallback(false);
EXPECT_EQ(load_result(), false);
EXPECT_TRUE(loaded_entries().empty());
}
// Failed database initialization will result in error.
TEST_F(ImpressionStoreTest, InitFailed) {
Init(TestClientStates(), InitStatus::kCorrupt);
EXPECT_EQ(load_result(), false);
EXPECT_TRUE(loaded_entries().empty());
}
// Verifies adding data.
TEST_F(ImpressionStoreTest, Add) {
Init(TestClientStates(), InitStatus::kOK);
db()->LoadCallback(true);
EXPECT_EQ(load_result(), true);
EXPECT_TRUE(loaded_entries().empty());
// Add data to the store.
store()->Add(kClientStateKey, kDefaultClientState,
base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
db()->UpdateCallback(true);
// Verify the new data is in the database.
auto expected = std::make_unique<DbEntries>();
expected->emplace_back(kDefaultClientState);
VerifyDataInDb(std::move(expected));
}
// Verifies failure when adding data will result in error.
TEST_F(ImpressionStoreTest, AddFailed) {
Init(TestClientStates(), InitStatus::kOK);
db()->LoadCallback(true);
EXPECT_EQ(load_result(), true);
EXPECT_TRUE(loaded_entries().empty());
store()->Add(kClientStateKey, kDefaultClientState,
base::BindOnce([](bool success) { EXPECT_FALSE(success); }));
db()->UpdateCallback(false);
}
// Verifies updating data.
TEST_F(ImpressionStoreTest, Update) {
auto test_data = TestClientStates();
test_data.emplace(kClientStateKey, kDefaultClientState);
Init(test_data, InitStatus::kOK);
db()->LoadCallback(true);
EXPECT_EQ(load_result(), true);
ClientState new_client_state;
new_client_state.current_max_daily_show = 100;
// Update the database.
store()->Update(kClientStateKey, new_client_state,
base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
db()->UpdateCallback(true);
// Verify the updated data is in the database.
auto expected = std::make_unique<DbEntries>();
expected->emplace_back(new_client_state);
VerifyDataInDb(std::move(expected));
}
// Verifies deleting data.
TEST_F(ImpressionStoreTest, Delete) {
auto test_data = TestClientStates();
test_data.emplace(kClientStateKey, kDefaultClientState);
Init(test_data, InitStatus::kOK);
db()->LoadCallback(true);
EXPECT_EQ(load_result(), true);
// Delete the entry.
store()->Delete(kClientStateKey,
base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
// Verify there is no data in the database.
VerifyDataInDb(std::make_unique<DbEntries>());
}
} // namespace
} // namespace notifications
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment