Commit 9c448fac authored by Charles Zhao's avatar Charles Zhao Committed by Commit Bot

cros: Add HourAppLaunchPredictor for AppSearchResultRanker

(1) Add HourAppLaunchPredictor with unit test.

(2) Add SerializationTesterAppLaunchPredictor for testing
    AppSearchResultRanker materialization logic.

(3) Extend AppLaunchPredictor interface to support more functions.

Bug: 871674
Change-Id: I38bbc0b7efd1ed3576c4c3d170c553ec80dcec93
Reviewed-on: https://chromium-review.googlesource.com/1189506
Commit-Queue: Charles . <charleszhao@chromium.org>
Reviewed-by: default avatarJia Meng <jiameng@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#590133}
parent cbb4d247
...@@ -35,10 +35,8 @@ const base::Feature kEnableZeroStateSuggestions{ ...@@ -35,10 +35,8 @@ const base::Feature kEnableZeroStateSuggestions{
"EnableZeroStateSuggestions", base::FEATURE_DISABLED_BY_DEFAULT}; "EnableZeroStateSuggestions", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kEnableAppListSearchAutocomplete{ const base::Feature kEnableAppListSearchAutocomplete{
"EnableAppListSearchAutocomplete", base::FEATURE_ENABLED_BY_DEFAULT}; "EnableAppListSearchAutocomplete", base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kEnableSearchResultRankerTrain{ const base::Feature kEnableAppSearchResultRanker{
"EnableSearchResultRankerTrain", base::FEATURE_DISABLED_BY_DEFAULT}; "EnableAppSearchResultRanker", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kEnableSearchResultRankerInfer{
"EnableSearchResultRankerInfer", base::FEATURE_DISABLED_BY_DEFAULT};
bool IsAnswerCardEnabled() { bool IsAnswerCardEnabled() {
// Not using local static variable to allow tests to change this value. // Not using local static variable to allow tests to change this value.
...@@ -90,12 +88,8 @@ bool IsAppListSearchAutocompleteEnabled() { ...@@ -90,12 +88,8 @@ bool IsAppListSearchAutocompleteEnabled() {
return base::FeatureList::IsEnabled(kEnableAppListSearchAutocomplete); return base::FeatureList::IsEnabled(kEnableAppListSearchAutocomplete);
} }
bool IsSearchResultRankerTrainEnabled() { bool IsAppSearchResultRankerEnabled() {
return base::FeatureList::IsEnabled(kEnableSearchResultRankerTrain); return base::FeatureList::IsEnabled(kEnableAppSearchResultRanker);
}
bool IsSearchResultRankerInferEnabled() {
return base::FeatureList::IsEnabled(kEnableSearchResultRankerInfer);
} }
std::string AnswerServerUrl() { std::string AnswerServerUrl() {
...@@ -111,10 +105,9 @@ std::string AnswerServerQuerySuffix() { ...@@ -111,10 +105,9 @@ std::string AnswerServerQuerySuffix() {
"QuerySuffix"); "QuerySuffix");
} }
std::string SearchResultRankerPredictorName() { std::string AppSearchResultRankerPredictorName() {
const std::string predictor_name = base::GetFieldTrialParamValueByFeature( const std::string predictor_name = base::GetFieldTrialParamValueByFeature(
kEnableSearchResultRankerTrain, kEnableAppSearchResultRanker, "app_search_result_ranker_predictor_name");
"app_search_result_ranker_predictor_name");
if (!predictor_name.empty()) if (!predictor_name.empty())
return predictor_name; return predictor_name;
return std::string("MrfuAppLaunchPredictor"); return std::string("MrfuAppLaunchPredictor");
......
...@@ -61,13 +61,8 @@ ASH_PUBLIC_EXPORT extern const base::Feature kEnableZeroStateSuggestions; ...@@ -61,13 +61,8 @@ ASH_PUBLIC_EXPORT extern const base::Feature kEnableZeroStateSuggestions;
// Enables the feature to autocomplete text typed in the AppList search box. // Enables the feature to autocomplete text typed in the AppList search box.
ASH_PUBLIC_EXPORT extern const base::Feature kEnableAppListSearchAutocomplete; ASH_PUBLIC_EXPORT extern const base::Feature kEnableAppListSearchAutocomplete;
// Enables the feature to rank app search result using AppSearchResultRanker // Enables the feature to rank app search result using AppSearchResultRanker.
// (only training). ASH_PUBLIC_EXPORT extern const base::Feature kEnableAppSearchResultRanker;
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 IsAnswerCardEnabled();
bool ASH_PUBLIC_EXPORT IsAppShortcutSearchEnabled(); bool ASH_PUBLIC_EXPORT IsAppShortcutSearchEnabled();
...@@ -81,12 +76,11 @@ bool ASH_PUBLIC_EXPORT IsNewStyleLauncherEnabled(); ...@@ -81,12 +76,11 @@ bool ASH_PUBLIC_EXPORT IsNewStyleLauncherEnabled();
bool ASH_PUBLIC_EXPORT IsContinueReadingEnabled(); bool ASH_PUBLIC_EXPORT IsContinueReadingEnabled();
bool ASH_PUBLIC_EXPORT IsZeroStateSuggestionsEnabled(); bool ASH_PUBLIC_EXPORT IsZeroStateSuggestionsEnabled();
bool ASH_PUBLIC_EXPORT IsAppListSearchAutocompleteEnabled(); bool ASH_PUBLIC_EXPORT IsAppListSearchAutocompleteEnabled();
bool ASH_PUBLIC_EXPORT IsSearchResultRankerTrainEnabled(); bool ASH_PUBLIC_EXPORT IsAppSearchResultRankerEnabled();
bool ASH_PUBLIC_EXPORT IsSearchResultRankerInferEnabled();
std::string ASH_PUBLIC_EXPORT AnswerServerUrl(); std::string ASH_PUBLIC_EXPORT AnswerServerUrl();
std::string ASH_PUBLIC_EXPORT AnswerServerQuerySuffix(); std::string ASH_PUBLIC_EXPORT AnswerServerQuerySuffix();
std::string ASH_PUBLIC_EXPORT SearchResultRankerPredictorName(); std::string ASH_PUBLIC_EXPORT AppSearchResultRankerPredictorName();
} // namespace features } // namespace features
} // namespace app_list } // namespace app_list
......
...@@ -3589,6 +3589,7 @@ jumbo_split_static_library("ui") { ...@@ -3589,6 +3589,7 @@ jumbo_split_static_library("ui") {
"//ash/app_list", "//ash/app_list",
"//ash/public/cpp/app_list/vector_icons", "//ash/public/cpp/app_list/vector_icons",
"//ash/resources/vector_icons", "//ash/resources/vector_icons",
"//chrome/browser/ui/app_list/search/search_result_ranker:app_launch_predictor_proto",
"//ui/views/mus/remote_view:remote_view_provider", "//ui/views/mus/remote_view:remote_view_provider",
] ]
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h" #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h" #include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/extensions/gfx_utils.h" #include "chrome/browser/chromeos/extensions/gfx_utils.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_ui_util.h" #include "chrome/browser/extensions/extension_ui_util.h"
#include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/extension_util.h"
...@@ -512,7 +513,9 @@ AppSearchProvider::AppSearchProvider(Profile* profile, ...@@ -512,7 +513,9 @@ AppSearchProvider::AppSearchProvider(Profile* profile,
list_controller_(list_controller), list_controller_(list_controller),
model_updater_(model_updater), model_updater_(model_updater),
clock_(clock), clock_(clock),
ranker_(std::make_unique<AppSearchResultRanker>(profile)), ranker_(std::make_unique<AppSearchResultRanker>(
profile->GetPath(),
chromeos::ProfileHelper::IsEphemeralUserProfile(profile))),
update_results_factory_(this) { update_results_factory_(this) {
data_sources_.emplace_back( data_sources_.emplace_back(
std::make_unique<ExtensionDataSource>(profile, this)); std::make_unique<ExtensionDataSource>(profile, this));
......
# 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.
import("//third_party/protobuf/proto_library.gni")
proto_library("app_launch_predictor_proto") {
sources = [
"app_launch_predictor.proto",
]
}
...@@ -7,22 +7,37 @@ ...@@ -7,22 +7,37 @@
#include <cmath> #include <cmath>
#include "base/logging.h" #include "base/logging.h"
#include "base/stl_util.h"
namespace app_list { namespace app_list {
namespace {
constexpr int kHoursADay = 24;
constexpr base::TimeDelta kSaveInternal = base::TimeDelta::FromHours(1);
// A bin with index i has 5 adjacent bins as: i + 0, i + 1, i + 2, i + 22, and
// i + 23. They each contributes to the final Rank score with different level:
// 0.6 for i-th bin itself, 0.15 for i + 1 (one hour later) and i + 23 (
// one hour earlier), 0.05 for i + 2 (two hours later) and i + 22 (two hours
// earlier).
constexpr int kAdjacentHourBin[] = {0, 1, 2, 22, 23};
constexpr float kAdjacentHourWeight[] = {0.6, 0.15, 0.05, 0.05, 0.15};
} // namespace
MrfuAppLaunchPredictor::MrfuAppLaunchPredictor() = default; MrfuAppLaunchPredictor::MrfuAppLaunchPredictor() = default;
MrfuAppLaunchPredictor::~MrfuAppLaunchPredictor() = default; MrfuAppLaunchPredictor::~MrfuAppLaunchPredictor() = default;
// Updates the score for this |app_id|.
void MrfuAppLaunchPredictor::Train(const std::string& app_id) { void MrfuAppLaunchPredictor::Train(const std::string& app_id) {
// Updates the score for this |app_id|.
++num_of_trains_; ++num_of_trains_;
Score& score = scores_[app_id]; Score& score = scores_[app_id];
UpdateScore(&score); UpdateScore(&score);
score.last_score += 1.0f - decay_coeff_; 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> MrfuAppLaunchPredictor::Rank() {
// Updates all scores and return app_id to score map.
base::flat_map<std::string, float> output; base::flat_map<std::string, float> output;
for (auto& pair : scores_) { for (auto& pair : scores_) {
UpdateScore(&pair.second); UpdateScore(&pair.second);
...@@ -31,8 +46,30 @@ base::flat_map<std::string, float> MrfuAppLaunchPredictor::Rank() { ...@@ -31,8 +46,30 @@ base::flat_map<std::string, float> MrfuAppLaunchPredictor::Rank() {
return output; return output;
} }
// Updates last_score and last_update_timestamp. const char MrfuAppLaunchPredictor::kPredictorName[] = "MrfuAppLaunchPredictor";
const char* MrfuAppLaunchPredictor::GetPredictorName() const {
return kPredictorName;
}
bool MrfuAppLaunchPredictor::ShouldSave() {
// MrfuAppLaunchPredictor doesn't need materialization.
return false;
}
AppLaunchPredictorProto MrfuAppLaunchPredictor::ToProto() const {
// MrfuAppLaunchPredictor doesn't need materialization.
NOTREACHED();
return AppLaunchPredictorProto();
}
bool MrfuAppLaunchPredictor::FromProto(const AppLaunchPredictorProto& proto) {
// MrfuAppLaunchPredictor doesn't need materialization.
NOTREACHED();
return false;
}
void MrfuAppLaunchPredictor::UpdateScore(Score* score) { void MrfuAppLaunchPredictor::UpdateScore(Score* score) {
// Updates last_score and num_of_trains_at_last_update.
const int trains_since_last_time = const int trains_since_last_time =
num_of_trains_ - score->num_of_trains_at_last_update; num_of_trains_ - score->num_of_trains_at_last_update;
if (trains_since_last_time > 0) { if (trains_since_last_time > 0) {
...@@ -41,4 +78,131 @@ void MrfuAppLaunchPredictor::UpdateScore(Score* score) { ...@@ -41,4 +78,131 @@ void MrfuAppLaunchPredictor::UpdateScore(Score* score) {
} }
} }
HourAppLaunchPredictor::HourAppLaunchPredictor()
: last_save_timestamp_(base::Time::Now()) {}
HourAppLaunchPredictor::~HourAppLaunchPredictor() = default;
void HourAppLaunchPredictor::Train(const std::string& app_id) {
auto& frequency_table = (*proto_.mutable_hour_app_launch_predictor()
->mutable_binned_frequency_table())[GetBin()];
frequency_table.set_total_counts(frequency_table.total_counts() + 1);
(*frequency_table.mutable_frequency())[app_id] += 1;
}
base::flat_map<std::string, float> HourAppLaunchPredictor::Rank() {
base::flat_map<std::string, float> output;
const int bin = GetBin();
const bool is_weekend = bin >= kHoursADay;
const int hour = bin % kHoursADay;
const auto& frequency_table_map =
proto_.hour_app_launch_predictor().binned_frequency_table();
for (size_t i = 0; i < base::size(kAdjacentHourBin); ++i) {
// Finds adjacent bin and weight.
const int adj_bin =
(hour + kAdjacentHourBin[i]) % kHoursADay + kHoursADay * is_weekend;
const auto find_frequency_table = frequency_table_map.find(adj_bin);
if (find_frequency_table == frequency_table_map.end())
continue;
const auto& frequency_table = find_frequency_table->second;
const float weight = kAdjacentHourWeight[i];
// Accumulates the frequency to the output.
if (frequency_table.total_counts() > 0) {
const int total_counts = frequency_table.total_counts();
for (const auto& pair : frequency_table.frequency()) {
output[pair.first] +=
static_cast<float>(pair.second) / total_counts * weight;
}
}
}
return output;
}
const char HourAppLaunchPredictor::kPredictorName[] = "HourAppLaunchPredictor";
const char* HourAppLaunchPredictor::GetPredictorName() const {
return kPredictorName;
}
bool HourAppLaunchPredictor::ShouldSave() {
const base::Time now = base::Time::Now();
if (now - last_save_timestamp_ >= kSaveInternal) {
last_save_timestamp_ = now;
return true;
}
return false;
}
AppLaunchPredictorProto HourAppLaunchPredictor::ToProto() const {
return proto_;
}
bool HourAppLaunchPredictor::FromProto(const AppLaunchPredictorProto& proto) {
if (proto.predictor_case() !=
AppLaunchPredictorProto::kHourAppLaunchPredictor) {
return false;
}
proto_ = proto;
return true;
}
int HourAppLaunchPredictor::GetBin() const {
base::Time::Exploded now;
base::Time::Now().LocalExplode(&now);
const bool is_weekend = now.day_of_week == 6 || now.day_of_week == 0;
// To distinguish workdays with weekends, we use now.hour for workdays, and
// now.hour + 24 for weekends.
if (!is_weekend) {
return now.hour;
} else {
return now.hour + kHoursADay;
}
}
void FakeAppLaunchPredictor::SetShouldSave(bool should_save) {
should_save_ = should_save;
}
void FakeAppLaunchPredictor::Train(const std::string& app_id) {
// Increases 1.0 for rank score of app_id.
(*proto_.mutable_fake_app_launch_predictor()
->mutable_rank_result())[app_id] += 1.0f;
}
base::flat_map<std::string, float> FakeAppLaunchPredictor::Rank() {
// Outputs proto_.fake_app_launch_predictor().rank_result() as Rank result.
base::flat_map<std::string, float> output;
for (const auto& pair : proto_.fake_app_launch_predictor().rank_result()) {
output[pair.first] = pair.second;
}
return output;
}
const char FakeAppLaunchPredictor::kPredictorName[] = "FakeAppLaunchPredictor";
const char* FakeAppLaunchPredictor::GetPredictorName() const {
return kPredictorName;
}
bool FakeAppLaunchPredictor::ShouldSave() {
return should_save_;
}
AppLaunchPredictorProto FakeAppLaunchPredictor::ToProto() const {
return proto_;
}
bool FakeAppLaunchPredictor::FromProto(const AppLaunchPredictorProto& proto) {
if (proto.predictor_case() !=
AppLaunchPredictorProto::kFakeAppLaunchPredictor) {
return false;
}
proto_ = proto;
return true;
}
} // namespace app_list } // namespace app_list
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
#include "base/containers/flat_map.h" #include "base/containers/flat_map.h"
#include "base/gtest_prod_util.h" #include "base/gtest_prod_util.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/time/time.h"
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.pb.h"
namespace app_list { namespace app_list {
...@@ -26,6 +28,14 @@ class AppLaunchPredictor { ...@@ -26,6 +28,14 @@ class AppLaunchPredictor {
// (3) The returned scores should be in range [0.0, 1.0] for // (3) The returned scores should be in range [0.0, 1.0] for
// AppSearchProvider to handle. // AppSearchProvider to handle.
virtual base::flat_map<std::string, float> Rank() = 0; virtual base::flat_map<std::string, float> Rank() = 0;
// Returns the name of the predictor.
virtual const char* GetPredictorName() const = 0;
// Whether the model should be saved on disk at this moment.
virtual bool ShouldSave() = 0;
// Converts the predictor to AppLaunchPredictorProto.
virtual AppLaunchPredictorProto ToProto() const = 0;
// Initializes the predictor with |proto|.
virtual bool FromProto(const AppLaunchPredictorProto& proto) = 0;
}; };
// MrfuAppLaunchPredictor is a simple AppLaunchPredictor that balances MRU (most // MrfuAppLaunchPredictor is a simple AppLaunchPredictor that balances MRU (most
...@@ -39,10 +49,16 @@ class MrfuAppLaunchPredictor : public AppLaunchPredictor { ...@@ -39,10 +49,16 @@ class MrfuAppLaunchPredictor : public AppLaunchPredictor {
// AppLaunchPredictor: // AppLaunchPredictor:
void Train(const std::string& app_id) override; void Train(const std::string& app_id) override;
base::flat_map<std::string, float> Rank() override; base::flat_map<std::string, float> Rank() override;
const char* GetPredictorName() const override;
bool ShouldSave() override;
AppLaunchPredictorProto ToProto() const override;
bool FromProto(const AppLaunchPredictorProto& proto) override;
// Name of the predictor;
static const char kPredictorName[];
private: private:
FRIEND_TEST_ALL_PREFIXES(AppLaunchPredictorTest, MrfuAppLaunchPredictor); FRIEND_TEST_ALL_PREFIXES(AppLaunchPredictorTest, MrfuAppLaunchPredictor);
FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerTest, TrainAndInfer);
// Records last updates of the Score for an app. // Records last updates of the Score for an app.
struct Score { struct Score {
...@@ -68,6 +84,75 @@ class MrfuAppLaunchPredictor : public AppLaunchPredictor { ...@@ -68,6 +84,75 @@ class MrfuAppLaunchPredictor : public AppLaunchPredictor {
DISALLOW_COPY_AND_ASSIGN(MrfuAppLaunchPredictor); DISALLOW_COPY_AND_ASSIGN(MrfuAppLaunchPredictor);
}; };
// HourAppLaunchPredictor is a AppLaunchPredictor that uses hour of the day as
// bins, and uses app-launch frequency of in each bin as the Rank score.
// For example, if it's 8:30 am right now, then only app-launches between 8am to
// 9am in the last a few days are mainly considered.
// NOTE 1: bins of nearby hours also contributes to the final score but less
// significient. For example if current time is 8am, then scores in 6am,
// 7am, 9am, and 10am are also added to the final Rank score with
// smaller weights.
// NOTE 2: workdays and weekends are put into different bins.
class HourAppLaunchPredictor : public AppLaunchPredictor {
public:
HourAppLaunchPredictor();
~HourAppLaunchPredictor() override;
// AppLaunchPredictor:
void Train(const std::string& app_id) override;
base::flat_map<std::string, float> Rank() override;
const char* GetPredictorName() const override;
bool ShouldSave() override;
AppLaunchPredictorProto ToProto() const override;
bool FromProto(const AppLaunchPredictorProto& proto) override;
// Name of the predictor;
static const char kPredictorName[];
private:
FRIEND_TEST_ALL_PREFIXES(HourAppLaunchPredictorTest, GetTheRightBin);
FRIEND_TEST_ALL_PREFIXES(HourAppLaunchPredictorTest, RankFromSingleBin);
FRIEND_TEST_ALL_PREFIXES(HourAppLaunchPredictorTest, RankFromMultipleBin);
// Returns current bin index of this predictor.
int GetBin() const;
// The proto for this predictor.
AppLaunchPredictorProto proto_;
// Last time the predictor was saved.
base::Time last_save_timestamp_;
DISALLOW_COPY_AND_ASSIGN(HourAppLaunchPredictor);
};
// Predictor for testing AppSearchResultRanker only.
class FakeAppLaunchPredictor : public AppLaunchPredictor {
public:
FakeAppLaunchPredictor() = default;
~FakeAppLaunchPredictor() override = default;
// Manually set |should_save_|;
void SetShouldSave(bool should_save);
// AppLaunchPredictor:
void Train(const std::string& app_id) override;
base::flat_map<std::string, float> Rank() override;
const char* GetPredictorName() const override;
bool ShouldSave() override;
AppLaunchPredictorProto ToProto() const override;
bool FromProto(const AppLaunchPredictorProto& proto) override;
// Name of the predictor;
static const char kPredictorName[];
private:
bool should_save_ = false;
// The proto for this predictor.
AppLaunchPredictorProto proto_;
DISALLOW_COPY_AND_ASSIGN(FakeAppLaunchPredictor);
};
} // namespace app_list } // namespace app_list
#endif // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_APP_LAUNCH_PREDICTOR_H_ #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.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package app_list;
// HourAppLaunchPredictorProto is used for materializing HourAppLaunchPredictor.
message HourAppLaunchPredictorProto {
// A frequency table records app launches that happened in a particular bin.
message FrequencyTable {
// Total number of launches (within this bin), should equal to the sum of
// frequency below.
optional int32 total_counts = 1;
// Number of launches for each app (within this bin).
map<string, int32> frequency = 2;
}
// A map from bin indices to each FrequencyTable of that bin.
map<int32, FrequencyTable> binned_frequency_table = 1;
}
// Used only for testing AppSearchResultRanker.
message FakeAppLaunchPredictorProto {
map<string, float> rank_result = 1;
}
// AppLaunchPredictorProto contains one type of the predictor proto above.
message AppLaunchPredictorProto {
oneof predictor {
FakeAppLaunchPredictorProto fake_app_launch_predictor = 1;
HourAppLaunchPredictorProto hour_app_launch_predictor = 2;
}
}
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h" #include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h"
#include "base/test/scoped_mock_clock_override.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -41,4 +42,135 @@ TEST(AppLaunchPredictorTest, MrfuAppLaunchPredictor) { ...@@ -41,4 +42,135 @@ TEST(AppLaunchPredictorTest, MrfuAppLaunchPredictor) {
Pair(kTarget2, FloatEq(score_1)))); Pair(kTarget2, FloatEq(score_1))));
} }
class HourAppLaunchPredictorTest : public testing::Test {
protected:
// Sets local time according to |day_of_week| and |hour_of_day|.
void SetLocalTime(const int day_of_week, const int hour_of_day) {
AdvanceToNextLocalSunday();
const auto advance = base::TimeDelta::FromDays(day_of_week) +
base::TimeDelta::FromHours(hour_of_day);
if (advance > base::TimeDelta()) {
time_.Advance(advance);
}
}
private:
// Advances time to be 0am next Sunday.
void AdvanceToNextLocalSunday() {
base::Time::Exploded now;
base::Time::Now().LocalExplode(&now);
const auto advance = base::TimeDelta::FromDays(6 - now.day_of_week) +
base::TimeDelta::FromHours(24 - now.hour);
if (advance > base::TimeDelta()) {
time_.Advance(advance);
}
base::Time::Now().LocalExplode(&now);
CHECK_EQ(now.day_of_week, 0);
CHECK_EQ(now.hour, 0);
}
base::ScopedMockClockOverride time_;
};
// Checks HourAppLaunchPredictor::GetBin returns the right bin index for given
// local time.
TEST_F(HourAppLaunchPredictorTest, GetTheRightBin) {
HourAppLaunchPredictor predictor;
// Monday.
for (int i = 0; i <= 23; ++i) {
SetLocalTime(1, i);
EXPECT_EQ(predictor.GetBin(), i);
}
// Friday.
for (int i = 0; i <= 23; ++i) {
SetLocalTime(5, i);
EXPECT_EQ(predictor.GetBin(), i);
}
// Saturday.
for (int i = 0; i <= 23; ++i) {
SetLocalTime(6, i);
EXPECT_EQ(predictor.GetBin(), i + 24);
}
// Sunday.
for (int i = 0; i <= 23; ++i) {
SetLocalTime(0, i);
EXPECT_EQ(predictor.GetBin(), i + 24);
}
}
// Checks the apps are ranked based on frequency in a single bin.
TEST_F(HourAppLaunchPredictorTest, RankFromSingleBin) {
HourAppLaunchPredictor predictor;
// Create a model that trained on kTarget1 3 times, and kTarget2 2 times.
SetLocalTime(1, 10);
predictor.Train(kTarget1);
SetLocalTime(2, 10);
predictor.Train(kTarget1);
SetLocalTime(3, 10);
predictor.Train(kTarget1);
SetLocalTime(4, 10);
predictor.Train(kTarget2);
SetLocalTime(5, 10);
predictor.Train(kTarget2);
// Train on weekend will not fail into the same bin.
SetLocalTime(6, 10);
predictor.Train(kTarget1);
SetLocalTime(0, 10);
predictor.Train(kTarget2);
SetLocalTime(1, 10);
EXPECT_THAT(predictor.Rank(),
UnorderedElementsAre(Pair(kTarget1, FloatEq(0.6 * 0.6)),
Pair(kTarget2, FloatEq(0.6 * 0.4))));
}
// Checks the apps are ranked based on linearly combined scores from adjacent
// bins.
TEST_F(HourAppLaunchPredictorTest, RankFromMultipleBin) {
HourAppLaunchPredictor predictor;
// For bin 10
SetLocalTime(1, 10);
predictor.Train(kTarget1);
predictor.Train(kTarget1);
SetLocalTime(2, 10);
predictor.Train(kTarget2);
// For bin 11
SetLocalTime(3, 11);
predictor.Train(kTarget1);
predictor.Train(kTarget2);
// FOr bin 12
SetLocalTime(5, 12);
predictor.Train(kTarget2);
// Train on weekend.
SetLocalTime(6, 10);
predictor.Train(kTarget1);
predictor.Train(kTarget2);
SetLocalTime(0, 11);
predictor.Train(kTarget2);
// Check workdays.
SetLocalTime(1, 10);
EXPECT_THAT(
predictor.Rank(),
UnorderedElementsAre(
Pair(kTarget1, FloatEq(0.6 * 2.0 / 3.0 + 0.15 * 0.5)),
Pair(kTarget2, FloatEq(0.6 * 1.0 / 3.0 + 0.15 * 0.5 + 0.05 * 1.0))));
// Check weekends.
SetLocalTime(0, 9);
EXPECT_THAT(
predictor.Rank(),
UnorderedElementsAre(Pair(kTarget1, FloatEq(0.15 * 1.0 / 2.0)),
Pair(kTarget2, FloatEq(0.15 * 1.0 / 2.0 + 0.05))));
}
} // namespace app_list } // namespace app_list
...@@ -5,38 +5,141 @@ ...@@ -5,38 +5,141 @@
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h" #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 "ash/public/cpp/app_list/app_list_features.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/task_runner_util.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h" #include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h"
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.pb.h"
namespace app_list { namespace app_list {
namespace {
AppSearchResultRanker::AppSearchResultRanker(Profile* profile) { constexpr char kAppLaunchPredictorFilename[] = "app_launch_predictor";
if (!features::IsSearchResultRankerTrainEnabled())
return; // Returns a AppLaunchPredictor pointer based on the |predictor_name|.
std::unique_ptr<AppLaunchPredictor> CreatePredictor(
const std::string& predictor_name) {
if (predictor_name == MrfuAppLaunchPredictor::kPredictorName)
return std::make_unique<MrfuAppLaunchPredictor>();
if (predictor_name == HourAppLaunchPredictor::kPredictorName)
return std::make_unique<HourAppLaunchPredictor>();
if (predictor_name == FakeAppLaunchPredictor::kPredictorName)
return std::make_unique<FakeAppLaunchPredictor>();
if (features::SearchResultRankerPredictorName() == "MrfuAppLaunchPredictor") {
predictor_ = std::make_unique<MrfuAppLaunchPredictor>();
load_from_disk_success_ = true;
} else {
NOTREACHED(); NOTREACHED();
return nullptr;
}
// Save |proto| to |predictor_filename|.
void SaveToDiskOnWorkerThread(const base::FilePath& predictor_filename,
const AppLaunchPredictorProto& proto) {
base::AssertBlockingAllowed();
std::string proto_str;
if (!proto.SerializeToString(&proto_str))
return;
base::ImportantFileWriter::WriteFileAtomically(predictor_filename, proto_str,
"AppSearchResultRanker");
}
// Loads a AppLaunchPredictor from |predictor_filename|.
std::unique_ptr<AppLaunchPredictor> LoadPredictorFromDiskOnWorkerThread(
const base::FilePath& predictor_filename,
const std::string predictor_name) {
base::AssertBlockingAllowed();
// Loads proto string from local disk.
std::string proto_str;
if (!base::ReadFileToString(predictor_filename, &proto_str))
return nullptr;
// Parses proto string as AppLaunchPredictorProto.
AppLaunchPredictorProto proto;
if (!proto.ParseFromString(proto_str))
return nullptr;
auto predictor = CreatePredictor(predictor_name);
// Initializes the |predictor_| from the |proto|.
if (!predictor->FromProto(proto))
return nullptr;
return predictor;
}
} // namespace
AppSearchResultRanker::AppSearchResultRanker(const base::FilePath& profile_path,
bool is_ephemeral_user)
: predictor_filename_(
profile_path.AppendASCII(kAppLaunchPredictorFilename)),
weak_factory_(this) {
if (!features::IsAppSearchResultRankerEnabled())
return;
predictor_ = CreatePredictor(features::AppSearchResultRankerPredictorName());
// MrfuAppLaunchPredictor doesn't have materialization, so no loading from
// local disk.
if (predictor_->GetPredictorName() ==
MrfuAppLaunchPredictor::kPredictorName) {
load_from_disk_completed_ = true;
return;
} }
// For ephemeral users, we disable AppSearchResultRanker to make finch
// experiment easier.
if (is_ephemeral_user)
return;
task_runner_ = base::CreateSequencedTaskRunnerWithTraits(
{base::TaskPriority::BEST_EFFORT, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
// Loads the predictor from disk asynchronously.
base::PostTaskAndReplyWithResult(
task_runner_.get(), FROM_HERE,
base::BindOnce(&LoadPredictorFromDiskOnWorkerThread, predictor_filename_,
predictor_->GetPredictorName()),
base::BindOnce(&AppSearchResultRanker::OnLoadFromDiskComplete,
weak_factory_.GetWeakPtr()));
} }
AppSearchResultRanker::~AppSearchResultRanker() = default; AppSearchResultRanker::~AppSearchResultRanker() = default;
void AppSearchResultRanker::Train(const std::string& app_id) { void AppSearchResultRanker::Train(const std::string& app_id) {
if (load_from_disk_success_ && predictor_) { if (load_from_disk_completed_) {
predictor_->Train(app_id); predictor_->Train(app_id);
if (predictor_->ShouldSave()) {
// Writes the predictor proto to disk asynchronously.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SaveToDiskOnWorkerThread, predictor_filename_,
predictor_->ToProto()));
}
} }
} }
base::flat_map<std::string, float> AppSearchResultRanker::Rank() { base::flat_map<std::string, float> AppSearchResultRanker::Rank() {
if (load_from_disk_success_ && features::IsSearchResultRankerInferEnabled() && if (load_from_disk_completed_) {
predictor_) {
return predictor_->Rank(); return predictor_->Rank();
} }
return {}; return {};
} }
void AppSearchResultRanker::OnLoadFromDiskComplete(
std::unique_ptr<AppLaunchPredictor> predictor) {
if (predictor) {
predictor_.swap(predictor);
}
load_from_disk_completed_ = true;
}
} // namespace app_list } // namespace app_list
...@@ -9,9 +9,11 @@ ...@@ -9,9 +9,11 @@
#include <string> #include <string>
#include "base/containers/flat_map.h" #include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/scoped_refptr.h"
class Profile; #include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
namespace app_list { namespace app_list {
...@@ -22,11 +24,15 @@ class AppLaunchPredictor; ...@@ -22,11 +24,15 @@ class AppLaunchPredictor;
class AppSearchResultRanker { class AppSearchResultRanker {
public: public:
// Construct a AppSearchResultRanker with profile. It (possibly) // Construct a AppSearchResultRanker with profile. It (possibly)
// asynchronously loads model from disk from profile->GetPath(); and sets // asynchronously loads model from disk from |profile_path|; and sets
// |load_from_disk_success_| to true when the loading finishes. // |load_from_disk_success_| to true when the loading finishes.
// The internal |predictor_| is constructed with param // The internal |predictor_| is constructed with param
// SearchResultRankerPredictorName() in "app_list_features.h". // SearchResultRankerPredictorName() in "app_list_features.h".
explicit AppSearchResultRanker(Profile* profile); // Ephemeral users are speically handled since their profiles are cleaned up
// after logging out.
explicit AppSearchResultRanker(const base::FilePath& profile_path,
bool is_ephemeral_user);
~AppSearchResultRanker(); ~AppSearchResultRanker();
// Trains on the |app_id| and (possibly) updates its internal representation. // Trains on the |app_id| and (possibly) updates its internal representation.
...@@ -39,9 +45,25 @@ class AppSearchResultRanker { ...@@ -39,9 +45,25 @@ class AppSearchResultRanker {
base::flat_map<std::string, float> Rank(); base::flat_map<std::string, float> Rank();
private: private:
FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerSerializationTest,
LoadFromDiskSucceed);
FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerSerializationTest,
LoadFromDiskFailIfNoFileExists);
FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerSerializationTest,
LoadFromDiskFailWithInvalidProto);
FRIEND_TEST_ALL_PREFIXES(AppSearchResultRankerSerializationTest,
SaveToDiskSucceed);
// Sets |predictor_| and |load_from_disk_completed_| when
// LoadPredictorFromDiskOnWorkerThread completes.
void OnLoadFromDiskComplete(std::unique_ptr<AppLaunchPredictor> predictor);
// Internal predictor used for train and rank. // Internal predictor used for train and rank.
std::unique_ptr<AppLaunchPredictor> predictor_; std::unique_ptr<AppLaunchPredictor> predictor_;
bool load_from_disk_success_ = false; bool load_from_disk_completed_ = false;
const base::FilePath predictor_filename_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::WeakPtrFactory<AppSearchResultRanker> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(AppSearchResultRanker); DISALLOW_COPY_AND_ASSIGN(AppSearchResultRanker);
}; };
......
...@@ -5,8 +5,11 @@ ...@@ -5,8 +5,11 @@
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_search_result_ranker.h" #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 "ash/public/cpp/app_list/app_list_features.h"
#include "base/files/scoped_temp_dir.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h" #include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.h"
#include "chrome/browser/ui/app_list/search/search_result_ranker/app_launch_predictor.pb.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -20,52 +23,187 @@ namespace { ...@@ -20,52 +23,187 @@ namespace {
constexpr char kTarget1[] = "Target1"; constexpr char kTarget1[] = "Target1";
constexpr char kTarget2[] = "Target2"; constexpr char kTarget2[] = "Target2";
constexpr bool kNotAnEphemeralUser = false;
} // namespace } // namespace
TEST(AppSearchResultRankerTest, TrainAndInfer) { // Test flags of AppSearchResultRanker.
class AppSearchResultRankerFlagTest : public testing::Test {
protected:
void SetUp() override {
Test::SetUp();
// Creates file directory.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
}
// Waits for all tasks in to finish.
void Wait() { scoped_task_environment_.RunUntilIdle(); }
base::test::ScopedTaskEnvironment scoped_task_environment_;
base::ScopedTempDir temp_dir_;
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures( };
{features::kEnableSearchResultRankerTrain,
features::kEnableSearchResultRankerInfer},
{});
AppSearchResultRanker ranker(nullptr); TEST_F(AppSearchResultRankerFlagTest, TrainAndInfer) {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kEnableAppSearchResultRanker,
{{"app_search_result_ranker_predictor_name",
FakeAppLaunchPredictor::kPredictorName}});
AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
Wait();
ranker.Train(kTarget1); ranker.Train(kTarget1);
ranker.Train(kTarget2); ranker.Train(kTarget2);
ranker.Train(kTarget2);
const float decay = MrfuAppLaunchPredictor::decay_coeff_; EXPECT_THAT(ranker.Rank(),
UnorderedElementsAre(Pair(kTarget1, FloatEq(1.0f)),
EXPECT_THAT( Pair(kTarget2, FloatEq(2.0f))));
ranker.Rank(),
UnorderedElementsAre(Pair(kTarget1, FloatEq((1.0f - decay) * decay)),
Pair(kTarget2, FloatEq(1.0f - decay))));
} }
TEST(AppSearchResultRankerTest, ReturnEmptyIfInferIsDisabled) { TEST_F(AppSearchResultRankerFlagTest, EphemeralUsersAreDisabled) {
base::test::ScopedFeatureList scoped_feature_list_; scoped_feature_list_.InitAndEnableFeatureWithParameters(
scoped_feature_list_.InitWithFeatures( features::kEnableAppSearchResultRanker,
{features::kEnableSearchResultRankerTrain}, {{"app_search_result_ranker_predictor_name",
{features::kEnableSearchResultRankerInfer}); FakeAppLaunchPredictor::kPredictorName}});
AppSearchResultRanker ranker(nullptr); AppSearchResultRanker ranker(temp_dir_.GetPath(), !kNotAnEphemeralUser);
Wait();
ranker.Train(kTarget1); ranker.Train(kTarget1);
ranker.Train(kTarget2); ranker.Train(kTarget2);
ranker.Train(kTarget2);
EXPECT_TRUE(ranker.Rank().empty()); EXPECT_TRUE(ranker.Rank().empty());
} }
TEST(AppSearchResultRankerTest, ReturnEmptyIfTrainIsDisabled) { TEST_F(AppSearchResultRankerFlagTest, ReturnEmptyIfDisabled) {
base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures( scoped_feature_list_.InitWithFeatures(
{features::kEnableSearchResultRankerTrain}, {}, {features::kEnableAppSearchResultRanker});
{features::kEnableSearchResultRankerInfer});
AppSearchResultRanker ranker(nullptr); AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
Wait();
ranker.Train(kTarget1); ranker.Train(kTarget1);
ranker.Train(kTarget2); ranker.Train(kTarget2);
EXPECT_TRUE(ranker.Rank().empty()); EXPECT_TRUE(ranker.Rank().empty());
} }
// Test Serialization of AppSearchResultRanker.
class AppSearchResultRankerSerializationTest
: public AppSearchResultRankerFlagTest {
protected:
void SetUp() override {
AppSearchResultRankerFlagTest::SetUp();
predictor_filename_ =
temp_dir_.GetPath().AppendASCII("app_launch_predictor");
// Sets proto.
AppLaunchPredictorProto proto;
(*proto.mutable_fake_app_launch_predictor()
->mutable_rank_result())[kTarget1] = 1.0f;
(*proto.mutable_fake_app_launch_predictor()
->mutable_rank_result())[kTarget2] = 2.0f;
ASSERT_TRUE(proto.SerializeToString(&proto_str_));
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kEnableAppSearchResultRanker,
{{"app_search_result_ranker_predictor_name",
FakeAppLaunchPredictor::kPredictorName}});
}
base::FilePath predictor_filename_;
std::string proto_str_;
};
TEST_F(AppSearchResultRankerSerializationTest, LoadFromDiskSucceed) {
// Prepare file to be loaded.
EXPECT_NE(base::WriteFile(predictor_filename_, proto_str_.c_str(),
proto_str_.size()),
-1);
// Construct ranker.
AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
// Check that the file loading is executed in non-blocking way.
EXPECT_FALSE(ranker.load_from_disk_completed_);
// Wait for the loading to finish.
Wait();
// Check loading is complete.
EXPECT_TRUE(ranker.load_from_disk_completed_);
// Check predictor is loaded correctly.
EXPECT_THAT(ranker.Rank(),
UnorderedElementsAre(Pair(kTarget1, FloatEq((1.0f))),
Pair(kTarget2, FloatEq(2.0f))));
}
TEST_F(AppSearchResultRankerSerializationTest, LoadFromDiskFailIfNoFileExists) {
// Construct ranker.
AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
// Wait for the loading to finish.
Wait();
// Check loading is complete.
EXPECT_TRUE(ranker.load_from_disk_completed_);
// Check predictor is initialized.
EXPECT_TRUE(ranker.Rank().empty());
}
TEST_F(AppSearchResultRankerSerializationTest,
LoadFromDiskFailWithInvalidProto) {
const std::string wrong_proto = "abc";
// Prepare file to be loaded.
EXPECT_NE(base::WriteFile(predictor_filename_, wrong_proto.c_str(),
wrong_proto.size()),
-1);
// Construct ranker.
AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
// Wait for the loading to finish.
Wait();
// Check loading is complete.
EXPECT_TRUE(ranker.load_from_disk_completed_);
// Check predictor is initialized since the proto is not decodable.
EXPECT_TRUE(ranker.Rank().empty());
}
TEST_F(AppSearchResultRankerSerializationTest, SaveToDiskSucceed) {
// Construct ranker.
AppSearchResultRanker ranker(temp_dir_.GetPath(), kNotAnEphemeralUser);
// Wait for the loading to finish.
Wait();
// Check loading is complete.
EXPECT_TRUE(ranker.load_from_disk_completed_);
// Check predictor is initialized.
EXPECT_TRUE(ranker.Rank().empty());
ranker.Train(kTarget1);
ranker.Train(kTarget2);
// Check the predictor file is not created.
EXPECT_FALSE(base::PathExists(predictor_filename_));
// Set should_save to true.
static_cast<FakeAppLaunchPredictor*>(ranker.predictor_.get())
->SetShouldSave(true);
// Train and wait for the writing to finish.
ranker.Train(kTarget2);
Wait();
// Expect the predictor file is created.
EXPECT_TRUE(base::PathExists(predictor_filename_));
// Expect the content to be proto_str_.
std::string str_written;
EXPECT_TRUE(base::ReadFileToString(predictor_filename_, &str_written));
EXPECT_EQ(str_written, proto_str_);
}
} // namespace app_list } // namespace app_list
...@@ -336,9 +336,7 @@ TEST_F(AppSearchProviderTest, FetchUnlaunchedRecommendations) { ...@@ -336,9 +336,7 @@ TEST_F(AppSearchProviderTest, FetchUnlaunchedRecommendations) {
TEST_F(AppSearchProviderTest, FetchRecommendationsFromRanker) { TEST_F(AppSearchProviderTest, FetchRecommendationsFromRanker) {
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures( scoped_feature_list_.InitWithFeatures(
{features::kEnableSearchResultRankerTrain, {features::kEnableAppSearchResultRanker}, {});
features::kEnableSearchResultRankerInfer},
{});
CreateSearch(); CreateSearch();
extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs* prefs =
...@@ -360,8 +358,7 @@ TEST_F(AppSearchProviderTest, FetchRecommendationsFromRanker) { ...@@ -360,8 +358,7 @@ TEST_F(AppSearchProviderTest, FetchRecommendationsFromRanker) {
TEST_F(AppSearchProviderTest, RankerIsDisabledWithFlag) { TEST_F(AppSearchProviderTest, RankerIsDisabledWithFlag) {
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures( scoped_feature_list_.InitWithFeatures(
{features::kEnableSearchResultRankerTrain}, {}, {features::kEnableAppSearchResultRanker});
{features::kEnableSearchResultRankerInfer});
CreateSearch(); CreateSearch();
extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs* prefs =
......
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