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