| OLD | NEW |
| (Empty) | |
| 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 |
| 21 namespace app_list { |
| 22 namespace test { |
| 23 |
| 24 namespace { |
| 25 |
| 26 const size_t kMaxPrimary = 3; |
| 27 const 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. |
| 33 class 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. |
| 73 class 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 |
| 109 class 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 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 |
| 171 TEST_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 |
| 187 TEST_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 |
| 205 TEST_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 |
| 238 TEST_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 |
| 258 TEST_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 |
| 290 TEST_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 |
| OLD | NEW |