Commit c4894005 authored by Xing Liu's avatar Xing Liu Committed by Commit Bot

Query tiles: Only load TileGroup into memory during initialization.

Currently the UI may call the backend multiple times when showing
the subtiles. If the in memory TileGroup changed, the user may see
unrelated second level tiles loaded.

This CL only set the in memory TileGroup during initialization, in
order to make the UI always has the same TileGroup data.

During initialization, now we select the most recent valid TileGroup
instead of always picking the first valid tile group.

Also clean up TileManagerTest a little bit.

Bug: 1076665
Change-Id: I9c406509cebc06a3afabc09dcc1a873784f4db73
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2203697
Commit-Queue: Xing Liu <xingliu@chromium.org>
Reviewed-by: default avatarMin Qin <qinmin@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarHesen Zhang <hesen@chromium.org>
Reviewed-by: default avatarShakti Sahu <shaktisahu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769547}
parent 9c6ebbd9
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include <unordered_set>
#include <utility> #include <utility>
#include "base/bind.h" #include "base/bind.h"
...@@ -29,9 +30,6 @@ class TileManagerImpl : public TileManager { ...@@ -29,9 +30,6 @@ class TileManagerImpl : public TileManager {
clock_(clock), clock_(clock),
accept_languages_(accept_languages) {} accept_languages_(accept_languages) {}
TileManagerImpl(const TileManagerImpl& other) = delete;
TileManagerImpl& operator=(const TileManagerImpl& other) = delete;
private: private:
// TileManager implementation. // TileManager implementation.
void Init(TileGroupStatusCallback callback) override { void Init(TileGroupStatusCallback callback) override {
...@@ -47,31 +45,30 @@ class TileManagerImpl : public TileManager { ...@@ -47,31 +45,30 @@ class TileManagerImpl : public TileManager {
return; return;
} }
store_->Update(group->id, *group.get(), auto group_copy = *group;
store_->Update(group_copy.id, group_copy,
base::BindOnce(&TileManagerImpl::OnGroupSaved, base::BindOnce(&TileManagerImpl::OnGroupSaved,
weak_ptr_factory_.GetWeakPtr(), weak_ptr_factory_.GetWeakPtr(),
std::move(group), std::move(callback))); std::move(group), std::move(callback)));
} }
void GetTiles(GetTilesCallback callback) override { void GetTiles(GetTilesCallback callback) override {
if (!tile_group_ || !ValidateLocale(tile_group_.get())) { if (!tile_group_) {
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::vector<Tile>())); FROM_HERE, base::BindOnce(std::move(callback), std::vector<Tile>()));
return; return;
} }
std::vector<Tile> tiles; std::vector<Tile> tiles;
if (tile_group_ && ValidateGroup(tile_group_.get())) { for (const auto& tile : tile_group_->tiles)
for (const auto& tile : tile_group_->tiles) tiles.emplace_back(*tile.get());
tiles.emplace_back(*tile.get());
}
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(tiles))); FROM_HERE, base::BindOnce(std::move(callback), std::move(tiles)));
} }
void GetTile(const std::string& tile_id, TileCallback callback) override { void GetTile(const std::string& tile_id, TileCallback callback) override {
const Tile* result = nullptr; const Tile* result = nullptr;
if (tile_group_ && ValidateGroup(tile_group_.get())) { if (tile_group_) {
TileIterator it(*tile_group_, TileIterator::kAllTiles); TileIterator it(*tile_group_, TileIterator::kAllTiles);
while (it.HasNext()) { while (it.HasNext()) {
const auto* tile = it.Next(); const auto* tile = it.Next();
...@@ -106,43 +103,50 @@ class TileManagerImpl : public TileManager { ...@@ -106,43 +103,50 @@ class TileManagerImpl : public TileManager {
initialized_ = true; initialized_ = true;
PruneInvalidGroup(std::move(callback), std::move(loaded_groups)); PruneAndSelectGroup(std::move(callback), std::move(loaded_groups));
} }
// Filters out and deletes invalid groups from db and memory. // Select the most recent unexpired group from |loaded_groups| with the
void PruneInvalidGroup( // correct locale, and delete other groups.
void PruneAndSelectGroup(
TileGroupStatusCallback callback, TileGroupStatusCallback callback,
std::map<std::string, std::unique_ptr<TileGroup>> loaded_groups) { std::map<std::string, std::unique_ptr<TileGroup>> loaded_groups) {
DCHECK_LE(loaded_groups.size(), 1u);
TileGroupStatus status = TileGroupStatus::kSuccess; TileGroupStatus status = TileGroupStatus::kSuccess;
std::vector<std::string> to_deprecated_in_db; base::Time last_updated_time;
std::string selected_group_id;
for (const auto& pair : loaded_groups) { for (const auto& pair : loaded_groups) {
auto group_id = pair.first; DCHECK(!pair.first.empty()) << "Should not have empty tile group key.";
auto* group = pair.second.get(); auto* group = pair.second.get();
if (!ValidateGroup(group)) { if (!group)
to_deprecated_in_db.emplace_back(group_id); continue;
if (ValidateLocale(group) && !IsGroupExpired(group) &&
(group->last_updated_ts > last_updated_time)) {
last_updated_time = group->last_updated_ts;
selected_group_id = pair.first;
} }
} }
for (const auto& key : to_deprecated_in_db) { // Moves the selected group into in memory holder.
DeleteGroup(key); if (!selected_group_id.empty()) {
loaded_groups.erase(key); tile_group_ = std::move(loaded_groups[selected_group_id]);
loaded_groups.erase(selected_group_id);
} else {
status = TileGroupStatus::kNoTiles;
} }
// Moves the valid group into in memory holder. // Deletes other groups.
if (!loaded_groups.empty()) for (const auto& group_to_delete : loaded_groups)
std::swap(tile_group_, loaded_groups.begin()->second); DeleteGroup(group_to_delete.first);
else
status = TileGroupStatus::kNoTiles;
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), status)); FROM_HERE, base::BindOnce(std::move(callback), status));
} }
// Returns true if the group is not expired. // Returns true if the group is expired.
bool ValidateGroup(const TileGroup* group) const { bool IsGroupExpired(const TileGroup* group) const {
return clock_->Now() - group->last_updated_ts < return clock_->Now() >=
TileConfig::GetExpireDuration(); group->last_updated_ts + TileConfig::GetExpireDuration();
} }
// Check whether |locale_| matches with that of the |group|. // Check whether |locale_| matches with that of the |group|.
...@@ -172,10 +176,10 @@ class TileManagerImpl : public TileManager { ...@@ -172,10 +176,10 @@ class TileManagerImpl : public TileManager {
return; return;
} }
if (tile_group_) // Only swap the in memory tile group when there is no existing tile group.
DeleteGroup(tile_group_->id); if (!tile_group_)
tile_group_ = std::move(group);
std::swap(tile_group_, group);
std::move(callback).Run(TileGroupStatus::kSuccess); std::move(callback).Run(TileGroupStatus::kSuccess);
} }
......
...@@ -17,11 +17,14 @@ ...@@ -17,11 +17,14 @@
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
using testing::_; using testing::_;
using ::testing::Invoke; using testing::Invoke;
using testing::StrictMock;
namespace query_tiles { namespace query_tiles {
namespace { namespace {
const char kGuid[] = "awesome_guid";
class MockTileStore : public Store<TileGroup> { class MockTileStore : public Store<TileGroup> {
public: public:
MockTileStore() = default; MockTileStore() = default;
...@@ -40,7 +43,7 @@ class TileManagerTest : public testing::Test { ...@@ -40,7 +43,7 @@ class TileManagerTest : public testing::Test {
TileManagerTest& operator=(const TileManagerTest& other) = delete; TileManagerTest& operator=(const TileManagerTest& other) = delete;
void SetUp() override { void SetUp() override {
auto tile_store = std::make_unique<MockTileStore>(); auto tile_store = std::make_unique<StrictMock<MockTileStore>>();
tile_store_ = tile_store.get(); tile_store_ = tile_store.get();
base::Time fake_now; base::Time fake_now;
EXPECT_TRUE(base::Time::FromString("03/18/20 01:00:00 AM", &fake_now)); EXPECT_TRUE(base::Time::FromString("03/18/20 01:00:00 AM", &fake_now));
...@@ -88,17 +91,31 @@ class TileManagerTest : public testing::Test { ...@@ -88,17 +91,31 @@ class TileManagerTest : public testing::Test {
std::move(closure).Run(); std::move(closure).Run();
} }
TileGroup CreateValidGroup(const std::string& group_id,
const std::string& tile_id) {
TileGroup group;
group.last_updated_ts = clock()->Now();
group.id = group_id;
group.locale = "en-US";
Tile tile;
tile.id = tile_id;
group.tiles.emplace_back(std::make_unique<Tile>(tile));
return group;
}
// Run SaveTiles call from manager_, compare the |expected_status| to the // Run SaveTiles call from manager_, compare the |expected_status| to the
// actual returned status. // actual returned status.
void SaveTiles(std::vector<std::unique_ptr<Tile>> tiles, void SaveTiles(std::vector<std::unique_ptr<Tile>> tiles,
TileGroupStatus expected_status) { TileGroupStatus expected_status) {
auto group = CreateValidGroup(kGuid, "");
group.tiles = std::move(tiles);
SaveTiles(group, expected_status);
}
void SaveTiles(const TileGroup& group, TileGroupStatus expected_status) {
base::RunLoop loop; base::RunLoop loop;
auto group = std::make_unique<TileGroup>();
group->locale = "en-US";
group->last_updated_ts = clock_.Now();
group->tiles = std::move(tiles);
manager()->SaveTiles( manager()->SaveTiles(
std::move(group), std::unique_ptr<TileGroup>(new TileGroup(group)),
base::BindOnce(&TileManagerTest::OnTilesSaved, base::Unretained(this), base::BindOnce(&TileManagerTest::OnTilesSaved, base::Unretained(this),
loop.QuitClosure(), expected_status)); loop.QuitClosure(), expected_status));
loop.Run(); loop.Run();
...@@ -157,9 +174,6 @@ class TileManagerTest : public testing::Test { ...@@ -157,9 +174,6 @@ class TileManagerTest : public testing::Test {
base::SimpleTestClock clock_; base::SimpleTestClock clock_;
}; };
// TODO(hesen): Add a test where we request tiles before the initialize
// callback from the DB.
TEST_F(TileManagerTest, InitAndLoadWithDbOperationFailed) { TEST_F(TileManagerTest, InitAndLoadWithDbOperationFailed) {
EXPECT_CALL(*tile_store(), InitAndLoad(_)) EXPECT_CALL(*tile_store(), InitAndLoad(_))
.WillOnce(Invoke([](base::OnceCallback<void( .WillOnce(Invoke([](base::OnceCallback<void(
...@@ -172,58 +186,37 @@ TEST_F(TileManagerTest, InitAndLoadWithDbOperationFailed) { ...@@ -172,58 +186,37 @@ TEST_F(TileManagerTest, InitAndLoadWithDbOperationFailed) {
} }
TEST_F(TileManagerTest, InitWithEmptyDb) { TEST_F(TileManagerTest, InitWithEmptyDb) {
EXPECT_CALL(*tile_store(), InitAndLoad(_)) InitWithData(TileGroupStatus::kNoTiles, std::vector<TileGroup>());
.WillOnce(Invoke([](base::OnceCallback<void(
bool, MockTileStore::KeysAndEntries)> callback) {
std::move(callback).Run(true, MockTileStore::KeysAndEntries());
}));
Init(TileGroupStatus::kNoTiles);
GetTiles(std::vector<Tile>() /*expect an empty result*/); GetTiles(std::vector<Tile>() /*expect an empty result*/);
} }
TEST_F(TileManagerTest, InitAndLoadWithExpiredGroup) { // Expired group will be deleted during initialization.
auto invalid_group = std::make_unique<TileGroup>(); TEST_F(TileManagerTest, InitAndLoadWithInvalidGroup) {
test::ResetTestGroup(invalid_group.get()); // Create an expired group.
invalid_group->last_updated_ts = auto expired_group = CreateValidGroup("expired_group_id", "tile_id");
clock()->Now() - base::TimeDelta::FromDays(3); expired_group.last_updated_ts = clock()->Now() - base::TimeDelta::FromDays(3);
MockTileStore::KeysAndEntries input;
input[invalid_group->id] = std::move(invalid_group);
EXPECT_CALL(*tile_store(), InitAndLoad(_))
.WillOnce(Invoke(
[&input](base::OnceCallback<void(bool, MockTileStore::KeysAndEntries)>
callback) {
std::move(callback).Run(true, std::move(input));
}));
EXPECT_CALL(*tile_store(), Delete(_, _)); // Locale mismatch group.
auto locale_mismatch_group =
CreateValidGroup("locale_group_id", "locale_tile_id");
locale_mismatch_group.locale = "";
Init(TileGroupStatus::kNoTiles); EXPECT_CALL(*tile_store(), Delete("expired_group_id", _));
GetTiles(std::vector<Tile>() /*expect an empty result*/); InitWithData(TileGroupStatus::kNoTiles, {expired_group});
GetTiles(std::vector<Tile>());
} }
// The most recent valid group will be selected during initialization.
TEST_F(TileManagerTest, InitAndLoadSuccess) { TEST_F(TileManagerTest, InitAndLoadSuccess) {
auto input_group = std::make_unique<TileGroup>(); // Two valid groups are loaded, the most recent one will be selected.
test::ResetTestGroup(input_group.get()); auto group1 = CreateValidGroup("group_id_1", "tile_id_1");
std::vector<Tile> expected; group1.last_updated_ts -= base::TimeDelta::FromMinutes(5);
input_group->last_updated_ts = auto group2 = CreateValidGroup("group_id_2", "tile_id_2");
clock()->Now() - base::TimeDelta::FromMinutes(5); const Tile expected = *group2.tiles[0];
for (const auto& tile : input_group->tiles)
expected.emplace_back(*tile.get()); EXPECT_CALL(*tile_store(), Delete("group_id_1", _));
InitWithData(TileGroupStatus::kSuccess, {group1, group2});
MockTileStore::KeysAndEntries input; GetTiles({expected});
input[input_group->id] = std::move(input_group);
EXPECT_CALL(*tile_store(), InitAndLoad(_))
.WillOnce(Invoke(
[&input](base::OnceCallback<void(bool, MockTileStore::KeysAndEntries)>
callback) {
std::move(callback).Run(true, std::move(input));
}));
EXPECT_CALL(*tile_store(), Delete(_, _)).Times(0);
Init(TileGroupStatus::kSuccess);
GetTiles(expected);
} }
// Failed to init an empty db, and save tiles call failed because of db is // Failed to init an empty db, and save tiles call failed because of db is
...@@ -305,46 +298,22 @@ TEST_F(TileManagerTest, SaveTilesSuccess) { ...@@ -305,46 +298,22 @@ TEST_F(TileManagerTest, SaveTilesSuccess) {
} }
// Init with store successfully. The store originally has entries loaded into // Init with store successfully. The store originally has entries loaded into
// memory. Save new tiles successfully. GetTiles should return the recent saved // memory. Save new tiles successfully. GetTiles should return original saved
// tiles, and Delete() call is executed in store, also replace the old group in // tiles.
// memory. TEST_F(TileManagerTest, SaveTilesStillReturnOldTiles) {
TEST_F(TileManagerTest, SaveTilesAndReplaceOldGroupSuccess) { TileGroup old_group = CreateValidGroup("old_group_id", "old_tile_id");
auto input_group = std::make_unique<TileGroup>(); const Tile old_tile = *old_group.tiles[0].get();
test::ResetTestGroup(input_group.get()); InitWithData(TileGroupStatus::kSuccess, {old_group});
input_group->last_updated_ts =
clock()->Now() - base::TimeDelta::FromMinutes(5); EXPECT_CALL(*tile_store(), Update("new_group_id", _, _))
MockTileStore::KeysAndEntries input;
input[input_group->id] = std::move(input_group);
EXPECT_CALL(*tile_store(), InitAndLoad(_))
.WillOnce(Invoke(
[&input](base::OnceCallback<void(bool, MockTileStore::KeysAndEntries)>
callback) {
std::move(callback).Run(true, std::move(input));
}));
EXPECT_CALL(*tile_store(), Update(_, _, _))
.WillOnce(Invoke([](const std::string& id, const TileGroup& group, .WillOnce(Invoke([](const std::string& id, const TileGroup& group,
MockTileStore::UpdateCallback callback) { MockTileStore::UpdateCallback callback) {
std::move(callback).Run(true); std::move(callback).Run(true);
})); }));
EXPECT_CALL(*tile_store(), Delete("group_guid", _)); TileGroup new_group = CreateValidGroup("new_group_id", "new_tile_id");
SaveTiles(new_group, TileGroupStatus::kSuccess);
Init(TileGroupStatus::kSuccess); GetTiles({old_tile});
auto tile_to_save = std::make_unique<Tile>();
test::ResetTestEntry(tile_to_save.get());
std::vector<std::unique_ptr<Tile>> tiles_to_save;
tiles_to_save.emplace_back(std::move(tile_to_save));
auto expected_tile = std::make_unique<Tile>();
test::ResetTestEntry(expected_tile.get());
std::vector<Tile> expected;
expected.emplace_back(std::move(*expected_tile.get()));
SaveTiles(std::move(tiles_to_save), TileGroupStatus::kSuccess);
GetTiles(std::move(expected));
} }
// Verifies GetTile(tile_id) API can return the right thing. // Verifies GetTile(tile_id) API can return the right thing.
...@@ -362,8 +331,9 @@ TEST_F(TileManagerTest, GetTilesWithoutMatchingAcceptLanguages) { ...@@ -362,8 +331,9 @@ TEST_F(TileManagerTest, GetTilesWithoutMatchingAcceptLanguages) {
manager()->SetAcceptLanguagesForTesting("zh"); manager()->SetAcceptLanguagesForTesting("zh");
TileGroup group; TileGroup group;
test::ResetTestGroup(&group); test::ResetTestGroup(&group);
InitWithData(TileGroupStatus::kSuccess, {group});
EXPECT_CALL(*tile_store(), Delete("group_guid", _));
InitWithData(TileGroupStatus::kNoTiles, {group});
GetTiles(std::vector<Tile>()); GetTiles(std::vector<Tile>());
} }
...@@ -371,20 +341,11 @@ TEST_F(TileManagerTest, GetTilesWithoutMatchingAcceptLanguages) { ...@@ -371,20 +341,11 @@ TEST_F(TileManagerTest, GetTilesWithoutMatchingAcceptLanguages) {
// is found. // is found.
TEST_F(TileManagerTest, GetTilesWithMatchingAcceptLanguages) { TEST_F(TileManagerTest, GetTilesWithMatchingAcceptLanguages) {
manager()->SetAcceptLanguagesForTesting("zh, en"); manager()->SetAcceptLanguagesForTesting("zh, en");
TileGroup group; TileGroup group = CreateValidGroup("group_id", "tile_id");
group.id = "gid"; const Tile tile = *group.tiles[0];
group.locale = "en-US";
group.last_updated_ts = clock()->Now();
auto tile = std::make_unique<Tile>();
test::ResetTestEntry(tile.get());
group.tiles.emplace_back(std::move(tile));
InitWithData(TileGroupStatus::kSuccess, {group});
auto expected_tile = std::make_unique<Tile>(); InitWithData(TileGroupStatus::kSuccess, {group});
test::ResetTestEntry(expected_tile.get()); GetTiles({tile});
std::vector<Tile> expected;
expected.emplace_back(std::move(*expected_tile.get()));
GetTiles(std::move(expected));
} }
} // namespace } // namespace
......
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