history_unittest.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
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/strings/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(
130        new HistoryData(history_->store_.get(), kMaxPrimary, 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  base::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
170TEST_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
186TEST_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
204TEST_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
237TEST_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
257TEST_F(SearchHistoryTest, MaxPrimary) {
258  for (size_t i = 0; i < kMaxPrimary; ++i) {
259    std::string query = base::StringPrintf("%d", static_cast<int>(i));
260    history()->AddLaunchEvent(query, "app");
261  }
262  EXPECT_EQ(kMaxPrimary, 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  // Primary entry trimming is based on time. The code could run here too fast
269  // and Time::Now has not changed on some platform. Sleep a bit here to ensure
270  // that some time has passed to get rid of the flake.
271  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(25));
272
273  // Touches the oldest and 2nd oldest becomes oldest now.
274  history()->AddLaunchEvent("0", "app");
275
276  // Adds one more
277  history()->AddLaunchEvent("extra", "app");
278
279  // Number of entries are capped to kMaxPrimary.
280  EXPECT_EQ(kMaxPrimary, associations().size());
281
282  // Oldest entry is trimmed.
283  EXPECT_FALSE(associations().find("1") != associations().end());
284
285  // The touched oldest survived.
286  EXPECT_TRUE(associations().find("0") != associations().end());
287}
288
289TEST_F(SearchHistoryTest, MaxSecondary) {
290  const char kQuery[] = "query";
291  history()->AddLaunchEvent(kQuery, "primary");
292  for (size_t i = 0; i < kMaxSecondary; ++i) {
293    std::string result_id = base::StringPrintf("%d", static_cast<int>(i));
294    history()->AddLaunchEvent(kQuery, result_id);
295  }
296
297  EXPECT_EQ(kMaxSecondary + 1, GetKnownResults(kQuery));
298  EXPECT_EQ(PERFECT_SECONDARY, GetResultType("0"));
299  EXPECT_EQ(PERFECT_SECONDARY, GetResultType("1"));
300
301  // Touches the oldest secondary.
302  history()->AddLaunchEvent(kQuery, "0");
303
304  // Adds one more.
305  history()->AddLaunchEvent(kQuery, "extra");
306
307  // Total number of results is capped.
308  EXPECT_EQ(kMaxSecondary + 1, GetKnownResults(kQuery));
309
310  // The oldest secondary is gone.
311  EXPECT_EQ(UNKNOWN_RESULT, GetResultType("1"));
312
313  // Touched oldest survived.
314  EXPECT_EQ(PERFECT_SECONDARY, GetResultType("0"));
315}
316
317}  // namespace test
318}  // namespace app_list
319