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