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/stringprintf.h"
12 #include "base/threading/platform_thread.h"
13 #include "chrome/browser/ui/app_list/search/history_factory.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "ui/app_list/search/dictionary_data_store.h"
16 #include "ui/app_list/search/history.h"
17 #include "ui/app_list/search/history_data.h"
18 #include "ui/app_list/search/history_data_observer.h"
19 #include "ui/app_list/search/history_data_store.h"
26 const size_t kMaxPrimary
= 3;
27 const size_t kMaxSecondary
= 2;
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
33 class HistoryDataLoadWaiter
: public HistoryDataObserver
{
35 explicit HistoryDataLoadWaiter(HistoryData
* data
) : data_(data
) {}
36 ~HistoryDataLoadWaiter() override
{}
39 data_
->AddObserver(this);
41 run_loop_
.reset(new base::RunLoop
);
44 data_
->RemoveObserver(this);
48 // HistoryDataObserver overrides:
49 void OnHistoryDataLoadedFromStore() override
{ run_loop_
->Quit(); }
51 HistoryData
* data_
; // Not owned.
52 scoped_ptr
<base::RunLoop
> run_loop_
;
54 DISALLOW_COPY_AND_ASSIGN(HistoryDataLoadWaiter
);
57 // StoreFlushWaiter waits for the given |store| to flush its data to disk.
58 // The flush and disk write happens on the blocking pool. The waiter waits
59 // on the main message loop until the OnFlushed() is invoked or the maximum
60 // allowed wait time has passed.
61 class StoreFlushWaiter
{
63 explicit StoreFlushWaiter(HistoryDataStore
* store
) : store_(store
) {}
64 ~StoreFlushWaiter() {}
68 base::Bind(&StoreFlushWaiter::OnFlushed
, base::Unretained(this)));
70 run_loop_
.reset(new base::RunLoop
);
79 HistoryDataStore
* store_
; // Not owned.
80 scoped_ptr
<base::RunLoop
> run_loop_
;
82 DISALLOW_COPY_AND_ASSIGN(StoreFlushWaiter
);
87 class SearchHistoryTest
: public testing::Test
{
89 SearchHistoryTest() {}
90 ~SearchHistoryTest() override
{}
92 // testing::Test overrides:
93 void SetUp() override
{
94 worker_pool_
= new base::SequencedWorkerPool(1, "AppLauncherTest");
95 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
98 void TearDown() override
{ Flush(); }
100 void CreateHistory() {
101 const char kStoreDataFileName
[] = "app-launcher-test";
102 const base::FilePath data_file
=
103 temp_dir_
.path().AppendASCII(kStoreDataFileName
);
104 scoped_refptr
<DictionaryDataStore
> dictionary_data_store(
105 new DictionaryDataStore(data_file
, worker_pool_
.get()));
106 history_
.reset(new History(scoped_refptr
<HistoryDataStore
>(
107 new HistoryDataStore(dictionary_data_store
))));
109 // Replace |data_| with test params.
110 history_
->data_
->RemoveObserver(history_
.get());
111 history_
->data_
.reset(
112 new HistoryData(history_
->store_
.get(), kMaxPrimary
, kMaxSecondary
));
113 history_
->data_
->AddObserver(history_
.get());
115 HistoryDataLoadWaiter(history_
->data_
.get()).Wait();
116 ASSERT_TRUE(history_
->IsReady());
120 StoreFlushWaiter(history_
->store_
.get()).Wait();
123 size_t GetKnownResults(const std::string
& query
) {
124 known_results_
= history()->GetKnownResults(query
).Pass();
125 return known_results_
->size();
128 KnownResultType
GetResultType(const std::string
& result_id
) {
129 return known_results_
->find(result_id
) != known_results_
->end()
130 ? (*known_results_
.get())[result_id
]
134 History
* history() { return history_
.get(); }
135 const HistoryData::Associations
& associations() const {
136 return history_
->data_
->associations();
140 base::MessageLoopForUI message_loop_
;
141 base::ScopedTempDir temp_dir_
;
142 scoped_refptr
<base::SequencedWorkerPool
> worker_pool_
;
144 scoped_ptr
<History
> history_
;
145 scoped_ptr
<KnownResults
> known_results_
;
147 DISALLOW_COPY_AND_ASSIGN(SearchHistoryTest
);
150 TEST_F(SearchHistoryTest
, Persistence
) {
151 // Ensure it's empty.
152 EXPECT_EQ(0u, GetKnownResults("cal"));
154 // Add one launch event.
155 history()->AddLaunchEvent("cal", "calendar");
156 EXPECT_EQ(1u, GetKnownResults("cal"));
158 // Flush and recreate the history object.
162 // History should be initialized with data just added.
163 EXPECT_EQ(1u, GetKnownResults("cal"));
166 TEST_F(SearchHistoryTest
, PerfectAndPrefixMatch
) {
167 const char kQuery
[] = "cal";
168 const char kQueryPrefix
[] = "c";
169 const char kPrimary
[] = "calendar";
170 const char kSecondary
[] = "calculator";
172 history()->AddLaunchEvent(kQuery
, kPrimary
);
173 history()->AddLaunchEvent(kQuery
, kSecondary
);
175 EXPECT_EQ(2u, GetKnownResults(kQuery
));
176 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kPrimary
));
177 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kSecondary
));
179 EXPECT_EQ(2u, GetKnownResults(kQueryPrefix
));
180 EXPECT_EQ(PREFIX_PRIMARY
, GetResultType(kPrimary
));
181 EXPECT_EQ(PREFIX_SECONDARY
, GetResultType(kSecondary
));
184 TEST_F(SearchHistoryTest
, StickyPrimary
) {
185 const char kQuery
[] = "cal";
186 const char kPrimary
[] = "calendar";
187 const char kSecondary
[] = "calculator";
188 const char kOther
[] = "other";
190 // Add two launch events. kPrimary becomes primary.
191 history()->AddLaunchEvent(kQuery
, kPrimary
);
192 history()->AddLaunchEvent(kQuery
, kSecondary
);
194 EXPECT_EQ(2u, GetKnownResults(kQuery
));
195 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kPrimary
));
196 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kSecondary
));
198 // These launch events should not change primary.
199 history()->AddLaunchEvent(kQuery
, kPrimary
);
200 history()->AddLaunchEvent(kQuery
, kSecondary
);
201 history()->AddLaunchEvent(kQuery
, kPrimary
);
202 history()->AddLaunchEvent(kQuery
, kSecondary
);
203 history()->AddLaunchEvent(kQuery
, kPrimary
);
204 history()->AddLaunchEvent(kQuery
, kSecondary
);
205 history()->AddLaunchEvent(kQuery
, kOther
);
206 history()->AddLaunchEvent(kQuery
, kSecondary
);
207 history()->AddLaunchEvent(kQuery
, kOther
);
208 history()->AddLaunchEvent(kQuery
, kSecondary
);
209 history()->AddLaunchEvent(kQuery
, kOther
);
211 EXPECT_EQ(3u, GetKnownResults(kQuery
));
212 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kPrimary
));
213 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kSecondary
));
214 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kOther
));
217 TEST_F(SearchHistoryTest
, PromoteSecondary
) {
218 const char kQuery
[] = "cal";
219 const char kPrimary
[] = "calendar";
220 const char kSecondary
[] = "calculator";
222 history()->AddLaunchEvent(kQuery
, kPrimary
);
223 history()->AddLaunchEvent(kQuery
, kSecondary
);
225 EXPECT_EQ(2u, GetKnownResults(kQuery
));
226 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kPrimary
));
227 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kSecondary
));
229 // The 2nd launch in a row promotes it to be primary.
230 history()->AddLaunchEvent(kQuery
, kSecondary
);
232 EXPECT_EQ(2u, GetKnownResults(kQuery
));
233 EXPECT_EQ(PERFECT_PRIMARY
, GetResultType(kSecondary
));
234 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType(kPrimary
));
237 TEST_F(SearchHistoryTest
, MaxPrimary
) {
238 for (size_t i
= 0; i
< kMaxPrimary
; ++i
) {
239 std::string query
= base::StringPrintf("%d", static_cast<int>(i
));
240 history()->AddLaunchEvent(query
, "app");
242 EXPECT_EQ(kMaxPrimary
, associations().size());
244 // Oldest entries still exists.
245 EXPECT_TRUE(associations().find("0") != associations().end());
246 EXPECT_TRUE(associations().find("1") != associations().end());
248 // Primary entry trimming is based on time. The code could run here too fast
249 // and Time::Now has not changed on some platform. Sleep a bit here to ensure
250 // that some time has passed to get rid of the flake.
251 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(25));
253 // Touches the oldest and 2nd oldest becomes oldest now.
254 history()->AddLaunchEvent("0", "app");
257 history()->AddLaunchEvent("extra", "app");
259 // Number of entries are capped to kMaxPrimary.
260 EXPECT_EQ(kMaxPrimary
, associations().size());
262 // Oldest entry is trimmed.
263 EXPECT_FALSE(associations().find("1") != associations().end());
265 // The touched oldest survived.
266 EXPECT_TRUE(associations().find("0") != associations().end());
269 TEST_F(SearchHistoryTest
, MaxSecondary
) {
270 const char kQuery
[] = "query";
271 history()->AddLaunchEvent(kQuery
, "primary");
272 for (size_t i
= 0; i
< kMaxSecondary
; ++i
) {
273 std::string result_id
= base::StringPrintf("%d", static_cast<int>(i
));
274 history()->AddLaunchEvent(kQuery
, result_id
);
277 EXPECT_EQ(kMaxSecondary
+ 1, GetKnownResults(kQuery
));
278 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType("0"));
279 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType("1"));
281 // Touches the oldest secondary.
282 history()->AddLaunchEvent(kQuery
, "0");
285 history()->AddLaunchEvent(kQuery
, "extra");
287 // Total number of results is capped.
288 EXPECT_EQ(kMaxSecondary
+ 1, GetKnownResults(kQuery
));
290 // The oldest secondary is gone.
291 EXPECT_EQ(UNKNOWN_RESULT
, GetResultType("1"));
293 // Touched oldest survived.
294 EXPECT_EQ(PERFECT_SECONDARY
, GetResultType("0"));
298 } // namespace app_list