history_unittest.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/basictypes.h" 6#include "base/bind.h" 7#include "base/memory/scoped_ptr.h" 8#include "base/message_loop/message_loop.h" 9#include "base/run_loop.h" 10#include "base/strings/stringprintf.h" 11#include "base/threading/platform_thread.h" 12#include "chrome/browser/ui/app_list/search/history.h" 13#include "chrome/browser/ui/app_list/search/history_data.h" 14#include "chrome/browser/ui/app_list/search/history_data_observer.h" 15#include "chrome/browser/ui/app_list/search/history_data_store.h" 16#include "chrome/test/base/testing_profile.h" 17#include "content/public/test/test_browser_thread.h" 18#include "testing/gtest/include/gtest/gtest.h" 19 20namespace app_list { 21namespace test { 22 23namespace { 24 25const size_t kMaxPrimary = 3; 26const size_t kMaxSecondary = 2; 27 28// HistoryDataLoadWaiter waits for give |data| to be loaded from underlying 29// store on the blocking pool. The waiter waits on the main message loop until 30// OnHistoryDataLoadedFromStore() is invoked or the maximum allowed wait time 31// has passed. 32class HistoryDataLoadWaiter : public HistoryDataObserver { 33 public: 34 explicit HistoryDataLoadWaiter(HistoryData* data) : data_(data) {} 35 virtual ~HistoryDataLoadWaiter() {} 36 37 void Wait() { 38 data_->AddObserver(this); 39 40 run_loop_.reset(new base::RunLoop); 41 run_loop_->Run(); 42 43 data_->RemoveObserver(this); 44 } 45 46 private: 47 // HistoryDataObserver overrides: 48 virtual void OnHistoryDataLoadedFromStore() OVERRIDE { 49 run_loop_->Quit(); 50 } 51 52 HistoryData* data_; // Not owned. 53 scoped_ptr<base::RunLoop> run_loop_; 54 55 DISALLOW_COPY_AND_ASSIGN(HistoryDataLoadWaiter); 56}; 57 58// StoreFlushWaiter waits for the given |store| to flush its data to disk. 59// The flush and disk write happens on the blocking pool. The waiter waits 60// on the main message loop until the OnFlushed() is invoked or the maximum 61// allowed wait time has passed. 62class StoreFlushWaiter { 63 public: 64 explicit StoreFlushWaiter(HistoryDataStore* store) : store_(store) {} 65 ~StoreFlushWaiter() {} 66 67 void Wait() { 68 store_->Flush( 69 base::Bind(&StoreFlushWaiter::OnFlushed, base::Unretained(this))); 70 71 run_loop_.reset(new base::RunLoop); 72 run_loop_->Run(); 73 } 74 75 private: 76 void OnFlushed() { 77 run_loop_->Quit(); 78 } 79 80 HistoryDataStore* store_; // Not owned. 81 scoped_ptr<base::RunLoop> run_loop_; 82 83 DISALLOW_COPY_AND_ASSIGN(StoreFlushWaiter); 84}; 85 86} // namespace 87 88class SearchHistoryTest : public testing::Test { 89 public: 90 SearchHistoryTest() 91 : ui_thread_(content::BrowserThread::UI, &message_loop_) {} 92 virtual ~SearchHistoryTest() {} 93 94 // testing::Test overrides: 95 virtual void SetUp() OVERRIDE { 96 profile_.reset(new TestingProfile); 97 CreateHistory(); 98 } 99 virtual void TearDown() OVERRIDE { 100 Flush(); 101 } 102 103 void CreateHistory() { 104 history_.reset(new History(profile_.get())); 105 106 // Replace |data_| with test params. 107 history_->data_->RemoveObserver(history_.get()); 108 history_->data_.reset( 109 new HistoryData(history_->store_.get(), kMaxPrimary, kMaxSecondary)); 110 history_->data_->AddObserver(history_.get()); 111 112 HistoryDataLoadWaiter(history_->data_.get()).Wait(); 113 ASSERT_TRUE(history_->IsReady()); 114 } 115 116 void Flush() { 117 StoreFlushWaiter(history_->store_.get()).Wait(); 118 } 119 120 size_t GetKnownResults(const std::string& query) { 121 known_results_ = history()->GetKnownResults(query).Pass(); 122 return known_results_->size(); 123 } 124 125 KnownResultType GetResultType(const std::string& result_id) { 126 return known_results_->find(result_id) != known_results_->end() 127 ? (*known_results_.get())[result_id] 128 : UNKNOWN_RESULT; 129 } 130 131 History* history() { return history_.get(); } 132 const HistoryData::Associations& associations() const { 133 return history_->data_->associations(); 134 } 135 136 private: 137 base::MessageLoopForUI message_loop_; 138 content::TestBrowserThread ui_thread_; 139 scoped_ptr<TestingProfile> profile_; 140 141 scoped_ptr<History> history_; 142 scoped_ptr<KnownResults> known_results_; 143 144 DISALLOW_COPY_AND_ASSIGN(SearchHistoryTest); 145}; 146 147TEST_F(SearchHistoryTest, Persistence) { 148 // Ensure it's empty. 149 EXPECT_EQ(0u, GetKnownResults("cal")); 150 151 // Add one launch event. 152 history()->AddLaunchEvent("cal", "calendar"); 153 EXPECT_EQ(1u, GetKnownResults("cal")); 154 155 // Flush and recreate the history object. 156 Flush(); 157 CreateHistory(); 158 159 // History should be initialized with data just added. 160 EXPECT_EQ(1u, GetKnownResults("cal")); 161} 162 163TEST_F(SearchHistoryTest, PerfectAndPrefixMatch) { 164 const char kQuery[] = "cal"; 165 const char kQueryPrefix[] = "c"; 166 const char kPrimary[] = "calendar"; 167 const char kSecondary[] = "calculator"; 168 169 history()->AddLaunchEvent(kQuery, kPrimary); 170 history()->AddLaunchEvent(kQuery, kSecondary); 171 172 EXPECT_EQ(2u, GetKnownResults(kQuery)); 173 EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kPrimary)); 174 EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kSecondary)); 175 176 EXPECT_EQ(2u, GetKnownResults(kQueryPrefix)); 177 EXPECT_EQ(PREFIX_PRIMARY, GetResultType(kPrimary)); 178 EXPECT_EQ(PREFIX_SECONDARY, GetResultType(kSecondary)); 179} 180 181TEST_F(SearchHistoryTest, StickyPrimary) { 182 const char kQuery[] = "cal"; 183 const char kPrimary[] = "calendar"; 184 const char kSecondary[] = "calculator"; 185 const char kOther[] = "other"; 186 187 // Add two launch events. kPrimary becomes primary. 188 history()->AddLaunchEvent(kQuery, kPrimary); 189 history()->AddLaunchEvent(kQuery, kSecondary); 190 191 EXPECT_EQ(2u, GetKnownResults(kQuery)); 192 EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kPrimary)); 193 EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kSecondary)); 194 195 // These launch events should not change primary. 196 history()->AddLaunchEvent(kQuery, kPrimary); 197 history()->AddLaunchEvent(kQuery, kSecondary); 198 history()->AddLaunchEvent(kQuery, kPrimary); 199 history()->AddLaunchEvent(kQuery, kSecondary); 200 history()->AddLaunchEvent(kQuery, kPrimary); 201 history()->AddLaunchEvent(kQuery, kSecondary); 202 history()->AddLaunchEvent(kQuery, kOther); 203 history()->AddLaunchEvent(kQuery, kSecondary); 204 history()->AddLaunchEvent(kQuery, kOther); 205 history()->AddLaunchEvent(kQuery, kSecondary); 206 history()->AddLaunchEvent(kQuery, kOther); 207 208 EXPECT_EQ(3u, GetKnownResults(kQuery)); 209 EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kPrimary)); 210 EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kSecondary)); 211 EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kOther)); 212} 213 214TEST_F(SearchHistoryTest, PromoteSecondary) { 215 const char kQuery[] = "cal"; 216 const char kPrimary[] = "calendar"; 217 const char kSecondary[] = "calculator"; 218 219 history()->AddLaunchEvent(kQuery, kPrimary); 220 history()->AddLaunchEvent(kQuery, kSecondary); 221 222 EXPECT_EQ(2u, GetKnownResults(kQuery)); 223 EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kPrimary)); 224 EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kSecondary)); 225 226 // The 2nd launch in a row promotes it to be primary. 227 history()->AddLaunchEvent(kQuery, kSecondary); 228 229 EXPECT_EQ(2u, GetKnownResults(kQuery)); 230 EXPECT_EQ(PERFECT_PRIMARY, GetResultType(kSecondary)); 231 EXPECT_EQ(PERFECT_SECONDARY, GetResultType(kPrimary)); 232} 233 234TEST_F(SearchHistoryTest, MaxPrimary) { 235 for (size_t i = 0; i < kMaxPrimary; ++i) { 236 std::string query = base::StringPrintf("%d", static_cast<int>(i)); 237 history()->AddLaunchEvent(query, "app"); 238 } 239 EXPECT_EQ(kMaxPrimary, associations().size()); 240 241 // Oldest entries still exists. 242 EXPECT_TRUE(associations().find("0") != associations().end()); 243 EXPECT_TRUE(associations().find("1") != associations().end()); 244 245 // Primary entry trimming is based on time. The code could run here too fast 246 // and Time::Now has not changed on some platform. Sleep a bit here to ensure 247 // that some time has passed to get rid of the flake. 248 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(25)); 249 250 // Touches the oldest and 2nd oldest becomes oldest now. 251 history()->AddLaunchEvent("0", "app"); 252 253 // Adds one more 254 history()->AddLaunchEvent("extra", "app"); 255 256 // Number of entries are capped to kMaxPrimary. 257 EXPECT_EQ(kMaxPrimary, associations().size()); 258 259 // Oldest entry is trimmed. 260 EXPECT_FALSE(associations().find("1") != associations().end()); 261 262 // The touched oldest survived. 263 EXPECT_TRUE(associations().find("0") != associations().end()); 264} 265 266TEST_F(SearchHistoryTest, MaxSecondary) { 267 const char kQuery[] = "query"; 268 history()->AddLaunchEvent(kQuery, "primary"); 269 for (size_t i = 0; i < kMaxSecondary; ++i) { 270 std::string result_id = base::StringPrintf("%d", static_cast<int>(i)); 271 history()->AddLaunchEvent(kQuery, result_id); 272 } 273 274 EXPECT_EQ(kMaxSecondary + 1, GetKnownResults(kQuery)); 275 EXPECT_EQ(PERFECT_SECONDARY, GetResultType("0")); 276 EXPECT_EQ(PERFECT_SECONDARY, GetResultType("1")); 277 278 // Touches the oldest secondary. 279 history()->AddLaunchEvent(kQuery, "0"); 280 281 // Adds one more. 282 history()->AddLaunchEvent(kQuery, "extra"); 283 284 // Total number of results is capped. 285 EXPECT_EQ(kMaxSecondary + 1, GetKnownResults(kQuery)); 286 287 // The oldest secondary is gone. 288 EXPECT_EQ(UNKNOWN_RESULT, GetResultType("1")); 289 290 // Touched oldest survived. 291 EXPECT_EQ(PERFECT_SECONDARY, GetResultType("0")); 292} 293 294} // namespace test 295} // namespace app_list 296