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