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