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.
5 #include "base/basictypes.h"
7 #include "base/files/scoped_temp_dir.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/run_loop.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/test/sequenced_worker_pool_owner.h"
13 #include "base/threading/platform_thread.h"
14 #include "chrome/browser/ui/app_list/search/history_factory.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "ui/app_list/search/dictionary_data_store.h"
17 #include "ui/app_list/search/history.h"
18 #include "ui/app_list/search/history_data.h"
19 #include "ui/app_list/search/history_data_observer.h"
20 #include "ui/app_list/search/history_data_store.h"
27 const size_t kMaxPrimary
= 3;
28 const size_t kMaxSecondary
= 2;
30 // HistoryDataLoadWaiter waits for give |data| to be loaded from underlying
31 // store on the blocking pool. The waiter waits on the main message loop until
32 // OnHistoryDataLoadedFromStore() is invoked or the maximum allowed wait time
34 class HistoryDataLoadWaiter
: public HistoryDataObserver
{
36 explicit HistoryDataLoadWaiter(HistoryData
* data
) : data_(data
) {}
37 ~HistoryDataLoadWaiter() override
{}
40 data_
->AddObserver(this);
42 run_loop_
.reset(new base::RunLoop
);
45 data_
->RemoveObserver(this);
49 // HistoryDataObserver overrides:
50 void OnHistoryDataLoadedFromStore() override
{ run_loop_
->Quit(); }
52 HistoryData
* data_
; // Not owned.
53 scoped_ptr
<base::RunLoop
> run_loop_
;
55 DISALLOW_COPY_AND_ASSIGN(HistoryDataLoadWaiter
);
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.
62 class StoreFlushWaiter
{
64 explicit StoreFlushWaiter(HistoryDataStore
* store
) : store_(store
) {}
65 ~StoreFlushWaiter() {}
69 base::Bind(&StoreFlushWaiter::OnFlushed
, base::Unretained(this)));
71 run_loop_
.reset(new base::RunLoop
);
80 HistoryDataStore
* store_
; // Not owned.
81 scoped_ptr
<base::RunLoop
> run_loop_
;
83 DISALLOW_COPY_AND_ASSIGN(StoreFlushWaiter
);
88 class SearchHistoryTest
: public testing::Test
{
90 SearchHistoryTest() {}
91 ~SearchHistoryTest() override
{}
93 // testing::Test overrides:
94 void SetUp() override
{
95 worker_pool_owner_
.reset(
96 new base::SequencedWorkerPoolOwner(1, "AppLauncherTest"));
97 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
100 void TearDown() override
{
102 worker_pool_owner_
->pool()->Shutdown();
105 void CreateHistory() {
106 const char kStoreDataFileName
[] = "app-launcher-test";
107 const base::FilePath data_file
=
108 temp_dir_
.path().AppendASCII(kStoreDataFileName
);
109 scoped_refptr
<DictionaryDataStore
> dictionary_data_store(
110 new DictionaryDataStore(data_file
, worker_pool_owner_
->pool().get()));
111 history_
.reset(new History(scoped_refptr
<HistoryDataStore
>(
112 new HistoryDataStore(dictionary_data_store
))));
114 // Replace |data_| with test params.
115 history_
->data_
->RemoveObserver(history_
.get());
116 history_
->data_
.reset(
117 new HistoryData(history_
->store_
.get(), kMaxPrimary
, kMaxSecondary
));
118 history_
->data_
->AddObserver(history_
.get());
120 HistoryDataLoadWaiter(history_
->data_
.get()).Wait();
121 ASSERT_TRUE(history_
->IsReady());
125 StoreFlushWaiter(history_
->store_
.get()).Wait();
128 size_t GetKnownResults(const std::string
& query
) {
129 known_results_
= history()->GetKnownResults(query
).Pass();
130 return known_results_
->size();
133 KnownResultType
GetResultType(const std::string
& result_id
) {
134 return known_results_
->find(result_id
) != known_results_
->end()
135 ? (*known_results_
.get())[result_id
]
139 History
* history() { return history_
.get(); }
140 const HistoryData::Associations
& associations() const {
141 return history_
->data_
->associations();
145 base::MessageLoopForUI message_loop_
;
146 base::ScopedTempDir temp_dir_
;
147 scoped_ptr
<base::SequencedWorkerPoolOwner
> worker_pool_owner_
;
149 scoped_ptr
<History
> history_
;
150 scoped_ptr
<KnownResults
> known_results_
;
152 DISALLOW_COPY_AND_ASSIGN(SearchHistoryTest
);
155 TEST_F(SearchHistoryTest
, Persistence
) {
156 // Ensure it's empty.
157 EXPECT_EQ(0u, GetKnownResults("cal"));
159 // Add one launch event.
160 history()->AddLaunchEvent("cal", "calendar");
161 EXPECT_EQ(1u, GetKnownResults("cal"));
163 // Flush and recreate the history object.
167 // History should be initialized with data just added.
168 EXPECT_EQ(1u, GetKnownResults("cal"));
171 TEST_F(SearchHistoryTest
, PerfectAndPrefixMatch
) {
172 const char kQuery
[] = "cal";
173 const char kQueryPrefix
[] = "c";
174 const char kPrimary
[] = "calendar";
175 const char kSecondary
[] = "calculator";
177 history()->AddLaunchEvent(kQuery
, kPrimary
);
178 history()->AddLaunchEvent(kQuery
, kSecondary
);
180 EXPECT_EQ(2u, GetKnownResults(kQuery
));
181 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kPrimary
));
182 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kSecondary
));
184 EXPECT_EQ(2u, GetKnownResults(kQueryPrefix
));
185 EXPECT_EQ(PREFIX_PRIMARY
, GetResultType(kPrimary
));
186 EXPECT_EQ(PREFIX_SECONDARY
, GetResultType(kSecondary
));
189 TEST_F(SearchHistoryTest
, StickyPrimary
) {
190 const char kQuery
[] = "cal";
191 const char kPrimary
[] = "calendar";
192 const char kSecondary
[] = "calculator";
193 const char kOther
[] = "other";
195 // Add two launch events. kPrimary becomes primary.
196 history()->AddLaunchEvent(kQuery
, kPrimary
);
197 history()->AddLaunchEvent(kQuery
, kSecondary
);
199 EXPECT_EQ(2u, GetKnownResults(kQuery
));
200 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kPrimary
));
201 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kSecondary
));
203 // These launch events should not change primary.
204 history()->AddLaunchEvent(kQuery
, kPrimary
);
205 history()->AddLaunchEvent(kQuery
, kSecondary
);
206 history()->AddLaunchEvent(kQuery
, kPrimary
);
207 history()->AddLaunchEvent(kQuery
, kSecondary
);
208 history()->AddLaunchEvent(kQuery
, kPrimary
);
209 history()->AddLaunchEvent(kQuery
, kSecondary
);
210 history()->AddLaunchEvent(kQuery
, kOther
);
211 history()->AddLaunchEvent(kQuery
, kSecondary
);
212 history()->AddLaunchEvent(kQuery
, kOther
);
213 history()->AddLaunchEvent(kQuery
, kSecondary
);
214 history()->AddLaunchEvent(kQuery
, kOther
);
216 EXPECT_EQ(3u, GetKnownResults(kQuery
));
217 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kPrimary
));
218 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kSecondary
));
219 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kOther
));
222 TEST_F(SearchHistoryTest
, PromoteSecondary
) {
223 const char kQuery
[] = "cal";
224 const char kPrimary
[] = "calendar";
225 const char kSecondary
[] = "calculator";
227 history()->AddLaunchEvent(kQuery
, kPrimary
);
228 history()->AddLaunchEvent(kQuery
, kSecondary
);
230 EXPECT_EQ(2u, GetKnownResults(kQuery
));
231 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kPrimary
));
232 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kSecondary
));
234 // The 2nd launch in a row promotes it to be primary.
235 history()->AddLaunchEvent(kQuery
, kSecondary
);
237 EXPECT_EQ(2u, GetKnownResults(kQuery
));
238 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kSecondary
));
239 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kPrimary
));
242 TEST_F(SearchHistoryTest
, MaxPrimary
) {
243 for (size_t i
= 0; i
< kMaxPrimary
; ++i
) {
244 std::string query
= base::SizeTToString(i
);
245 history()->AddLaunchEvent(query
, "app");
247 EXPECT_EQ(kMaxPrimary
, associations().size());
249 // Oldest entries still exists.
250 EXPECT_TRUE(associations().find("0") != associations().end());
251 EXPECT_TRUE(associations().find("1") != associations().end());
253 // Primary entry trimming is based on time. The code could run here too fast
254 // and Time::Now has not changed on some platform. Sleep a bit here to ensure
255 // that some time has passed to get rid of the flake.
256 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(25));
258 // Touches the oldest and 2nd oldest becomes oldest now.
259 history()->AddLaunchEvent("0", "app");
262 history()->AddLaunchEvent("extra", "app");
264 // Number of entries are capped to kMaxPrimary.
265 EXPECT_EQ(kMaxPrimary
, associations().size());
267 // Oldest entry is trimmed.
268 EXPECT_FALSE(associations().find("1") != associations().end());
270 // The touched oldest survived.
271 EXPECT_TRUE(associations().find("0") != associations().end());
274 TEST_F(SearchHistoryTest
, MaxSecondary
) {
275 const char kQuery
[] = "query";
276 history()->AddLaunchEvent(kQuery
, "primary");
277 for (size_t i
= 0; i
< kMaxSecondary
; ++i
) {
278 std::string result_id
= base::SizeTToString(i
);
279 history()->AddLaunchEvent(kQuery
, result_id
);
282 EXPECT_EQ(kMaxSecondary
+ 1, GetKnownResults(kQuery
));
283 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType("0"));
284 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType("1"));
286 // Touches the oldest secondary.
287 history()->AddLaunchEvent(kQuery
, "0");
290 history()->AddLaunchEvent(kQuery
, "extra");
292 // Total number of results is capped.
293 EXPECT_EQ(kMaxSecondary
+ 1, GetKnownResults(kQuery
));
295 // The oldest secondary is gone.
296 EXPECT_EQ(UNKNOWN_RESULT
, GetResultType("1"));
298 // Touched oldest survived.
299 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType("0"));
303 } // namespace app_list