Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ui / app_list / search / mixer_unittest.cc
blob248f0774b0e22d10cf98fdd3535027f050d1b417
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 <set>
6 #include <string>
8 #include "base/memory/scoped_vector.h"
9 #include "base/metrics/field_trial.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/test/mock_entropy_provider.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "ui/app_list/app_list_model.h"
16 #include "ui/app_list/search/history_types.h"
17 #include "ui/app_list/search/mixer.h"
18 #include "ui/app_list/search_provider.h"
19 #include "ui/app_list/search_result.h"
21 namespace app_list {
22 namespace test {
24 // Maximum number of results to show in each mixer group.
25 const size_t kMaxAppsGroupResults = 4;
26 // Ignored unless AppListMixer field trial is "Blended".
27 const size_t kMaxOmniboxResults = 4;
28 const size_t kMaxWebstoreResults = 2;
29 const size_t kMaxPeopleResults = 2;
31 class TestSearchResult : public SearchResult {
32 public:
33 TestSearchResult(const std::string& id, double relevance)
34 : instance_id_(instantiation_count++) {
35 set_id(id);
36 set_title(base::UTF8ToUTF16(id));
37 set_relevance(relevance);
39 ~TestSearchResult() override {}
41 using SearchResult::set_voice_result;
43 // SearchResult overrides:
44 void Open(int event_flags) override {}
45 void InvokeAction(int action_index, int event_flags) override {}
46 scoped_ptr<SearchResult> Duplicate() const override {
47 return make_scoped_ptr(new TestSearchResult(id(), relevance()));
50 // For reference equality testing. (Addresses cannot be used to test reference
51 // equality because it is possible that an object will be allocated at the
52 // same address as a previously deleted one.)
53 static int GetInstanceId(SearchResult* result) {
54 return static_cast<const TestSearchResult*>(result)->instance_id_;
57 private:
58 static int instantiation_count;
60 int instance_id_;
62 DISALLOW_COPY_AND_ASSIGN(TestSearchResult);
64 int TestSearchResult::instantiation_count = 0;
66 class TestSearchProvider : public SearchProvider {
67 public:
68 explicit TestSearchProvider(const std::string& prefix)
69 : prefix_(prefix), count_(0), bad_relevance_range_(false) {}
70 ~TestSearchProvider() override {}
72 // SearchProvider overrides:
73 void Start(bool is_voice_query, const base::string16& query) override {
74 ClearResults();
75 for (size_t i = 0; i < count_; ++i) {
76 const std::string id =
77 base::StringPrintf("%s%d", prefix_.c_str(), static_cast<int>(i));
78 double relevance = 1.0 - i / 10.0;
79 // If bad_relevance_range_, change the relevances to give results outside
80 // of the canonical [0.0, 1.0] range.
81 if (bad_relevance_range_)
82 relevance = 10.0 - i * 10;
83 TestSearchResult* result = new TestSearchResult(id, relevance);
84 if (voice_result_indices.find(i) != voice_result_indices.end())
85 result->set_voice_result(true);
86 Add(scoped_ptr<SearchResult>(result).Pass());
89 void Stop() override {}
91 void set_prefix(const std::string& prefix) { prefix_ = prefix; }
92 void set_count(size_t count) { count_ = count; }
93 void set_as_voice_result(size_t index) { voice_result_indices.insert(index); }
94 void set_bad_relevance_range() { bad_relevance_range_ = true; }
96 private:
97 std::string prefix_;
98 size_t count_;
99 bool bad_relevance_range_;
100 // Indices of results that will have the |voice_result| flag set.
101 std::set<size_t> voice_result_indices;
103 DISALLOW_COPY_AND_ASSIGN(TestSearchProvider);
106 // Test is parameterized with bool. True enables the "Blended" field trial.
107 class MixerTest : public testing::Test,
108 public testing::WithParamInterface<bool> {
109 public:
110 MixerTest()
111 : is_voice_query_(false),
112 field_trial_list_(new base::MockEntropyProvider()) {}
113 ~MixerTest() override {}
115 // testing::Test overrides:
116 void SetUp() override {
117 // If the parameter is true, enable the field trial.
118 const char* field_trial_name = GetParam() ? "Blended" : "default";
119 base::FieldTrialList::CreateFieldTrial("AppListMixer", field_trial_name);
121 results_.reset(new AppListModel::SearchResults);
123 providers_.push_back(new TestSearchProvider("app"));
124 providers_.push_back(new TestSearchProvider("omnibox"));
125 providers_.push_back(new TestSearchProvider("webstore"));
126 providers_.push_back(new TestSearchProvider("people"));
128 is_voice_query_ = false;
130 mixer_.reset(new Mixer(results_.get()));
132 size_t apps_group_id = mixer_->AddGroup(kMaxAppsGroupResults, 3.0, 1.0);
133 size_t omnibox_group_id =
134 mixer_->AddOmniboxGroup(kMaxOmniboxResults, 2.0, 1.0);
135 size_t webstore_group_id = mixer_->AddGroup(kMaxWebstoreResults, 1.0, 0.5);
136 size_t people_group_id = mixer_->AddGroup(kMaxPeopleResults, 0.0, 1.0);
138 mixer_->AddProviderToGroup(apps_group_id, providers_[0]);
139 mixer_->AddProviderToGroup(omnibox_group_id, providers_[1]);
140 mixer_->AddProviderToGroup(webstore_group_id, providers_[2]);
141 mixer_->AddProviderToGroup(people_group_id, providers_[3]);
144 void RunQuery() {
145 const base::string16 query;
147 for (size_t i = 0; i < providers_.size(); ++i) {
148 providers_[i]->Start(is_voice_query_, query);
149 providers_[i]->Stop();
152 mixer_->MixAndPublish(is_voice_query_, known_results_);
155 std::string GetResults() const {
156 std::string result;
157 for (size_t i = 0; i < results_->item_count(); ++i) {
158 if (!result.empty())
159 result += ',';
161 result += base::UTF16ToUTF8(results_->GetItemAt(i)->title());
164 return result;
167 Mixer* mixer() { return mixer_.get(); }
168 TestSearchProvider* app_provider() { return providers_[0]; }
169 TestSearchProvider* omnibox_provider() { return providers_[1]; }
170 TestSearchProvider* webstore_provider() { return providers_[2]; }
171 TestSearchProvider* people_provider() { return providers_[3]; }
173 // Sets whether test runs should be treated as a voice query.
174 void set_is_voice_query(bool is_voice_query) {
175 is_voice_query_ = is_voice_query;
178 void AddKnownResult(const std::string& id, KnownResultType type) {
179 known_results_[id] = type;
182 private:
183 scoped_ptr<Mixer> mixer_;
184 scoped_ptr<AppListModel::SearchResults> results_;
185 KnownResults known_results_;
187 bool is_voice_query_;
189 ScopedVector<TestSearchProvider> providers_;
191 base::FieldTrialList field_trial_list_;
193 DISALLOW_COPY_AND_ASSIGN(MixerTest);
196 TEST_P(MixerTest, Basic) {
197 // Note: Some cases in |expected_blended| have vastly more results than
198 // others, due to the "at least 6" mechanism. If it gets at least 6 results
199 // from all providers, it stops at 6. If not, it fetches potentially many more
200 // results from all providers. Not ideal, but currently by design.
201 struct TestCase {
202 const size_t app_results;
203 const size_t omnibox_results;
204 const size_t webstore_results;
205 const size_t people_results;
206 const char* expected_default; // Expected results with trial off.
207 const char* expected_blended; // Expected results with trial on.
208 } kTestCases[] = {
209 {0, 0, 0, 0, "", ""},
210 {10,
214 "app0,app1,app2,app3",
215 "app0,app1,app2,app3,app4,app5,app6,app7,app8,app9"},
220 "webstore0,webstore1",
221 "webstore0,webstore1,webstore2,webstore3,webstore4,webstore5,webstore6,"
222 "webstore7,webstore8,webstore9"},
227 "people0,people1",
228 "people0,people1,people2,people3,people4,people5,people6,people7,"
229 "people8,people9"},
234 "app0,app1,app2,app3,omnibox0,omnibox1",
235 "app0,omnibox0,app1,omnibox1,app2,omnibox2,app3,omnibox3"},
240 "app0,app1,app2,app3,omnibox0,webstore0",
241 "app0,omnibox0,app1,omnibox1,app2,omnibox2,app3,omnibox3,webstore0,"
242 "webstore1"},
247 "app0,app1,app2,app3,omnibox0,people0",
248 "app0,omnibox0,people0,app1,omnibox1,people1,app2,omnibox2,app3,"
249 "omnibox3"},
250 {10,
254 "app0,app1,app2,app3,omnibox0,webstore0",
255 "app0,omnibox0,app1,omnibox1,app2,omnibox2,app3,omnibox3,webstore0,"
256 "webstore1"},
261 "omnibox0,omnibox1,omnibox2,omnibox3,omnibox4,omnibox5",
262 "omnibox0,omnibox1,omnibox2,omnibox3,omnibox4,omnibox5,omnibox6,"
263 "omnibox7,omnibox8,omnibox9"},
268 "omnibox0,omnibox1,omnibox2,omnibox3,omnibox4,webstore0",
269 "omnibox0,omnibox1,omnibox2,omnibox3,webstore0,omnibox4,omnibox5,"
270 "omnibox6,omnibox7,omnibox8,omnibox9"},
275 "omnibox0,omnibox1,omnibox2,omnibox3,webstore0,webstore1",
276 "omnibox0,omnibox1,omnibox2,omnibox3,webstore0,webstore1"},
281 "app0,omnibox0,omnibox1,omnibox2,omnibox3,omnibox4",
282 "app0,omnibox0,omnibox1,omnibox2,omnibox3,omnibox4,omnibox5,omnibox6,"
283 "omnibox7,omnibox8,omnibox9"},
288 "app0,app1,omnibox0,omnibox1,omnibox2,omnibox3",
289 "app0,omnibox0,app1,omnibox1,omnibox2,omnibox3"},
294 "app0,app1,omnibox0,omnibox1,omnibox2,webstore0",
295 "app0,omnibox0,app1,omnibox1,omnibox2,omnibox3,webstore0"},
300 "app0,app1,omnibox0,omnibox1,webstore0,webstore1",
301 "app0,omnibox0,app1,omnibox1,omnibox2,omnibox3,webstore0,webstore1"},
306 "app0,app1,webstore0,webstore1",
307 "app0,app1,webstore0,webstore1"},
308 {10,
312 "app0,app1,app2,app3,webstore0,webstore1",
313 "app0,people0,app1,people1,app2,app3,webstore0,webstore1"},
314 {10,
318 "app0,app1,app2,app3,omnibox0,webstore0",
319 "app0,omnibox0,people0,app1,omnibox1,people1,app2,omnibox2,app3,"
320 "omnibox3,webstore0,webstore1"},
321 {0, 0, 0, 0, "", ""},
324 for (size_t i = 0; i < arraysize(kTestCases); ++i) {
325 app_provider()->set_count(kTestCases[i].app_results);
326 omnibox_provider()->set_count(kTestCases[i].omnibox_results);
327 webstore_provider()->set_count(kTestCases[i].webstore_results);
328 people_provider()->set_count(kTestCases[i].people_results);
329 RunQuery();
331 const char* expected = GetParam() ? kTestCases[i].expected_blended
332 : kTestCases[i].expected_default;
333 EXPECT_EQ(expected, GetResults()) << "Case " << i;
337 TEST_P(MixerTest, RemoveDuplicates) {
338 const std::string dup = "dup";
340 // This gives "dup0,dup1,dup2".
341 app_provider()->set_prefix(dup);
342 app_provider()->set_count(3);
344 // This gives "dup0,dup1".
345 omnibox_provider()->set_prefix(dup);
346 omnibox_provider()->set_count(2);
348 // This gives "dup0".
349 webstore_provider()->set_prefix(dup);
350 webstore_provider()->set_count(1);
352 RunQuery();
354 // Only three results with unique id are kept.
355 EXPECT_EQ("dup0,dup1,dup2", GetResults());
358 // Tests that "known results" have priority over others.
359 TEST_P(MixerTest, KnownResultsPriority) {
360 // This gives omnibox 0 -- 5.
361 omnibox_provider()->set_count(6);
363 // omnibox 1 -- 4 are "known results".
364 AddKnownResult("omnibox1", PREFIX_SECONDARY);
365 AddKnownResult("omnibox2", PERFECT_SECONDARY);
366 AddKnownResult("omnibox3", PREFIX_PRIMARY);
367 AddKnownResult("omnibox4", PERFECT_PRIMARY);
369 RunQuery();
371 // omnibox 1 -- 4 should be prioritised over the others. They should be
372 // ordered 4, 3, 2, 1 (in order of match quality).
373 EXPECT_EQ("omnibox4,omnibox3,omnibox2,omnibox1,omnibox0,omnibox5",
374 GetResults());
377 TEST_P(MixerTest, VoiceQuery) {
378 omnibox_provider()->set_count(3);
379 RunQuery();
380 EXPECT_EQ("omnibox0,omnibox1,omnibox2", GetResults());
382 // Set "omnibox1" as a voice result. Do not expect any changes (as this is not
383 // a voice query).
384 omnibox_provider()->set_as_voice_result(1);
385 RunQuery();
386 EXPECT_EQ("omnibox0,omnibox1,omnibox2", GetResults());
388 // Perform a voice query. Expect voice result first.
389 set_is_voice_query(true);
390 RunQuery();
391 EXPECT_EQ("omnibox1,omnibox0,omnibox2", GetResults());
393 // All voice results should appear before non-voice results.
394 omnibox_provider()->set_as_voice_result(2);
395 RunQuery();
396 EXPECT_EQ("omnibox1,omnibox2,omnibox0", GetResults());
399 TEST_P(MixerTest, BadRelevanceRange) {
400 // This gives relevance scores: (10.0, 0.0). Even though providers are
401 // supposed to give scores within the range [0.0, 1.0], we cannot rely on
402 // providers to do this, since they retrieve results from disparate and
403 // unreliable sources (like the Google+ API).
404 people_provider()->set_bad_relevance_range();
405 people_provider()->set_count(2);
407 // Give a massive boost to the second result.
408 AddKnownResult("people1", PERFECT_PRIMARY);
410 RunQuery();
412 // If the results are correctly clamped to the range [0.0, 1.0], the boost to
413 // "people1" will push it over the first result. If not, the massive base
414 // score of "people0" will erroneously keep it on top.
415 EXPECT_EQ("people1,people0", GetResults());
418 TEST_P(MixerTest, Publish) {
419 scoped_ptr<SearchResult> result1(new TestSearchResult("app1", 0));
420 scoped_ptr<SearchResult> result2(new TestSearchResult("app2", 0));
421 scoped_ptr<SearchResult> result3(new TestSearchResult("app3", 0));
422 scoped_ptr<SearchResult> result3_copy = result3->Duplicate();
423 scoped_ptr<SearchResult> result4(new TestSearchResult("app4", 0));
424 scoped_ptr<SearchResult> result5(new TestSearchResult("app5", 0));
426 AppListModel::SearchResults ui_results;
428 // Publish the first three results to |ui_results|.
429 Mixer::SortedResults new_results;
430 new_results.push_back(Mixer::SortData(result1.get(), 1.0f));
431 new_results.push_back(Mixer::SortData(result2.get(), 1.0f));
432 new_results.push_back(Mixer::SortData(result3.get(), 1.0f));
434 Mixer::Publish(new_results, &ui_results);
435 EXPECT_EQ(3u, ui_results.item_count());
436 // The objects in |ui_results| should be new copies because the input results
437 // are owned and |ui_results| needs to own its results as well.
438 EXPECT_NE(TestSearchResult::GetInstanceId(new_results[0].result),
439 TestSearchResult::GetInstanceId(ui_results.GetItemAt(0)));
440 EXPECT_NE(TestSearchResult::GetInstanceId(new_results[1].result),
441 TestSearchResult::GetInstanceId(ui_results.GetItemAt(1)));
442 EXPECT_NE(TestSearchResult::GetInstanceId(new_results[2].result),
443 TestSearchResult::GetInstanceId(ui_results.GetItemAt(2)));
445 // Save the current |ui_results| instance ids for comparison later.
446 std::vector<int> old_ui_result_ids;
447 for (size_t i = 0; i < ui_results.item_count(); ++i) {
448 old_ui_result_ids.push_back(
449 TestSearchResult::GetInstanceId(ui_results.GetItemAt(i)));
452 // Change the first result to a totally new object (with a new ID).
453 new_results[0] = Mixer::SortData(result4.get(), 1.0f);
455 // Change the second result's title, but keep the same id. (The result will
456 // keep the id "app2" but change its title to "New App 2 Title".)
457 const base::string16 kNewAppTitle = base::UTF8ToUTF16("New App 2 Title");
458 new_results[1].result->set_title(kNewAppTitle);
460 // Change the third result's object address (it points to an object with the
461 // same data).
462 new_results[2] = Mixer::SortData(result3_copy.get(), 1.0f);
464 Mixer::Publish(new_results, &ui_results);
465 EXPECT_EQ(3u, ui_results.item_count());
467 // The first result will be a new object, as the ID has changed.
468 EXPECT_NE(old_ui_result_ids[0],
469 TestSearchResult::GetInstanceId(ui_results.GetItemAt(0)));
471 // The second result will still use the original object, but have a different
472 // title, since the ID did not change.
473 EXPECT_EQ(old_ui_result_ids[1],
474 TestSearchResult::GetInstanceId(ui_results.GetItemAt(1)));
475 EXPECT_EQ(kNewAppTitle, ui_results.GetItemAt(1)->title());
477 // The third result will use the original object as the ID did not change.
478 EXPECT_EQ(old_ui_result_ids[2],
479 TestSearchResult::GetInstanceId(ui_results.GetItemAt(2)));
481 // Save the current |ui_results| order which should is app4, app2, app3.
482 old_ui_result_ids.clear();
483 for (size_t i = 0; i < ui_results.item_count(); ++i) {
484 old_ui_result_ids.push_back(
485 TestSearchResult::GetInstanceId(ui_results.GetItemAt(i)));
488 // Reorder the existing results and add a new one in the second place.
489 new_results[0] = Mixer::SortData(result2.get(), 1.0f);
490 new_results[1] = Mixer::SortData(result5.get(), 1.0f);
491 new_results[2] = Mixer::SortData(result3.get(), 1.0f);
492 new_results.push_back(Mixer::SortData(result4.get(), 1.0f));
494 Mixer::Publish(new_results, &ui_results);
495 EXPECT_EQ(4u, ui_results.item_count());
497 // The reordered results should use the original objects.
498 EXPECT_EQ(old_ui_result_ids[0],
499 TestSearchResult::GetInstanceId(ui_results.GetItemAt(3)));
500 EXPECT_EQ(old_ui_result_ids[1],
501 TestSearchResult::GetInstanceId(ui_results.GetItemAt(0)));
502 EXPECT_EQ(old_ui_result_ids[2],
503 TestSearchResult::GetInstanceId(ui_results.GetItemAt(2)));
506 INSTANTIATE_TEST_CASE_P(MixerTestInstance, MixerTest, testing::Bool());
508 } // namespace test
509 } // namespace app_list