Chromium Code Reviews| Index: chrome/browser/ui/app_list/search/history_unittest.cc |
| diff --git a/chrome/browser/ui/app_list/search/history_unittest.cc b/chrome/browser/ui/app_list/search/history_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a20417095cfc89a824e8815229a178ed8ae9be9e |
| --- /dev/null |
| +++ b/chrome/browser/ui/app_list/search/history_unittest.cc |
| @@ -0,0 +1,313 @@ |
| +// 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 "base/basictypes.h" |
| +#include "base/bind.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/message_loop.h" |
| +#include "base/run_loop.h" |
| +#include "base/stringprintf.h" |
| +#include "base/timer.h" |
| +#include "chrome/browser/ui/app_list/search/history.h" |
| +#include "chrome/browser/ui/app_list/search/history_data.h" |
| +#include "chrome/browser/ui/app_list/search/history_data_observer.h" |
| +#include "chrome/browser/ui/app_list/search/history_data_store.h" |
| +#include "chrome/test/base/testing_profile.h" |
| +#include "content/public/test/test_browser_thread.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace app_list { |
| +namespace test { |
| + |
| +namespace { |
| + |
| +const size_t kMaxEntry = 3; |
| +const size_t kMaxSecondary = 2; |
| + |
| +// HistoryDataLoadWaiter waits for give |data| to be loaded from underlying |
| +// store on the blocking pool. The waiter waits on the main message loop until |
| +// OnHistoryDataLoadedFromStore() is invoked or the maximum allowed wait time |
| +// has passed. |
| +class HistoryDataLoadWaiter : public HistoryDataObserver { |
| + public: |
| + explicit HistoryDataLoadWaiter(HistoryData* data) : data_(data) {} |
| + virtual ~HistoryDataLoadWaiter() {} |
| + |
| + void Wait(int wait_time_ms) { |
|
James Cook
2013/05/24 03:57:05
nit: I think changing |wait_time_ms| to |max_wait_
xiyuan
2013/05/24 16:33:02
Done.
|
| + data_->AddObserver(this); |
| + |
| + timer_.Start(FROM_HERE, |
| + base::TimeDelta::FromMilliseconds(wait_time_ms), |
| + this, |
| + &HistoryDataLoadWaiter::OnTimeOut); |
| + |
| + run_loop_.reset(new base::RunLoop); |
| + run_loop_->Run(); |
| + |
| + data_->RemoveObserver(this); |
| + } |
| + |
| + private: |
| + void OnTimeOut() { |
| + run_loop_->Quit(); |
| + } |
| + |
| + // HistoryDataObserver overrides: |
| + virtual void OnHistoryDataLoadedFromStore() OVERRIDE { |
| + run_loop_->Quit(); |
| + } |
| + |
| + HistoryData* data_; // Not owned. |
| + scoped_ptr<base::RunLoop> run_loop_; |
| + base::OneShotTimer<HistoryDataLoadWaiter> timer_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(HistoryDataLoadWaiter); |
| +}; |
| + |
| +// StoreFlushWaiter waits for the given |store| to flush its data to disk. |
| +// The flush and disk write happens on the blocking pool. The waiter waits |
| +// on the main message loop until the OnFlushed() is invoked or the maximum |
| +// allowed wait time has passed. |
| +class StoreFlushWaiter { |
| + public: |
| + explicit StoreFlushWaiter(HistoryDataStore* store) : store_(store) {} |
| + ~StoreFlushWaiter() {} |
| + |
| + void Wait(int wait_time_ms) { |
|
James Cook
2013/05/24 03:57:05
ditto, max_wait_time_ms or timeout_ms
xiyuan
2013/05/24 16:33:02
Done.
|
| + store_->Flush( |
| + base::Bind(&StoreFlushWaiter::OnFlushed, base::Unretained(this))); |
| + |
| + timer_.Start(FROM_HERE, |
| + base::TimeDelta::FromMilliseconds(wait_time_ms), |
| + this, |
| + &StoreFlushWaiter::OnTimeOut); |
| + |
| + run_loop_.reset(new base::RunLoop); |
| + run_loop_->Run(); |
| + } |
| + |
| + private: |
| + void OnTimeOut() { |
| + run_loop_->Quit(); |
| + } |
| + |
| + void OnFlushed() { |
| + run_loop_->Quit(); |
| + } |
| + |
| + HistoryDataStore* store_; // Not owned. |
| + scoped_ptr<base::RunLoop> run_loop_; |
| + base::OneShotTimer<StoreFlushWaiter> timer_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(StoreFlushWaiter); |
| +}; |
| + |
| +} // namespace |
| + |
| +class SearchHistoryTest : public testing::Test { |
| + public: |
| + SearchHistoryTest() |
| + : ui_thread_(content::BrowserThread::UI, &message_loop_) {} |
| + virtual ~SearchHistoryTest() {} |
| + |
| + // testing::Test overrides: |
| + virtual void SetUp() OVERRIDE { |
| + profile_.reset(new TestingProfile); |
| + CreateHistory(); |
| + } |
| + virtual void TearDown() OVERRIDE { |
| + Flush(); |
| + } |
| + |
| + void CreateHistory() { |
| + history_.reset(new History(profile_.get())); |
| + |
| + // Replace |data_| with test params. |
| + history_->data_->RemoveObserver(history_.get()); |
| + history_->data_.reset(new HistoryData(history_->store_, |
| + kMaxEntry, |
| + kMaxSecondary)); |
| + history_->data_->AddObserver(history_.get()); |
| + |
| + HistoryDataLoadWaiter waiter(history_->data_.get()); |
| + waiter.Wait(1000); |
| + ASSERT_TRUE(history_->IsReady()); |
| + } |
| + |
| + void Flush() { |
| + StoreFlushWaiter waiter(history_->store_.get()); |
| + waiter.Wait(1000); |
| + } |
| + |
| + size_t GetKnownResults(const std::string& query) { |
| + known_results_ = history()->GetKnownResults(query).Pass(); |
| + return known_results_->size(); |
| + } |
| + |
| + KnownResultType GetResultType(const std::string& result_id) { |
| + return known_results_->find(result_id) != known_results_->end() |
| + ? (*known_results_.get())[result_id] |
| + : UNKNOWN_RESULT; |
| + } |
| + |
| + History* history() { return history_.get(); } |
| + const HistoryData::Associations& associations() const { |
| + return history_->data_->associations(); |
| + } |
| + |
| + private: |
| + MessageLoopForUI message_loop_; |
| + content::TestBrowserThread ui_thread_; |
| + scoped_ptr<TestingProfile> profile_; |
| + |
| + scoped_ptr<History> history_; |
| + scoped_ptr<KnownResults> known_results_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(SearchHistoryTest); |
| +}; |
| + |
| +TEST_F(SearchHistoryTest, Persistence) { |
| + // Ensure it's empty. |
| + EXPECT_EQ(0u, GetKnownResults("cal")); |
| + |
| + // Add one launch event. |
| + history()->AddLaunchEvent("cal", "calendar"); |
| + EXPECT_EQ(1u, GetKnownResults("cal")); |
| + |
| + // Flush and recreate the history object. |
| + Flush(); |
| + CreateHistory(); |
| + |
| + // History should be initialized with data just added. |
| + EXPECT_EQ(1u, GetKnownResults("cal")); |
| +} |
| + |
| +TEST_F(SearchHistoryTest, PerfectAndPrefixMatch) { |
| + const char kQuery[] = "cal"; |
| + const char kQueryPrefix[] = "c"; |
| + const char kPrimary[] = "calendar"; |
| + const char kSecondary[] = "calculator"; |
| + |
| + history()->AddLaunchEvent(kQuery, kPrimary); |
| + history()->AddLaunchEvent(kQuery, kSecondary); |
| + |
| + EXPECT_EQ(2u, GetKnownResults(kQuery)); |
| + EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kPrimary)); |
| + EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kSecondary)); |
| + |
| + EXPECT_EQ(2u, GetKnownResults(kQueryPrefix)); |
| + EXPECT_EQ(PREFIX_PRIMARY, GetResultType(kPrimary)); |
| + EXPECT_EQ(PREFIX_SECONDARY, GetResultType(kSecondary)); |
| +} |
| + |
| +TEST_F(SearchHistoryTest, StickyPrimary) { |
| + const char kQuery[] = "cal"; |
| + const char kPrimary[] = "calendar"; |
| + const char kSecondary[] = "calculator"; |
| + const char kOther[] = "other"; |
| + |
| + // Add two launch events. kPrimary becomes primary. |
| + history()->AddLaunchEvent(kQuery, kPrimary); |
| + history()->AddLaunchEvent(kQuery, kSecondary); |
| + |
| + EXPECT_EQ(2u, GetKnownResults(kQuery)); |
| + EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kPrimary)); |
| + EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kSecondary)); |
| + |
| + // These launch events should not change primary. |
| + history()->AddLaunchEvent(kQuery, kPrimary); |
| + history()->AddLaunchEvent(kQuery, kSecondary); |
| + history()->AddLaunchEvent(kQuery, kPrimary); |
| + history()->AddLaunchEvent(kQuery, kSecondary); |
| + history()->AddLaunchEvent(kQuery, kPrimary); |
| + history()->AddLaunchEvent(kQuery, kSecondary); |
| + history()->AddLaunchEvent(kQuery, kOther); |
| + history()->AddLaunchEvent(kQuery, kSecondary); |
| + history()->AddLaunchEvent(kQuery, kOther); |
| + history()->AddLaunchEvent(kQuery, kSecondary); |
| + history()->AddLaunchEvent(kQuery, kOther); |
| + |
| + EXPECT_EQ(3u, GetKnownResults(kQuery)); |
| + EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kPrimary)); |
| + EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kSecondary)); |
| + EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kOther)); |
| +} |
| + |
| +TEST_F(SearchHistoryTest, PromoteSecondary) { |
| + const char kQuery[] = "cal"; |
| + const char kPrimary[] = "calendar"; |
| + const char kSecondary[] = "calculator"; |
| + |
| + history()->AddLaunchEvent(kQuery, kPrimary); |
| + history()->AddLaunchEvent(kQuery, kSecondary); |
| + |
| + EXPECT_EQ(2u, GetKnownResults(kQuery)); |
| + EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kPrimary)); |
| + EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kSecondary)); |
| + |
| + // The 2nd launch in a row promotes it to be primary. |
| + history()->AddLaunchEvent(kQuery, kSecondary); |
| + |
| + EXPECT_EQ(2u, GetKnownResults(kQuery)); |
| + EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kSecondary)); |
| + EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kPrimary)); |
| +} |
| + |
| +TEST_F(SearchHistoryTest, MaxEntry) { |
| + for (size_t i = 0; i < kMaxEntry; ++i) { |
| + std::string query = base::StringPrintf("%d", static_cast<int>(i)); |
| + history()->AddLaunchEvent(query, "app"); |
| + } |
| + EXPECT_EQ(kMaxEntry, associations().size()); |
| + |
| + // Oldest entries still exists. |
| + EXPECT_TRUE(associations().find("0") != associations().end()); |
| + EXPECT_TRUE(associations().find("1") != associations().end()); |
| + |
| + // Touches the oldest and 2nd oldest becomes oldest now.. |
|
James Cook
2013/05/24 03:57:05
nit: one . at end
xiyuan
2013/05/24 16:33:02
Done.
|
| + history()->AddLaunchEvent("0", "app"); |
| + |
| + // Adds one more |
| + history()->AddLaunchEvent("extra", "app"); |
| + |
| + // Number of entries are capped to kMaxEntry. |
| + EXPECT_EQ(kMaxEntry, associations().size()); |
| + |
| + // Oldest entry is trimmed. |
| + EXPECT_FALSE(associations().find("1") != associations().end()); |
| + |
| + // The touched oldest survived. |
| + EXPECT_TRUE(associations().find("0") != associations().end()); |
| +} |
| + |
| +TEST_F(SearchHistoryTest, MaxSecondary) { |
| + const char kQuery[] = "query"; |
| + history()->AddLaunchEvent(kQuery, "primary"); |
| + for (size_t i = 0; i < kMaxSecondary; ++i) { |
| + std::string result_id = base::StringPrintf("%d", static_cast<int>(i)); |
| + history()->AddLaunchEvent(kQuery, result_id); |
| + } |
| + |
| + EXPECT_EQ(kMaxSecondary + 1, GetKnownResults(kQuery)); |
| + EXPECT_EQ(PERFECT_SECONDARY, GetResultType("0")); |
| + EXPECT_EQ(PERFECT_SECONDARY, GetResultType("1")); |
| + |
| + // Touches the oldest secondary. |
| + history()->AddLaunchEvent(kQuery, "0"); |
| + |
| + // Adds one more. |
| + history()->AddLaunchEvent(kQuery, "extra"); |
| + |
| + // Total number of results is capped. |
| + EXPECT_EQ(kMaxSecondary + 1, GetKnownResults(kQuery)); |
| + |
| + // The oldest secondary is gone. |
| + EXPECT_EQ(UNKNOWN_RESULT, GetResultType("1")); |
| + |
| + // Touched oldest survived. |
| + EXPECT_EQ(PERFECT_SECONDARY, GetResultType("0")); |
| +} |
| + |
| +} // namespace test |
| +} // namespace app_list |