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