Commit 04d80b5d authored by Charles Zhao's avatar Charles Zhao Committed by Commit Bot

First CL for SearchResultRanker.

(1) A SearchResultRanker class with a simple AppLaunchPredictor is
    added.

(2) SearchResultRanker lives inside AppSearchProvider and re-rank
    the suggested apps.

(3) training signal is passed from AppListClientImpl to
    SearchController, then to all SearchProviders; but only
    AppSearchProvider responds to that training signal by passing the
    signal to SearchResultRanker.


Change-Id: I9879bcf1932e2928720c43680ca37edf35c3a785

Bug: 871674
Change-Id: I9879bcf1932e2928720c43680ca37edf35c3a785
Reviewed-on: https://chromium-review.googlesource.com/1164863
Commit-Queue: Charles . <charleszhao@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarJia Meng <jiameng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#583953}
parent 7dd74b25
......@@ -33,6 +33,10 @@ const base::Feature kEnableZeroStateSuggestions{
"EnableZeroStateSuggestions", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kEnableAppListSearchAutocomplete{
"EnableAppListSearchAutocomplete", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kEnableSearchResultRankerTrain{
"EnableSearchResultRankerTrain", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kEnableSearchResultRankerInfer{
"EnableSearchResultRankerInfer", base::FEATURE_DISABLED_BY_DEFAULT};
bool IsAnswerCardEnabled() {
// Not using local static variable to allow tests to change this value.
......@@ -80,6 +84,14 @@ bool IsAppListSearchAutocompleteEnabled() {
return base::FeatureList::IsEnabled(kEnableAppListSearchAutocomplete);
}
bool IsSearchResultRankerTrainEnabled() {
return base::FeatureList::IsEnabled(kEnableSearchResultRankerTrain);
}
bool IsSearchResultRankerInferEnabled() {
return base::FeatureList::IsEnabled(kEnableSearchResultRankerInfer);
}
std::string AnswerServerUrl() {
const std::string experiment_url =
base::GetFieldTrialParamValueByFeature(kEnableAnswerCard, "ServerUrl");
......@@ -93,5 +105,14 @@ std::string AnswerServerQuerySuffix() {
"QuerySuffix");
}
std::string SearchResultRankerPredictorName() {
const std::string predictor_name = base::GetFieldTrialParamValueByFeature(
kEnableSearchResultRankerTrain,
"app_search_result_ranker_predictor_name");
if (!predictor_name.empty())
return predictor_name;
return std::string("MrfuAppLaunchPredictor");
}
} // namespace features
} // namespace app_list
......@@ -56,6 +56,14 @@ ASH_PUBLIC_EXPORT extern const base::Feature kEnableZeroStateSuggestions;
// Enables the feature to autocomplete text typed in the AppList search box.
ASH_PUBLIC_EXPORT extern const base::Feature kEnableAppListSearchAutocomplete;
// Enables the feature to rank app search result using AppSearchResultRanker
// (only training).
ASH_PUBLIC_EXPORT extern const base::Feature kEnableSearchResultRankerTrain;
// Enables the feature to rank app search result using AppSearchResultRanker
// (only inferencing).
ASH_PUBLIC_EXPORT extern const base::Feature kEnableSearchResultRankerInfer;
bool ASH_PUBLIC_EXPORT IsAnswerCardEnabled();
bool ASH_PUBLIC_EXPORT IsAppShortcutSearchEnabled();
bool ASH_PUBLIC_EXPORT IsBackgroundBlurEnabled();
......@@ -67,8 +75,12 @@ bool ASH_PUBLIC_EXPORT IsNewStyleLauncherEnabled();
bool ASH_PUBLIC_EXPORT IsContinueReadingEnabled();
bool ASH_PUBLIC_EXPORT IsZeroStateSuggestionsEnabled();
bool ASH_PUBLIC_EXPORT IsAppListSearchAutocompleteEnabled();
bool ASH_PUBLIC_EXPORT IsSearchResultRankerTrainEnabled();
bool ASH_PUBLIC_EXPORT IsSearchResultRankerInferEnabled();
std::string ASH_PUBLIC_EXPORT AnswerServerUrl();
std::string ASH_PUBLIC_EXPORT AnswerServerQuerySuffix();
std::string ASH_PUBLIC_EXPORT SearchResultRankerPredictorName();
} // namespace features
} // namespace app_list
......
......@@ -3611,6 +3611,10 @@ jumbo_split_static_library("ui") {
"app_list/search/search_provider.h",
"app_list/search/search_resource_manager.cc",
"app_list/search/search_resource_manager.h",
"app_list/search/search_result_ranker/app_launch_predictor.cc",
"app_list/search/search_result_ranker/app_launch_predictor.h",
"app_list/search/search_result_ranker/app_search_result_ranker.cc",
"app_list/search/search_result_ranker/app_search_result_ranker.h",
"app_list/search/search_util.cc",
"app_list/search/search_util.h",
"app_list/search/search_webstore_result.cc",
......
......@@ -24,6 +24,7 @@
#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ui/app_list/app_sync_ui_state_watcher.h"
#include "chrome/browser/ui/app_list/search/app_result.h"
#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
#include "chrome/browser/ui/app_list/search/search_controller.h"
#include "chrome/browser/ui/app_list/search/search_controller_factory.h"
......@@ -89,8 +90,16 @@ void AppListClientImpl::OpenSearchResult(const std::string& result_id,
if (!search_controller_)
return;
ChromeSearchResult* result = search_controller_->FindSearchResult(result_id);
if (result)
if (result) {
search_controller_->OpenResult(result, event_flags);
// Send training signal to search controller.
if (result->result_type() == ash::SearchResultType::kInstalledApp ||
result->result_type() == ash::SearchResultType::kInternalApp) {
search_controller_->Train(
static_cast<app_list::AppResult*>(result)->app_id());
}
}
}
void AppListClientImpl::InvokeSearchResultAction(const std::string& result_id,
......@@ -153,6 +162,9 @@ void AppListClientImpl::ActivateItem(const std::string& id, int event_flags) {
if (!model_updater_)
return;
model_updater_->ActivateChromeItem(id, event_flags);
// Send training signal to search controller.
search_controller_->Train(id);
}
void AppListClientImpl::GetContextMenuModel(
......
......@@ -46,6 +46,7 @@
#include "chrome/browser/ui/app_list/search/crostini_app_result.h"
#include "chrome/browser/ui/app_list/search/extension_app_result.h"
#include "chrome/browser/ui/app_list/search/internal_app_result.h"
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
#include "chrome/common/pref_names.h"
#include "components/browser_sync/profile_sync_service.h"
#include "components/sync/driver/sync_service.h"
......@@ -61,9 +62,6 @@ using extensions::ExtensionRegistry;
namespace {
// The size of each step unlaunched apps should increase their relevance by.
constexpr double kUnlaunchedAppRelevanceStepSize = 0.0001;
// The minimum capacity we reserve in the Apps container which will be filled
// with extensions and ARC apps, to avoid successive reallocation.
constexpr size_t kMinimumReservedAppsContainerCapacity = 60U;
......@@ -99,6 +97,18 @@ void MaybeAddResult(app_list::SearchProvider::Results* results,
duplicate_app_ids.end());
}
// Linearly maps |score| to the range [min, max].
// |score| is assumed to be within [0.0, 1.0]; if it's greater than 1.0
// then max is returned; if it's less than 0.0, then min is returned.
float ReRange(const float score, const float min, const float max) {
if (score >= 1.0f)
return max;
if (score <= 0.0f)
return min;
return min + score * (max - min);
}
} // namespace
namespace app_list {
......@@ -479,6 +489,7 @@ AppSearchProvider::AppSearchProvider(Profile* profile,
list_controller_(list_controller),
model_updater_(model_updater),
clock_(clock),
ranker_(std::make_unique<AppSearchResultRanker>(profile)),
update_results_factory_(this) {
data_sources_.emplace_back(
std::make_unique<ExtensionDataSource>(profile, this));
......@@ -505,6 +516,10 @@ void AppSearchProvider::Start(const base::string16& query) {
UpdateResults();
}
void AppSearchProvider::Train(const std::string& id) {
ranker_->Train(id);
}
void AppSearchProvider::RefreshApps() {
apps_.clear();
apps_.reserve(kMinimumReservedAppsContainerCapacity);
......@@ -518,6 +533,7 @@ void AppSearchProvider::UpdateRecommendedResults(
std::set<std::string> seen_or_filtered_apps;
const uint16_t apps_size = apps_.size();
new_results.reserve(apps_size);
const auto& ranker_scores = ranker_->Rank();
for (auto& app : apps_) {
// Skip apps which cannot be shown as a suggested app.
......@@ -536,28 +552,33 @@ void AppSearchProvider::UpdateRecommendedResults(
app->data_source()->CreateResult(app->id(), list_controller_, true);
result->SetTitle(title);
// Use the app list order to tiebreak apps that have never been
// launched. The apps that have been installed or launched recently
// should be more relevant than other apps.
// If it is |kInternalAppIdContinueReading|, always show it as the first
// result.
// Set app->relevance based on the following criteria.
const auto find_in_ranker = ranker_scores.find(app->id());
const auto find_in_app_list = id_to_app_list_index.find(app->id());
const base::Time time = app->GetLastActivityTime();
if (time.is_null()) {
double relevance = 1.0;
if (app->id() != kInternalAppIdContinueReading) {
const auto& it = id_to_app_list_index.find(app->id());
// If it's in a folder, it won't be in |id_to_app_list_index|.
// Rank those as if they are at the end of the list.
const size_t app_list_index = (it == id_to_app_list_index.end())
? apps_size
: std::min(apps_size, it->second);
relevance =
kUnlaunchedAppRelevanceStepSize * (apps_size - app_list_index);
}
result->set_relevance(relevance);
} else {
if (app->id() == kInternalAppIdContinueReading) {
// Case 1: if it's |kInternalAppIdContinueReading|, set relevance as 1.0
// (always show it as the first).
result->set_relevance(1.0);
} else if (find_in_ranker != ranker_scores.end()) {
// Case 2: if it's recommended by |ranker_|, set relevance as a score
// in [0.67, 0.99].
result->set_relevance(ReRange(find_in_ranker->second, 0.67, 0.99));
} else if (!time.is_null()) {
// Case 3: if it has last activity time or install time, set the relevance
// in [0.34, 0.66] based on the time.
result->UpdateFromLastLaunchedOrInstalledTime(clock_->Now(), time);
result->set_relevance(ReRange(result->relevance(), 0.34, 0.66));
} else if (find_in_app_list != id_to_app_list_index.end()) {
// Case 4: if it's in the app_list_index, set the relevance in [0.1, 0.33]
result->set_relevance(
ReRange(1.0f / (1.0f + find_in_app_list->second), 0.1, 0.33));
} else {
// Case 5: otherwise set the relevance as 0.0f;
result->set_relevance(0.0f);
}
MaybeAddResult(&new_results, std::move(result), &seen_or_filtered_apps);
}
......
......@@ -25,6 +25,8 @@ class Clock;
namespace app_list {
class AppSearchResultRanker;
class AppSearchProvider : public SearchProvider {
public:
class App;
......@@ -43,6 +45,7 @@ class AppSearchProvider : public SearchProvider {
// SearchProvider overrides:
void Start(const base::string16& query) override;
void Train(const std::string& id) override;
// Refresh indexed app data and update search results. When |force_inline| is
// set to true, search results is updated before returning from the function.
......@@ -64,6 +67,7 @@ class AppSearchProvider : public SearchProvider {
AppListModelUpdater* const model_updater_;
base::Clock* clock_;
std::vector<std::unique_ptr<DataSource>> data_sources_;
std::unique_ptr<AppSearchResultRanker> ranker_;
base::WeakPtrFactory<AppSearchProvider> update_results_factory_;
DISALLOW_COPY_AND_ASSIGN(AppSearchProvider);
......
......@@ -109,4 +109,9 @@ ChromeSearchResult* SearchController::GetResultByTitleForTest(
return nullptr;
}
void SearchController::Train(const std::string& id) {
for (const auto& provider : providers_)
provider->Train(id);
}
} // namespace app_list
......@@ -48,6 +48,9 @@ class SearchController {
ChromeSearchResult* FindSearchResult(const std::string& result_id);
ChromeSearchResult* GetResultByTitleForTest(const std::string& title);
// Sends training signal to each |providers_|
void Train(const std::string& id);
private:
// Invoked when the search results are changed.
void OnResultsChanged();
......
......@@ -26,6 +26,8 @@ class SearchProvider {
// Invoked to start a query.
virtual void Start(const base::string16& query) = 0;
// Handles training signals if necessary.
virtual void Train(const std::string& id) {}
void set_result_changed_callback(const ResultChangedCallback& callback) {
result_changed_callback_ = callback;
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h"
#include <cmath>
#include "base/logging.h"
namespace app_list {
MrfuAppLaunchPredictor::MrfuAppLaunchPredictor() = default;
MrfuAppLaunchPredictor::~MrfuAppLaunchPredictor() = default;
// Updates the score for this |app_id|.
void MrfuAppLaunchPredictor::Train(const std::string& app_id) {
++num_of_trains_;
Score& score = scores_[app_id];
UpdateScore(&score);
score.last_score += 1.0f - decay_coeff_;
}
// Updates all scores and return app_id to score map.
base::flat_map<std::string, float> MrfuAppLaunchPredictor::Rank() {
base::flat_map<std::string, float> output;
for (auto& pair : scores_) {
UpdateScore(&pair.second);
output[pair.first] = pair.second.last_score;
}
return output;
}
// Updates last_score and last_update_timestamp.
void MrfuAppLaunchPredictor::UpdateScore(Score* score) {
const int trains_since_last_time =
num_of_trains_ - score->num_of_trains_at_last_update;
if (trains_since_last_time > 0) {
score->last_score *= std::pow(decay_coeff_, trains_since_last_time);
score->num_of_trains_at_last_update = num_of_trains_;
}
}
} // namespace app_list
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_LAUNCH_PREDICTOR_H_
#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_LAUNCH_PREDICTOR_H_
#include <string>
#include "base/containers/flat_map.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
namespace app_list {
// AppLaunchPredictor is the interface implemented by all predictors. It defines
// two basic public functions Train and Rank for training and inferencing.
class AppLaunchPredictor {
public:
virtual ~AppLaunchPredictor() = default;
// Trains on the |app_id| and (possibly) updates its internal representation.
virtual void Train(const std::string& app_id) = 0;
// Returns a map of app_id and score.
// (1) Higher score means more relevant.
// (2) Only returns a subset of app_ids seen by this predictor.
// (3) The returned scores should be in range [0.0, 1.0] for
// AppSearchProvider to handle.
virtual base::flat_map<std::string, float> Rank() = 0;
};
// MrfuAppLaunchPredictor is a simple AppLaunchPredictor that balances MRU (most
// recently used) and MFU (most frequently used). It is adopted from LRFU cpu
// cache algorithm.
class MrfuAppLaunchPredictor : public AppLaunchPredictor {
public:
MrfuAppLaunchPredictor();
~MrfuAppLaunchPredictor() override;
// AppLaunchPredictor:
void Train(const std::string& app_id) override;
base::flat_map<std::string, float> Rank() override;
private:
FRIEND_TEST_ALL_PREFIXES(AppLaunchPredictorTest, MrfuAppLaunchPredictor);
FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerTest, TrainAndInfer);
// Records last updates of the Score for an app.
struct Score {
int32_t num_of_trains_at_last_update = 0;
float last_score = 0.0f;
};
// Updates the Score to now.
void UpdateScore(Score* score);
// Controls how much the score decays for each Train() call.
// This decay_coeff_ should be within [0.5f, 1.0f]. Setting it as 0.5f means
// MRU; setting as 1.0f means MFU;
// TODO(https://crbug.com/871674):
// (1) Set a better initial value based on real user data.
// (2) Dynamically change this coeff instead of setting it as constant.
static constexpr float decay_coeff_ = 0.8f;
// Map from app_id to its Score.
base::flat_map<std::string, Score> scores_;
// Increment 1 for each Train() call.
int32_t num_of_trains_ = 0;
DISALLOW_COPY_AND_ASSIGN(MrfuAppLaunchPredictor);
};
} // namespace app_list
#endif // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_LAUNCH_PREDICTOR_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::UnorderedElementsAre;
using testing::Pair;
using testing::FloatEq;
namespace app_list {
namespace {
constexpr char kTarget1[] = "Target1";
constexpr char kTarget2[] = "Target2";
} // namespace
TEST(AppLaunchPredictorTest, MrfuAppLaunchPredictor) {
MrfuAppLaunchPredictor predictor;
const float decay = MrfuAppLaunchPredictor::decay_coeff_;
predictor.Train(kTarget1);
const float score_1 = 1.0f - decay;
EXPECT_THAT(predictor.Rank(),
UnorderedElementsAre(Pair(kTarget1, FloatEq(score_1))));
predictor.Train(kTarget1);
const float score_2 = score_1 + score_1 * decay;
EXPECT_THAT(predictor.Rank(),
UnorderedElementsAre(Pair(kTarget1, FloatEq(score_2))));
predictor.Train(kTarget2);
const float score_3 = score_2 * decay;
EXPECT_THAT(predictor.Rank(),
UnorderedElementsAre(Pair(kTarget1, FloatEq(score_3)),
Pair(kTarget2, FloatEq(score_1))));
}
} // namespace app_list
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h"
namespace app_list {
AppSearchResultRanker::AppSearchResultRanker(Profile* profile) {
if (!features::IsSearchResultRankerTrainEnabled())
return;
if (features::SearchResultRankerPredictorName() == "MrfuAppLaunchPredictor") {
predictor_ = std::make_unique<MrfuAppLaunchPredictor>();
load_from_disk_success_ = true;
} else {
NOTREACHED();
}
}
AppSearchResultRanker::~AppSearchResultRanker() = default;
void AppSearchResultRanker::Train(const std::string& app_id) {
if (load_from_disk_success_ && predictor_) {
predictor_->Train(app_id);
}
}
base::flat_map<std::string, float> AppSearchResultRanker::Rank() {
if (load_from_disk_success_ && features::IsSearchResultRankerInferEnabled() &&
predictor_) {
return predictor_->Rank();
}
return {};
}
} // namespace app_list
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_SEARCH_RESULT_RANKER_H_
#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_SEARCH_RESULT_RANKER_H_
#include <memory>
#include <string>
#include "base/containers/flat_map.h"
#include "base/macros.h"
class Profile;
namespace app_list {
class AppLaunchPredictor;
// AppSearchResultRanker is the main class used to train and re-rank the app
// launches.
class AppSearchResultRanker {
public:
// Construct a AppSearchResultRanker with profile. It (possibly)
// asynchronously loads model from disk from profile->GetPath(); and sets
// |load_from_disk_success_| to true when the loading finishes.
// The internal |predictor_| is constructed with param
// SearchResultRankerPredictorName() in "app_list_features.h".
explicit AppSearchResultRanker(Profile* profile);
~AppSearchResultRanker();
// Trains on the |app_id| and (possibly) updates its internal representation.
void Train(const std::string& app_id);
// Returns a map of app_id and score.
// (1) Higher score means more relevant.
// (2) Only returns a subset of app_ids seen by this predictor.
// (3) The returned scores should be in range [0.0, 1.0] for
// AppSearchProvider to handle.
base::flat_map<std::string, float> Rank();
private:
// Internal predictor used for train and rank.
std::unique_ptr<AppLaunchPredictor> predictor_;
bool load_from_disk_success_ = false;
DISALLOW_COPY_AND_ASSIGN(AppSearchResultRanker);
};
} // namespace app_list
#endif // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_SEARCH_RESULT_RANKER_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::UnorderedElementsAre;
using testing::Pair;
using testing::FloatEq;
namespace app_list {
namespace {
constexpr char kTarget1[] = "Target1";
constexpr char kTarget2[] = "Target2";
} // namespace
TEST(AppSearchResultRankerTest, TrainAndInfer) {
base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures(
{features::kEnableSearchResultRankerTrain,
features::kEnableSearchResultRankerInfer},
{});
AppSearchResultRanker ranker(nullptr);
ranker.Train(kTarget1);
ranker.Train(kTarget2);
const float decay = MrfuAppLaunchPredictor::decay_coeff_;
EXPECT_THAT(
ranker.Rank(),
UnorderedElementsAre(Pair(kTarget1, FloatEq((1.0f - decay) * decay)),
Pair(kTarget2, FloatEq(1.0f - decay))));
}
TEST(AppSearchResultRankerTest, ReturnEmptyIfInferIsDisabled) {
base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures(
{features::kEnableSearchResultRankerTrain},
{features::kEnableSearchResultRankerInfer});
AppSearchResultRanker ranker(nullptr);
ranker.Train(kTarget1);
ranker.Train(kTarget2);
EXPECT_TRUE(ranker.Rank().empty());
}
TEST(AppSearchResultRankerTest, ReturnEmptyIfTrainIsDisabled) {
base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures(
{features::kEnableSearchResultRankerTrain},
{features::kEnableSearchResultRankerInfer});
AppSearchResultRanker ranker(nullptr);
ranker.Train(kTarget1);
ranker.Train(kTarget2);
EXPECT_TRUE(ranker.Rank().empty());
}
} // namespace app_list
......@@ -11,9 +11,11 @@
#include <string>
#include <utility>
#include "ash/public/cpp/app_list/app_list_features.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/ui/app_list/app_list_test_util.h"
......@@ -22,6 +24,7 @@
#include "chrome/browser/ui/app_list/arc/arc_app_test.h"
#include "chrome/browser/ui/app_list/extension_app_model_builder.h"
#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h"
#include "chrome/browser/ui/app_list/test/fake_app_list_model_updater.h"
#include "chrome/browser/ui/app_list/test/test_app_list_controller_delegate.h"
#include "chrome/common/chrome_constants.h"
......@@ -140,6 +143,9 @@ class AppSearchProviderTest : public AppListTestBase {
const SearchProvider::Results& results() { return app_search_->results(); }
ArcAppTest& arc_test() { return arc_test_; }
// Train the |app_search| provider with id.
void Train(const std::string& id) { app_search_->Train(id); }
private:
base::SimpleTestClock clock_;
std::unique_ptr<FakeAppListModelUpdater> model_updater_;
......@@ -294,6 +300,53 @@ TEST_F(AppSearchProviderTest, FetchUnlaunchedRecommendations) {
RunQuery(""));
}
TEST_F(AppSearchProviderTest, FetchRecommendationsFromRanker) {
base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures(
{features::kEnableSearchResultRankerTrain,
features::kEnableSearchResultRankerInfer},
{});
CreateSearch();
extensions::ExtensionPrefs* prefs =
extensions::ExtensionPrefs::Get(profile_.get());
prefs->SetLastLaunchTime(kHostedAppId, base::Time::FromInternalValue(20));
prefs->SetLastLaunchTime(kPackagedApp1Id, base::Time::FromInternalValue(10));
prefs->SetLastLaunchTime(kPackagedApp2Id, base::Time::FromInternalValue(5));
EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2,Settings,Camera",
RunQuery(""));
Train(kPackagedApp2Id);
Train(kPackagedApp2Id);
Train(kPackagedApp1Id);
EXPECT_EQ("Packaged App 2,Packaged App 1,Hosted App,Settings,Camera",
RunQuery(""));
}
TEST_F(AppSearchProviderTest, RankerIsDisabledWithFlag) {
base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures(
{features::kEnableSearchResultRankerTrain},
{features::kEnableSearchResultRankerInfer});
CreateSearch();
extensions::ExtensionPrefs* prefs =
extensions::ExtensionPrefs::Get(profile_.get());
prefs->SetLastLaunchTime(kHostedAppId, base::Time::FromInternalValue(20));
prefs->SetLastLaunchTime(kPackagedApp1Id, base::Time::FromInternalValue(10));
prefs->SetLastLaunchTime(kPackagedApp2Id, base::Time::FromInternalValue(5));
EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2,Settings,Camera",
RunQuery(""));
Train(kPackagedApp2Id);
Train(kPackagedApp2Id);
Train(kPackagedApp1Id);
EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2,Settings,Camera",
RunQuery(""));
}
TEST_F(AppSearchProviderTest, FilterDuplicate) {
arc_test().SetUp(profile());
......
......@@ -4513,6 +4513,8 @@ test("unit_tests") {
"../browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc",
"../browser/ui/app_list/search/arc/arc_playstore_search_provider_unittest.cc",
"../browser/ui/app_list/search/launcher_search/launcher_search_icon_image_loader_unittest.cc",
"../browser/ui/app_list/search/search_result_ranker/app_launch_predictor_unittest.cc",
"../browser/ui/app_list/search/search_result_ranker/app_search_result_ranker_unittest.cc",
"../browser/ui/app_list/search/settings_shortcut/settings_shortcut_provider_unittest.cc",
"../browser/ui/app_list/search/settings_shortcut/settings_shortcut_result_unittest.cc",
"../browser/ui/app_list/search/tests/app_search_provider_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment